/*
* Copyright (c) 2013, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.engine.internal.resolver;
import com.google.dart.engine.ast.AstNode;
import com.google.dart.engine.ast.CatchClause;
import com.google.dart.engine.ast.ClassDeclaration;
import com.google.dart.engine.ast.ClassMember;
import com.google.dart.engine.ast.ClassTypeAlias;
import com.google.dart.engine.ast.CompilationUnit;
import com.google.dart.engine.ast.ConstructorDeclaration;
import com.google.dart.engine.ast.DeclaredIdentifier;
import com.google.dart.engine.ast.DefaultFormalParameter;
import com.google.dart.engine.ast.EnumConstantDeclaration;
import com.google.dart.engine.ast.EnumDeclaration;
import com.google.dart.engine.ast.ExportDirective;
import com.google.dart.engine.ast.Expression;
import com.google.dart.engine.ast.FieldFormalParameter;
import com.google.dart.engine.ast.FormalParameter;
import com.google.dart.engine.ast.FunctionDeclaration;
import com.google.dart.engine.ast.FunctionExpression;
import com.google.dart.engine.ast.FunctionTypeAlias;
import com.google.dart.engine.ast.FunctionTypedFormalParameter;
import com.google.dart.engine.ast.ImportDirective;
import com.google.dart.engine.ast.Label;
import com.google.dart.engine.ast.LabeledStatement;
import com.google.dart.engine.ast.MethodDeclaration;
import com.google.dart.engine.ast.PartDirective;
import com.google.dart.engine.ast.SimpleFormalParameter;
import com.google.dart.engine.ast.SimpleIdentifier;
import com.google.dart.engine.ast.StringInterpolation;
import com.google.dart.engine.ast.StringLiteral;
import com.google.dart.engine.ast.SwitchCase;
import com.google.dart.engine.ast.SwitchDefault;
import com.google.dart.engine.ast.TypeParameter;
import com.google.dart.engine.ast.VariableDeclaration;
import com.google.dart.engine.ast.visitor.RecursiveAstVisitor;
import com.google.dart.engine.element.ClassElement;
import com.google.dart.engine.element.CompilationUnitElement;
import com.google.dart.engine.element.ConstructorElement;
import com.google.dart.engine.element.Element;
import com.google.dart.engine.element.ExecutableElement;
import com.google.dart.engine.element.ExportElement;
import com.google.dart.engine.element.FieldElement;
import com.google.dart.engine.element.FunctionElement;
import com.google.dart.engine.element.FunctionTypeAliasElement;
import com.google.dart.engine.element.ImportElement;
import com.google.dart.engine.element.LabelElement;
import com.google.dart.engine.element.LibraryElement;
import com.google.dart.engine.element.LocalVariableElement;
import com.google.dart.engine.element.ParameterElement;
import com.google.dart.engine.element.PrefixElement;
import com.google.dart.engine.element.PropertyAccessorElement;
import com.google.dart.engine.element.TypeParameterElement;
import com.google.dart.engine.element.VariableElement;
import com.google.dart.engine.element.visitor.GeneralizingElementVisitor;
import com.google.dart.engine.scanner.Keyword;
import com.google.dart.engine.scanner.KeywordToken;
import com.google.dart.engine.scanner.Token;
import com.google.dart.engine.scanner.TokenType;
import com.google.dart.engine.source.Source;
import java.util.HashSet;
/**
* Instances of the class {@code DeclarationMatcher} determine whether the element model defined by
* a given AST structure matches an existing element model.
*/
public class DeclarationMatcher extends RecursiveAstVisitor<Void> {
/**
* Instances of the class {@code DeclarationMismatchException} represent an exception that is
* thrown when the element model defined by a given AST structure does not match an existing
* element model.
*/
private static class DeclarationMismatchException extends RuntimeException {
public DeclarationMismatchException() {
super();
}
}
/**
* The compilation unit containing the AST nodes being visited.
*/
private CompilationUnitElement enclosingUnit;
/**
* The function type alias containing the AST nodes being visited, or {@code null} if we are not
* in the scope of a function type alias.
*/
private FunctionTypeAliasElement enclosingAlias;
/**
* The class containing the AST nodes being visited, or {@code null} if we are not in the scope of
* a class.
*/
private ClassElement enclosingClass;
/**
* The method or function containing the AST nodes being visited, or {@code null} if we are not in
* the scope of a method or function.
*/
private ExecutableElement enclosingExecutable;
/**
* The parameter containing the AST nodes being visited, or {@code null} if we are not in the
* scope of a parameter.
*/
private ParameterElement enclosingParameter;
/**
* A set containing all of the elements in the element model that were defined by the old AST node
* corresponding to the AST node being visited.
*/
private HashSet<Element> allElements = new HashSet<Element>();
/**
* A set containing all of the elements in the element model that were defined by the old AST node
* corresponding to the AST node being visited that have not already been matched to nodes in the
* AST structure being visited.
*/
private HashSet<Element> unmatchedElements = new HashSet<Element>();
/**
* Initialize a newly created visitor.
*/
public DeclarationMatcher() {
super();
}
/**
* Return {@code true} if the declarations within the given AST structure define an element model
* that is equivalent to the corresponding elements rooted at the given element.
*
* @param node the AST structure being compared to the element model
* @param element the root of the element model being compared to the AST structure
* @return {@code true} if the AST structure defines the same elements as those in the given
* element model
*/
public boolean matches(AstNode node, Element element) {
captureEnclosingElements(element);
gatherElements(element);
try {
node.accept(this);
} catch (DeclarationMismatchException exception) {
return false;
}
return unmatchedElements.isEmpty();
}
@Override
public Void visitCatchClause(CatchClause node) {
SimpleIdentifier exceptionParameter = node.getExceptionParameter();
if (exceptionParameter != null) {
LocalVariableElement[] localVariables = enclosingExecutable.getLocalVariables();
LocalVariableElement exceptionElement = findIdentifier(localVariables, exceptionParameter);
processElement(exceptionElement);
SimpleIdentifier stackTraceParameter = node.getStackTraceParameter();
if (stackTraceParameter != null) {
LocalVariableElement stackTraceElement = findIdentifier(localVariables, stackTraceParameter);
processElement(stackTraceElement);
}
}
return super.visitCatchClause(node);
}
@Override
public Void visitClassDeclaration(ClassDeclaration node) {
ClassElement outerClass = enclosingClass;
try {
SimpleIdentifier className = node.getName();
enclosingClass = findIdentifier(enclosingUnit.getTypes(), className);
processElement(enclosingClass);
if (!hasConstructor(node)) {
ConstructorElement constructor = enclosingClass.getUnnamedConstructor();
if (constructor.isSynthetic()) {
processElement(constructor);
}
}
return super.visitClassDeclaration(node);
} finally {
enclosingClass = outerClass;
}
}
@Override
public Void visitClassTypeAlias(ClassTypeAlias node) {
ClassElement outerClass = enclosingClass;
try {
SimpleIdentifier className = node.getName();
enclosingClass = findIdentifier(enclosingUnit.getTypes(), className);
processElement(enclosingClass);
return super.visitClassTypeAlias(node);
} finally {
enclosingClass = outerClass;
}
}
@Override
public Void visitCompilationUnit(CompilationUnit node) {
processElement(enclosingUnit);
return super.visitCompilationUnit(node);
}
@Override
public Void visitConstructorDeclaration(ConstructorDeclaration node) {
ExecutableElement outerExecutable = enclosingExecutable;
try {
SimpleIdentifier constructorName = node.getName();
if (constructorName == null) {
enclosingExecutable = enclosingClass.getUnnamedConstructor();
} else {
enclosingExecutable = enclosingClass.getNamedConstructor(constructorName.getName());
}
processElement(enclosingExecutable);
return super.visitConstructorDeclaration(node);
} finally {
enclosingExecutable = outerExecutable;
}
}
@Override
public Void visitDeclaredIdentifier(DeclaredIdentifier node) {
SimpleIdentifier variableName = node.getIdentifier();
LocalVariableElement element = findIdentifier(
enclosingExecutable.getLocalVariables(),
variableName);
processElement(element);
return super.visitDeclaredIdentifier(node);
}
@Override
public Void visitDefaultFormalParameter(DefaultFormalParameter node) {
SimpleIdentifier parameterName = node.getParameter().getIdentifier();
ParameterElement element = getElementForParameter(node, parameterName);
Expression defaultValue = node.getDefaultValue();
if (defaultValue != null) {
ExecutableElement outerExecutable = enclosingExecutable;
try {
if (element == null) {
// TODO(brianwilkerson) Report this internal error.
} else {
enclosingExecutable = element.getInitializer();
}
defaultValue.accept(this);
} finally {
enclosingExecutable = outerExecutable;
}
processElement(enclosingExecutable);
}
ParameterElement outerParameter = enclosingParameter;
try {
enclosingParameter = element;
processElement(enclosingParameter);
return super.visitDefaultFormalParameter(node);
} finally {
enclosingParameter = outerParameter;
}
}
@Override
public Void visitEnumDeclaration(EnumDeclaration node) {
ClassElement enclosingEnum = findIdentifier(enclosingUnit.getEnums(), node.getName());
processElement(enclosingEnum);
FieldElement[] constants = enclosingEnum.getFields();
for (EnumConstantDeclaration constant : node.getConstants()) {
FieldElement constantElement = findIdentifier(constants, constant.getName());
processElement(constantElement);
}
return super.visitEnumDeclaration(node);
}
@Override
public Void visitExportDirective(ExportDirective node) {
String uri = getStringValue(node.getUri());
if (uri != null) {
LibraryElement library = enclosingUnit.getLibrary();
ExportElement exportElement = findExport(
library.getExports(),
enclosingUnit.getContext().getSourceFactory().resolveUri(enclosingUnit.getSource(), uri));
processElement(exportElement);
}
return super.visitExportDirective(node);
}
@Override
public Void visitFieldFormalParameter(FieldFormalParameter node) {
if (!(node.getParent() instanceof DefaultFormalParameter)) {
SimpleIdentifier parameterName = node.getIdentifier();
ParameterElement element = getElementForParameter(node, parameterName);
ParameterElement outerParameter = enclosingParameter;
try {
enclosingParameter = element;
processElement(enclosingParameter);
return super.visitFieldFormalParameter(node);
} finally {
enclosingParameter = outerParameter;
}
} else {
return super.visitFieldFormalParameter(node);
}
}
@Override
public Void visitFunctionDeclaration(FunctionDeclaration node) {
ExecutableElement outerExecutable = enclosingExecutable;
try {
SimpleIdentifier functionName = node.getName();
Token property = node.getPropertyKeyword();
if (property == null) {
if (enclosingExecutable != null) {
enclosingExecutable = findIdentifier(enclosingExecutable.getFunctions(), functionName);
} else {
enclosingExecutable = findIdentifier(enclosingUnit.getFunctions(), functionName);
}
} else {
PropertyAccessorElement accessor = findIdentifier(
enclosingUnit.getAccessors(),
functionName);
if (((KeywordToken) property).getKeyword() == Keyword.SET) {
accessor = accessor.getVariable().getSetter();
}
enclosingExecutable = accessor;
}
processElement(enclosingExecutable);
return super.visitFunctionDeclaration(node);
} finally {
enclosingExecutable = outerExecutable;
}
}
@Override
public Void visitFunctionExpression(FunctionExpression node) {
if (!(node.getParent() instanceof FunctionDeclaration)) {
FunctionElement element = findAtOffset(
enclosingExecutable.getFunctions(),
node.getBeginToken().getOffset());
processElement(element);
}
ExecutableElement outerExecutable = enclosingExecutable;
try {
enclosingExecutable = node.getElement();
processElement(enclosingExecutable);
return super.visitFunctionExpression(node);
} finally {
enclosingExecutable = outerExecutable;
}
}
@Override
public Void visitFunctionTypeAlias(FunctionTypeAlias node) {
FunctionTypeAliasElement outerAlias = enclosingAlias;
try {
SimpleIdentifier aliasName = node.getName();
enclosingAlias = findIdentifier(enclosingUnit.getFunctionTypeAliases(), aliasName);
processElement(enclosingAlias);
return super.visitFunctionTypeAlias(node);
} finally {
enclosingAlias = outerAlias;
}
}
@Override
public Void visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
if (!(node.getParent() instanceof DefaultFormalParameter)) {
SimpleIdentifier parameterName = node.getIdentifier();
ParameterElement element = getElementForParameter(node, parameterName);
ParameterElement outerParameter = enclosingParameter;
try {
enclosingParameter = element;
processElement(enclosingParameter);
return super.visitFunctionTypedFormalParameter(node);
} finally {
enclosingParameter = outerParameter;
}
} else {
return super.visitFunctionTypedFormalParameter(node);
}
}
@Override
public Void visitImportDirective(ImportDirective node) {
String uri = getStringValue(node.getUri());
if (uri != null) {
LibraryElement library = enclosingUnit.getLibrary();
ImportElement importElement = findImport(
library.getImports(),
enclosingUnit.getContext().getSourceFactory().resolveUri(enclosingUnit.getSource(), uri),
node.getPrefix());
processElement(importElement);
}
return super.visitImportDirective(node);
}
@Override
public Void visitLabeledStatement(LabeledStatement node) {
for (Label label : node.getLabels()) {
SimpleIdentifier labelName = label.getLabel();
LabelElement element = findIdentifier(enclosingExecutable.getLabels(), labelName);
processElement(element);
}
return super.visitLabeledStatement(node);
}
@Override
public Void visitMethodDeclaration(MethodDeclaration node) {
ExecutableElement outerExecutable = enclosingExecutable;
try {
Token property = node.getPropertyKeyword();
SimpleIdentifier methodName = node.getName();
String nameOfMethod = methodName.getName();
if (nameOfMethod.equals(TokenType.MINUS.getLexeme())
&& node.getParameters().getParameters().size() == 0) {
nameOfMethod = "unary-";
}
if (property == null) {
enclosingExecutable = findWithNameAndOffset(
enclosingClass.getMethods(),
nameOfMethod,
methodName.getOffset());
methodName.setStaticElement(enclosingExecutable);
} else {
PropertyAccessorElement accessor = findIdentifier(enclosingClass.getAccessors(), methodName);
if (((KeywordToken) property).getKeyword() == Keyword.SET) {
accessor = accessor.getVariable().getSetter();
methodName.setStaticElement(accessor);
}
enclosingExecutable = accessor;
}
processElement(enclosingExecutable);
return super.visitMethodDeclaration(node);
} finally {
enclosingExecutable = outerExecutable;
}
}
@Override
public Void visitPartDirective(PartDirective node) {
String uri = getStringValue(node.getUri());
if (uri != null) {
Source partSource = enclosingUnit.getContext().getSourceFactory().resolveUri(
enclosingUnit.getSource(),
uri);
CompilationUnitElement element = findPart(enclosingUnit.getLibrary().getParts(), partSource);
processElement(element);
}
return super.visitPartDirective(node);
}
@Override
public Void visitSimpleFormalParameter(SimpleFormalParameter node) {
if (!(node.getParent() instanceof DefaultFormalParameter)) {
SimpleIdentifier parameterName = node.getIdentifier();
ParameterElement element = getElementForParameter(node, parameterName);
ParameterElement outerParameter = enclosingParameter;
try {
enclosingParameter = element;
processElement(enclosingParameter);
return super.visitSimpleFormalParameter(node);
} finally {
enclosingParameter = outerParameter;
}
} else {
}
return super.visitSimpleFormalParameter(node);
}
@Override
public Void visitSwitchCase(SwitchCase node) {
for (Label label : node.getLabels()) {
SimpleIdentifier labelName = label.getLabel();
LabelElement element = findIdentifier(enclosingExecutable.getLabels(), labelName);
processElement(element);
}
return super.visitSwitchCase(node);
}
@Override
public Void visitSwitchDefault(SwitchDefault node) {
for (Label label : node.getLabels()) {
SimpleIdentifier labelName = label.getLabel();
LabelElement element = findIdentifier(enclosingExecutable.getLabels(), labelName);
processElement(element);
}
return super.visitSwitchDefault(node);
}
@Override
public Void visitTypeParameter(TypeParameter node) {
SimpleIdentifier parameterName = node.getName();
TypeParameterElement element = null;
if (enclosingClass != null) {
element = findIdentifier(enclosingClass.getTypeParameters(), parameterName);
} else if (enclosingAlias != null) {
element = findIdentifier(enclosingAlias.getTypeParameters(), parameterName);
}
processElement(element);
return super.visitTypeParameter(node);
}
@Override
public Void visitVariableDeclaration(VariableDeclaration node) {
VariableElement element = null;
SimpleIdentifier variableName = node.getName();
if (enclosingExecutable != null) {
element = findIdentifier(enclosingExecutable.getLocalVariables(), variableName);
}
if (element == null && enclosingClass != null) {
element = findIdentifier(enclosingClass.getFields(), variableName);
}
if (element == null && enclosingUnit != null) {
element = findIdentifier(enclosingUnit.getTopLevelVariables(), variableName);
}
Expression initializer = node.getInitializer();
if (initializer != null) {
ExecutableElement outerExecutable = enclosingExecutable;
try {
if (element == null) {
// TODO(brianwilkerson) Report this internal error.
} else {
enclosingExecutable = element.getInitializer();
}
processElement(element);
processElement(enclosingExecutable);
return super.visitVariableDeclaration(node);
} finally {
enclosingExecutable = outerExecutable;
}
}
return super.visitVariableDeclaration(node);
}
protected void processElement(Element element) {
if (element == null) {
throw new DeclarationMismatchException();
}
if (!allElements.contains(element)) {
throw new DeclarationMismatchException();
}
unmatchedElements.remove(element);
}
/**
* Given that the comparison is to begin with the given element, capture the enclosing elements
* that might be used while performing the comparison.
*
* @param element the element corresponding to the AST structure to be compared
*/
private void captureEnclosingElements(Element element) {
Element parent = element instanceof CompilationUnitElement ? element
: element.getEnclosingElement();
while (parent != null) {
if (parent instanceof CompilationUnitElement) {
enclosingUnit = (CompilationUnitElement) parent;
} else if (parent instanceof ClassElement) {
if (enclosingClass == null) {
enclosingClass = (ClassElement) parent;
}
} else if (parent instanceof FunctionTypeAliasElement) {
if (enclosingAlias == null) {
enclosingAlias = (FunctionTypeAliasElement) parent;
}
} else if (parent instanceof ExecutableElement) {
if (enclosingExecutable == null) {
enclosingExecutable = (ExecutableElement) parent;
}
} else if (parent instanceof ParameterElement) {
if (enclosingParameter == null) {
enclosingParameter = (ParameterElement) parent;
}
}
parent = parent.getEnclosingElement();
}
}
/**
* Return the element in the given array of elements that was created for the declaration at the
* given offset. This method should only be used when there is no name
*
* @param elements the elements of the appropriate kind that exist in the current context
* @param offset the offset of the name of the element to be returned
* @return the element at the given offset
*/
private <E extends Element> E findAtOffset(E[] elements, int offset) {
return findWithNameAndOffset(elements, "", offset);
}
/**
* Return the export element from the given array whose library has the given source, or
* {@code null} if there is no such export.
*
* @param exports the export elements being searched
* @param source the source of the library associated with the export element to being searched
* for
* @return the export element whose library has the given source
*/
private ExportElement findExport(ExportElement[] exports, Source source) {
for (ExportElement export : exports) {
if (export.getExportedLibrary().getSource().equals(source)) {
return export;
}
}
return null;
}
/**
* Return the element in the given array of elements that was created for the declaration with the
* given name.
*
* @param elements the elements of the appropriate kind that exist in the current context
* @param identifier the name node in the declaration of the element to be returned
* @return the element created for the declaration with the given name
*/
private <E extends Element> E findIdentifier(E[] elements, SimpleIdentifier identifier) {
return findWithNameAndOffset(elements, identifier.getName(), identifier.getOffset());
}
/**
* Return the import element from the given array whose library has the given source and that has
* the given prefix, or {@code null} if there is no such import.
*
* @param imports the import elements being searched
* @param source the source of the library associated with the import element to being searched
* for
* @param prefix the prefix with which the library was imported
* @return the import element whose library has the given source and prefix
*/
private ImportElement findImport(ImportElement[] imports, Source source, SimpleIdentifier prefix) {
for (ImportElement element : imports) {
if (element.getImportedLibrary().getSource().equals(source)) {
PrefixElement prefixElement = element.getPrefix();
if (prefix == null) {
if (prefixElement == null) {
return element;
}
} else {
if (prefixElement != null && prefix.getName().equals(prefixElement.getDisplayName())) {
return element;
}
}
}
}
return null;
}
/**
* Return the element for the part with the given source, or {@code null} if there is no element
* for the given source.
*
* @param parts the elements for the parts
* @param partSource the source for the part whose element is to be returned
* @return the element for the part with the given source
*/
private CompilationUnitElement findPart(CompilationUnitElement[] parts, Source partSource) {
for (CompilationUnitElement part : parts) {
if (part.getSource().equals(partSource)) {
return part;
}
}
return null;
}
/**
* Return the element in the given array of elements that was created for the declaration with the
* given name at the given offset.
*
* @param elements the elements of the appropriate kind that exist in the current context
* @param name the name of the element to be returned
* @param offset the offset of the name of the element to be returned
* @return the element with the given name and offset
*/
private <E extends Element> E findWithNameAndOffset(E[] elements, String name, int offset) {
for (E element : elements) {
if (element.getDisplayName().equals(name) && element.getNameOffset() == offset) {
return element;
}
}
return null;
}
private void gatherElements(Element element) {
element.accept(new GeneralizingElementVisitor<Void>() {
@Override
public Void visitElement(Element element) {
allElements.add(element);
unmatchedElements.add(element);
return super.visitElement(element);
}
});
}
/**
* Search the most closely enclosing list of parameters for a parameter with the given name.
*
* @param node the node defining the parameter with the given name
* @param parameterName the name of the parameter being searched for
* @return the element representing the parameter with that name
*/
private ParameterElement getElementForParameter(FormalParameter node,
SimpleIdentifier parameterName) {
ParameterElement[] parameters = null;
if (enclosingParameter != null) {
parameters = enclosingParameter.getParameters();
}
if (parameters == null && enclosingExecutable != null) {
parameters = enclosingExecutable.getParameters();
}
if (parameters == null && enclosingAlias != null) {
parameters = enclosingAlias.getParameters();
}
return parameters == null ? null : findIdentifier(parameters, parameterName);
}
/**
* Return the value of the given string literal, or {@code null} if the string is not a constant
* string without any string interpolation.
*
* @param literal the string literal whose value is to be returned
* @return the value of the given string literal
*/
private String getStringValue(StringLiteral literal) {
if (literal instanceof StringInterpolation) {
return null;
}
return literal.getStringValue();
}
/**
* Return {@code true} if the given class defines at least one constructor.
*
* @param node the class being tested
* @return {@code true} if the class defines at least one constructor
*/
private boolean hasConstructor(ClassDeclaration node) {
for (ClassMember member : node.getMembers()) {
if (member instanceof ConstructorDeclaration) {
return true;
}
}
return false;
}
}