blob: e6701b9e06b099a2dd2dcd06f20d2e00640a1cf6 [file] [log] [blame]
package com.github.javaparser.symbolsolver.resolution.naming;
import com.github.javaparser.ast.ImportDeclaration;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.PackageDeclaration;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.expr.*;
import com.github.javaparser.ast.modules.*;
import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt;
import com.github.javaparser.ast.stmt.TryStmt;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.type.TypeParameter;
import com.github.javaparser.resolution.UnsolvedSymbolException;
import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration;
import com.github.javaparser.resolution.declarations.ResolvedTypeDeclaration;
import com.github.javaparser.symbolsolver.core.resolution.Context;
import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFactory;
import com.github.javaparser.symbolsolver.model.resolution.SymbolReference;
import com.github.javaparser.symbolsolver.model.resolution.TypeSolver;
import com.github.javaparser.ast.stmt.ReturnStmt;
/**
* NameLogic contains a set of static methods to implement the abstraction of a "Name" as defined
* in Chapter 6 of the JLS. This code could be moved to an interface or base class in a successive version of
* JavaParser.
*/
public class NameLogic {
/**
* Is the given node a non-qualified name?
*
* @throws IllegalArgumentException if the node is not a name
*/
public static boolean isSimpleName(Node node) {
return !isQualifiedName(node);
}
/**
* Is the given node a qualified name?
*
* @throws IllegalArgumentException if the node is not a name
*/
public static boolean isQualifiedName(Node node) {
if (!isAName(node)) {
throw new IllegalArgumentException();
}
return nameAsString(node).contains(".");
}
/**
* Does the Node represent a Name?
* <p>
* Note that while most specific AST classes either always represent names or never represent names
* there are exceptions as the FieldAccessExpr
*/
public static boolean isAName(Node node) {
if (node instanceof FieldAccessExpr) {
FieldAccessExpr fieldAccessExpr = (FieldAccessExpr) node;
return isAName(fieldAccessExpr.getScope());
} else {
return node instanceof SimpleName ||
node instanceof Name ||
node instanceof ClassOrInterfaceType ||
node instanceof NameExpr;
}
}
private static Node getQualifier(Node node) {
if (node instanceof FieldAccessExpr) {
FieldAccessExpr fieldAccessExpr = (FieldAccessExpr) node;
return fieldAccessExpr.getScope();
}
throw new UnsupportedOperationException(node.getClass().getCanonicalName());
}
private static Node getRightMostName(Node node) {
if (node instanceof FieldAccessExpr) {
FieldAccessExpr fieldAccessExpr = (FieldAccessExpr) node;
return fieldAccessExpr.getName();
}
throw new UnsupportedOperationException(node.getClass().getCanonicalName());
}
/**
* What is the Role of the given name? Does it represent a Declaration or a Reference?
* <p>
* This classification is purely syntactical, i.e., it does not require symbol resolution. For this reason in the
* future this could be moved to the core module of JavaParser.
*/
public static NameRole classifyRole(Node name) {
if (!isAName(name)) {
throw new IllegalArgumentException("The given node is not a name");
}
if (!name.getParentNode().isPresent()) {
throw new IllegalArgumentException("We cannot understand the role of a name if it has no parent");
}
if (whenParentIs(Name.class, name, (p, c) -> p.getQualifier().isPresent() && p.getQualifier().get() == c)) {
return classifyRole(name.getParentNode().get());
}
if (whenParentIs(PackageDeclaration.class, name, (p, c) -> p.getName() == c)) {
return NameRole.DECLARATION;
}
if (whenParentIs(ImportDeclaration.class, name, (p, c) -> p.getName() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(MarkerAnnotationExpr.class, name, (p, c) -> p.getName() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(ClassOrInterfaceDeclaration.class, name, (p, c) -> p.getName() == c)) {
return NameRole.DECLARATION;
}
if (whenParentIs(ClassOrInterfaceDeclaration.class, name, (p, c) -> p.getExtendedTypes().contains(c)
|| p.getImplementedTypes().contains(c))) {
return NameRole.REFERENCE;
}
if (whenParentIs(ClassOrInterfaceType.class, name, (p, c) -> p.getName() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(VariableDeclarator.class, name, (p, c) -> p.getName() == c)) {
return NameRole.DECLARATION;
}
if (whenParentIs(NameExpr.class, name, (p, c) -> p.getName() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(FieldAccessExpr.class, name, (p, c) -> p.getName() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(MethodDeclaration.class, name, (p, c) -> p.getName() == c)) {
return NameRole.DECLARATION;
}
if (whenParentIs(Parameter.class, name, (p, c) -> p.getName() == c)) {
return NameRole.DECLARATION;
}
if (whenParentIs(MethodCallExpr.class, name, (p, c) -> p.getName() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(ConstructorDeclaration.class, name, (p, c) -> p.getName() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(AnnotationDeclaration.class, name, (p, c) -> p.getName() == c)) {
return NameRole.DECLARATION;
}
if (whenParentIs(AnnotationMemberDeclaration.class, name, (p, c) -> p.getName() == c)) {
return NameRole.DECLARATION;
}
if (whenParentIs(AnnotationMemberDeclaration.class, name, (p, c) -> p.getType() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(MethodDeclaration.class, name, (p, c) -> p.getName() == c)) {
return NameRole.DECLARATION;
}
if (whenParentIs(MethodDeclaration.class, name, (p, c) -> p.getType() == c || p.getThrownExceptions().contains(c))) {
return NameRole.REFERENCE;
}
if (whenParentIs(Parameter.class, name, (p, c) -> p.getName() == c)) {
return NameRole.DECLARATION;
}
if (whenParentIs(Parameter.class, name, (p, c) -> p.getType() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(ReceiverParameter.class, name, (p, c) -> p.getType() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(MethodCallExpr.class, name, (p, c) -> p.getName() == c ||
(p.getTypeArguments().isPresent() && p.getTypeArguments().get().contains(c)) ||
(p.getScope().isPresent() && p.getScope().get() == c))) {
return NameRole.REFERENCE;
}
if (whenParentIs(ConstructorDeclaration.class, name, (p, c) -> p.getName() == c || p.getThrownExceptions().contains(c))) {
return NameRole.REFERENCE;
}
if (whenParentIs(TypeParameter.class, name, (p, c) -> p.getName() == c)) {
return NameRole.DECLARATION;
}
if (whenParentIs(EnumDeclaration.class, name, (p, c) -> p.getName() == c)) {
return NameRole.DECLARATION;
}
if (whenParentIs(EnumConstantDeclaration.class, name, (p, c) -> p.getName() == c)) {
return NameRole.DECLARATION;
}
if (whenParentIs(FieldAccessExpr.class, name, (p, c) -> p.getName() == c || p.getScope() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(ObjectCreationExpr.class, name, (p, c) -> p.getType() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(ReturnStmt.class, name, (p, c) -> p.getExpression().isPresent() && p.getExpression().get() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(ModuleDeclaration.class, name, (p, c) -> p.getName() == c)) {
return NameRole.DECLARATION;
}
if (whenParentIs(ModuleRequiresDirective.class, name, (p, c) -> p.getName() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(ModuleExportsDirective.class, name, (p, c) -> p.getName() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(ModuleExportsDirective.class, name, (p, c) -> p.getModuleNames().contains(c))) {
return NameRole.REFERENCE;
}
if (whenParentIs(ModuleOpensDirective.class, name, (p, c) -> p.getName() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(ModuleOpensDirective.class, name, (p, c) -> p.getModuleNames().contains(c))) {
return NameRole.REFERENCE;
}
if (whenParentIs(ModuleUsesDirective.class, name, (p, c) -> p.getName() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(ModuleProvidesDirective.class, name, (p, c) -> p.getName() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(ClassExpr.class, name, (p, c) -> p.getType() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(ThisExpr.class, name, (p, c) -> p.getTypeName().isPresent() && p.getTypeName().get() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(SuperExpr.class, name, (p, c) -> p.getTypeName().isPresent() && p.getTypeName().get() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(VariableDeclarator.class, name, (p, c) -> p.getName() == c)) {
return NameRole.DECLARATION;
}
if (whenParentIs(VariableDeclarator.class, name, (p, c) -> p.getType() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(ArrayCreationExpr.class, name, (p, c) -> p.getElementType() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(CastExpr.class, name, (p, c) -> p.getType() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(InstanceOfExpr.class, name, (p, c) -> p.getType() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(TypeExpr.class, name, (p, c) -> p.getType() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(ArrayAccessExpr.class, name, (p, c) -> p.getName() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(UnaryExpr.class, name, (p, c) -> p.getExpression() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(AssignExpr.class, name, (p, c) -> p.getTarget() == c || p.getValue() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(TryStmt.class, name, (p, c) -> p.getResources().contains(c))) {
return NameRole.REFERENCE;
}
if (whenParentIs(VariableDeclarator.class, name, (p, c) -> p.getName() == c)) {
return NameRole.DECLARATION;
}
if (whenParentIs(VariableDeclarator.class, name, (p, c) -> p.getType() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(VariableDeclarator.class, name, (p, c) -> p.getInitializer().isPresent() && p.getInitializer().get() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(MemberValuePair.class, name, (p, c) -> p.getValue() == c)) {
return NameRole.REFERENCE;
}
if (whenParentIs(MemberValuePair.class, name, (p, c) -> p.getName() == c)) {
return NameRole.DECLARATION;
}
if (whenParentIs(ExplicitConstructorInvocationStmt.class, name, (p, c) ->
(p.getExpression().isPresent() && p.getExpression().get() == c) ||
(p.getTypeArguments().isPresent() && p.getTypeArguments().get().contains(c)))) {
return NameRole.REFERENCE;
}
if (whenParentIs(ObjectCreationExpr.class, name, (p, c) -> p.getType() == c ||
(p.getScope().isPresent() && p.getScope().get() == c))) {
return NameRole.REFERENCE;
}
if (name.getParentNode().isPresent() && NameLogic.isAName(name.getParentNode().get())) {
return classifyRole(name.getParentNode().get());
}
throw new UnsupportedOperationException("Unable to classify role of name contained in " + name.getParentNode().get().getClass().getSimpleName());
}
public static NameCategory classifyReference(Node name, TypeSolver typeSolver) {
if (!name.getParentNode().isPresent()) {
throw new IllegalArgumentException("We cannot understand the category of a name if it has no parent");
}
if (classifyRole(name) != NameRole.REFERENCE) {
throw new IllegalArgumentException("This method can be used only to classify names used as references");
}
// JLS 6.5
// First, context causes a name syntactically to fall into one of seven categories: ModuleName, PackageName,
// TypeName, ExpressionName, MethodName, PackageOrTypeName, or AmbiguousName.
NameCategory first = syntacticClassificationAccordingToContext(name);
// Second, a name that is initially classified by its context as an AmbiguousName or as a PackageOrTypeName is
// then reclassified to be a PackageName, TypeName, or ExpressionName.
if (first.isNeedingDisambiguation()) {
NameCategory second = reclassificationOfContextuallyAmbiguousNames(name, first, typeSolver);
assert !second.isNeedingDisambiguation();
return second;
} else {
return first;
}
}
/**
* JLS 6.5.2. Reclassification of Contextually Ambiguous Names
*/
private static NameCategory reclassificationOfContextuallyAmbiguousNames(Node name, NameCategory ambiguousCategory,
TypeSolver typeSolver) {
if (!ambiguousCategory.isNeedingDisambiguation()) {
throw new IllegalArgumentException("The Name Category is not ambiguous: " + ambiguousCategory);
}
if (ambiguousCategory == NameCategory.AMBIGUOUS_NAME && isSimpleName(name)) {
return reclassificationOfContextuallyAmbiguousSimpleAmbiguousName(name, typeSolver);
}
if (ambiguousCategory == NameCategory.AMBIGUOUS_NAME && isQualifiedName(name)) {
return reclassificationOfContextuallyAmbiguousQualifiedAmbiguousName(name, typeSolver);
}
if (ambiguousCategory == NameCategory.PACKAGE_OR_TYPE_NAME) {
return reclassificationOfContextuallyAmbiguosPackageOrTypeName(name, typeSolver);
}
throw new UnsupportedOperationException("I do not know how to handle this semantic reclassification of ambiguous name categories");
}
private static NameCategory reclassificationOfContextuallyAmbiguosPackageOrTypeName(Node name, TypeSolver typeSolver) {
// 6.5.4.1. Simple PackageOrTypeNames
//
// If the PackageOrTypeName, Q, is a valid TypeIdentifier and occurs in the scope of a type named Q, then the
// PackageOrTypeName is reclassified as a TypeName.
//
// Otherwise, the PackageOrTypeName is reclassified as a PackageName. The meaning of the PackageOrTypeName is
// the meaning of the reclassified name.
if (isSimpleName(name)) {
if (JavaParserFactory.getContext(name, typeSolver).solveType(nameAsString(name)).isSolved()) {
return NameCategory.TYPE_NAME;
} else {
return NameCategory.PACKAGE_NAME;
}
}
// 6.5.4.2. Qualified PackageOrTypeNames
//
// Given a qualified PackageOrTypeName of the form Q.Id, if Id is a valid TypeIdentifier and the type or package
// denoted by Q has a member type named Id, then the qualified PackageOrTypeName name is reclassified as a
// TypeName.
//
// Otherwise, it is reclassified as a PackageName. The meaning of the qualified PackageOrTypeName is the meaning
// of the reclassified name.
if (isQualifiedName(name)) {
if (JavaParserFactory.getContext(name, typeSolver).solveType(nameAsString(name)).isSolved()) {
return NameCategory.TYPE_NAME;
} else {
return NameCategory.PACKAGE_NAME;
}
}
throw new UnsupportedOperationException("This is unexpected: the name is neither simple or qualified");
}
private static NameCategory reclassificationOfContextuallyAmbiguousQualifiedAmbiguousName(Node nameNode,
TypeSolver typeSolver) {
// If the AmbiguousName is a qualified name, consisting of a name, a ".", and an Identifier, then the name to
// the left of the "." is first reclassified, for it is itself an AmbiguousName. There is then a choice:
Node leftName = NameLogic.getQualifier(nameNode);
String rightName = NameLogic.nameAsString(NameLogic.getRightMostName(nameNode));
NameCategory leftNameCategory = classifyReference(leftName, typeSolver);
// * If the name to the left of the "." is reclassified as a PackageName, then:
//
// * If the Identifier is a valid TypeIdentifier, and there is a package whose name is the name to the left
// of the ".", and that package contains a declaration of a type whose name is the same as the Identifier,
// then this AmbiguousName is reclassified as a TypeName.
//
// * Otherwise, this AmbiguousName is reclassified as a PackageName. A later step determines whether or not
// a package of that name actually exists.
if (leftNameCategory == NameCategory.PACKAGE_NAME) {
if (typeSolver.hasType(nameAsString(nameNode))) {
return NameCategory.TYPE_NAME;
} else {
return NameCategory.PACKAGE_NAME;
}
}
// * If the name to the left of the "." is reclassified as a TypeName, then:
//
// * If the Identifier is the name of a method or field of the type denoted by TypeName, then this
// AmbiguousName is reclassified as an ExpressionName.
//
// * Otherwise, if the Identifier is a valid TypeIdentifier and is the name of a member type of the type
// denoted by TypeName, then this AmbiguousName is reclassified as a TypeName.
//
// * Otherwise, a compile-time error occurs.
if (leftNameCategory == NameCategory.TYPE_NAME) {
SymbolReference<ResolvedTypeDeclaration> scopeTypeRef = JavaParserFactory.getContext(leftName, typeSolver)
.solveType(NameLogic.nameAsString(leftName));
if (scopeTypeRef.isSolved()) {
ResolvedTypeDeclaration scopeType = scopeTypeRef.getCorrespondingDeclaration();
if (scopeType instanceof ResolvedReferenceTypeDeclaration) {
ResolvedReferenceTypeDeclaration scopeRefType = scopeType.asReferenceType();
if (scopeRefType.getAllMethods().stream().anyMatch(m -> m.getName().equals(rightName))) {
return NameCategory.EXPRESSION_NAME;
}
if (scopeRefType.getAllFields().stream().anyMatch(f -> f.isStatic() && f.getName().equals(rightName))) {
return NameCategory.EXPRESSION_NAME;
}
if (scopeRefType.hasInternalType(rightName)) {
return NameCategory.TYPE_NAME;
}
return NameCategory.COMPILATION_ERROR;
} else {
throw new UnsupportedOperationException("The name is a type but it has been resolved to something that is not a reference type");
}
} else {
throw new UnsolvedSymbolException("Unable to solve context type: " + NameLogic.nameAsString(leftName));
}
}
// * If the name to the left of the "." is reclassified as an ExpressionName, then this AmbiguousName is
// reclassified as an ExpressionName. A later step determines whether or not a member with the name Identifier
// actually exists.
if (leftNameCategory == NameCategory.EXPRESSION_NAME) {
return NameCategory.EXPRESSION_NAME;
}
throw new UnsupportedOperationException("I do not know how to handle this semantic reclassification of ambiguous name categories");
}
private static NameCategory reclassificationOfContextuallyAmbiguousSimpleAmbiguousName(Node nameNode,
TypeSolver typeSolver) {
// If the AmbiguousName is a simple name, consisting of a single Identifier:
//
// * If the Identifier appears within the scope (§6.3) of a local variable declaration (§14.4) or parameter
// declaration (§8.4.1, §8.8.1, §14.20) or field declaration (§8.3) with that name, then the AmbiguousName is
// reclassified as an ExpressionName.
String name = nameAsString(nameNode);
Context context = JavaParserFactory.getContext(nameNode, typeSolver);
if (context.localVariableDeclarationInScope(name).isPresent()) {
return NameCategory.EXPRESSION_NAME;
}
if (context.parameterDeclarationInScope(name).isPresent()) {
return NameCategory.EXPRESSION_NAME;
}
if (context.fieldDeclarationInScope(name).isPresent()) {
return NameCategory.EXPRESSION_NAME;
}
// * Otherwise, if a field of that name is declared in the compilation unit (§7.3) containing the Identifier by
// a single-static-import declaration (§7.5.3), or by a static-import-on-demand declaration (§7.5.4) then the
// AmbiguousName is reclassified as an ExpressionName.
//
// * Otherwise, if the Identifier is a valid TypeIdentifier and appears within the scope (§6.3) of a top level
// class (§8 (Classes)) or interface type declaration (§9 (Interfaces)), a local class declaration (§14.3) or
// member type declaration (§8.5, §9.5) with that name, then the AmbiguousName is reclassified as a TypeName.
//
// * Otherwise, if the Identifier is a valid TypeIdentifier and a type of that name is declared in the
// compilation unit (§7.3) containing the Identifier, either by a single-type-import declaration (§7.5.1), or
// by a type-import-on-demand declaration (§7.5.2), or by a single-static-import declaration (§7.5.3), or by
// a static-import-on-demand declaration (§7.5.4), then the AmbiguousName is reclassified as a TypeName.
//
// Otherwise, the AmbiguousName is reclassified as a PackageName. A later step determines whether or not a
// package of that name actually exists.
return NameCategory.PACKAGE_NAME;
}
/**
* See JLS 6.5.1 Syntactic Classification of a Name According to Context.
* <p>
* Most users do not want to call directly this method but call classifyReference instead.
*/
public static NameCategory syntacticClassificationAccordingToContext(Node name) {
if (name.getParentNode().isPresent()) {
Node parent = name.getParentNode().get();
if (isAName(parent) && nameAsString(name).equals(nameAsString(parent))) {
return syntacticClassificationAccordingToContext(parent);
}
}
if (isSyntacticallyATypeName(name)) {
return NameCategory.TYPE_NAME;
}
if (isSyntacticallyAnExpressionName(name)) {
return NameCategory.EXPRESSION_NAME;
}
if (isSyntacticallyAMethodName(name)) {
return NameCategory.METHOD_NAME;
}
if (isSyntacticallyAPackageOrTypeName(name)) {
return NameCategory.PACKAGE_OR_TYPE_NAME;
}
if (isSyntacticallyAAmbiguousName(name)) {
return NameCategory.AMBIGUOUS_NAME;
}
if (isSyntacticallyAModuleName(name)) {
return NameCategory.MODULE_NAME;
}
if (isSyntacticallyAPackageName(name)) {
return NameCategory.PACKAGE_NAME;
}
if (name instanceof NameExpr) {
return NameCategory.EXPRESSION_NAME;
}
if (name instanceof FieldAccessExpr) {
return NameCategory.EXPRESSION_NAME;
}
if (name instanceof ClassOrInterfaceType) {
return NameCategory.TYPE_NAME;
}
if (name.getParentNode().isPresent() && name.getParentNode().get() instanceof ClassOrInterfaceType) {
return NameCategory.TYPE_NAME;
}
if (name.getParentNode().isPresent() && name.getParentNode().get() instanceof FieldAccessExpr) {
return NameCategory.EXPRESSION_NAME;
}
throw new UnsupportedOperationException("Unable to classify category of name contained in "
+ name.getParentNode().get().getClass().getSimpleName() + ". See " + name + " at " + name.getRange());
}
private static boolean isSyntacticallyAAmbiguousName(Node name) {
// A name is syntactically classified as an AmbiguousName in these contexts:
//
// 1. To the left of the "." in a qualified ExpressionName
if (whenParentIs(FieldAccessExpr.class, name, (p, c) -> p.getScope() == c)) {
return true;
}
// 2. To the left of the rightmost . that occurs before the "(" in a method invocation expression
if (whenParentIs(MethodCallExpr.class, name, (p, c) -> p.getScope().isPresent() && p.getScope().get() == c)) {
return true;
}
// 3. To the left of the "." in a qualified AmbiguousName
//
// 4. In the default value clause of an annotation type element declaration (§9.6.2)
//
// 5. To the right of an "=" in an an element-value pair (§9.7.1)
if (whenParentIs(MemberValuePair.class, name, (p, c) -> p.getValue() == c)) {
return true;
}
// 6. To the left of :: in a method reference expression (§15.13)
return false;
}
private static boolean isSyntacticallyAPackageOrTypeName(Node name) {
// A name is syntactically classified as a PackageOrTypeName in these contexts:
//
// 1. To the left of the "." in a qualified TypeName
if (whenParentIs(ClassOrInterfaceType.class, name, (p, c) -> p.getScope().isPresent() && p.getScope().get() == c && (isSyntacticallyATypeName(p) || isSyntacticallyAPackageOrTypeName(p)))) {
return true;
}
// 2. In a type-import-on-demand declaration (§7.5.2)
if (whenParentIs(ImportDeclaration.class, name, (p, c) ->
!p.isStatic() && p.isAsterisk() && p.getName() == name)) {
return true;
}
return false;
}
private static boolean isSyntacticallyAMethodName(Node name) {
// A name is syntactically classified as a MethodName in this context:
//
// 1. Before the "(" in a method invocation expression (§15.12)
if (whenParentIs(MethodCallExpr.class, name, (p, c) -> p.getName() == c)) {
return true;
}
return false;
}
private static boolean isSyntacticallyAModuleName(Node name) {
// A name is syntactically classified as a ModuleName in these contexts:
//
// 1. In a requires directive in a module declaration (§7.7.1)
if (whenParentIs(ModuleRequiresDirective.class, name, (p, c) -> p.getName() == name)) {
return true;
}
// 2. To the right of to in an exports or opens directive in a module declaration (§7.7.2)
if (whenParentIs(ModuleExportsDirective.class, name, (p, c) -> p.getModuleNames().contains(name))) {
return true;
}
if (whenParentIs(ModuleOpensDirective.class, name, (p, c) -> p.getModuleNames().contains(name))) {
return true;
}
return false;
}
private static boolean isSyntacticallyAPackageName(Node name) {
// A name is syntactically classified as a PackageName in these contexts:
//
// 1. To the right of exports or opens in a module declaration
if (whenParentIs(ModuleExportsDirective.class, name, (p, c) -> p.getName() == name)) {
return true;
}
if (whenParentIs(ModuleOpensDirective.class, name, (p, c) -> p.getName() == name)) {
return true;
}
// 2. To the left of the "." in a qualified PackageName
if (whenParentIs(Name.class, name, (p, c) -> p.getQualifier().isPresent()
&& p.getQualifier().get() == name
&& isSyntacticallyAPackageName(p))) {
return true;
}
return false;
}
private static boolean isSyntacticallyATypeName(Node name) {
// A name is syntactically classified as a TypeName in these contexts:
//
// The first eleven non-generic contexts (§6.1):
//
// 1. In a uses or provides directive in a module declaration (§7.7.1)
if (whenParentIs(ModuleUsesDirective.class, name, (p, c) -> p.getName() == c)) {
return true;
}
if (whenParentIs(ModuleProvidesDirective.class, name, (p, c) -> p.getName() == c)) {
return true;
}
// 2. In a single-type-import declaration (§7.5.1)
if (whenParentIs(ImportDeclaration.class, name, (p, c) ->
!p.isStatic() && !p.isAsterisk() && p.getName() == name)) {
return true;
}
// 3. To the left of the . in a single-static-import declaration (§7.5.3)
if (whenParentIs(Name.class, name, (largerName, c) ->
whenParentIs(ImportDeclaration.class, largerName, (importDecl, c2) ->
importDecl.isStatic() && !importDecl.isAsterisk() && importDecl.getName() == c2)
)) {
return true;
}
if (whenParentIs(ImportDeclaration.class, name, (importDecl, c2) ->
importDecl.isStatic() && !importDecl.isAsterisk() && importDecl.getName() == c2)) {
return true;
}
// 4. To the left of the . in a static-import-on-demand declaration (§7.5.4)
if (whenParentIs(ImportDeclaration.class, name, (p, c) ->
p.isStatic() && p.isAsterisk() && p.getName() == name)) {
return true;
}
// 5. To the left of the ( in a constructor declaration (§8.8)
if (whenParentIs(ConstructorDeclaration.class, name, (p, c) -> p.getName() == name)) {
return true;
}
// 6. After the @ sign in an annotation (§9.7)
if (whenParentIs(AnnotationExpr.class, name, (p, c) -> p.getName() == name)) {
return true;
}
// 7. To the left of .class in a class literal (§15.8.2)
if (whenParentIs(ClassExpr.class, name, (p, c) -> p.getType() == c)) {
return true;
}
// 8. To the left of .this in a qualified this expression (§15.8.4)
if (whenParentIs(ThisExpr.class, name, (ne, c2) ->
ne.getTypeName().isPresent() && ne.getTypeName().get() == c2)) {
return true;
}
// 9. To the left of .super in a qualified superclass field access expression (§15.11.2)
if (whenParentIs(SuperExpr.class, name, (ne, c2) ->
ne.getTypeName().isPresent() && ne.getTypeName().get() == c2)) {
return true;
}
// 10. To the left of .Identifier or .super.Identifier in a qualified method invocation expression (§15.12)
//
// 11. To the left of .super:: in a method reference expression (§15.13)
//
// As the Identifier or dotted Identifier sequence that constitutes any ReferenceType (including a
// ReferenceType to the left of the brackets in an array type, or to the left of the < in a parameterized type,
// or in a non-wildcard type argument of a parameterized type, or in an extends or super clause of a wildcard
// type argument of a parameterized type) in the 16 contexts where types are used (§4.11):
//
// 1. In an extends or implements clause of a class declaration (§8.1.4, §8.1.5, §8.5, §9.5)
// 2. In an extends clause of an interface declaration (§9.1.3)
if (whenParentIs(ClassOrInterfaceDeclaration.class, name, (p, c) ->
p.getExtendedTypes().contains(c) || p.getImplementedTypes().contains(c))) {
return true;
}
// 3. The return type of a method (§8.4, §9.4) (including the type of an element of an annotation type (§9.6.1))
if (whenParentIs(MethodDeclaration.class, name, (p, c) ->
p.getType() == c)) {
return true;
}
if (whenParentIs(AnnotationMemberDeclaration.class, name, (p, c) ->
p.getType() == c)) {
return true;
}
// 4. In the throws clause of a method or constructor (§8.4.6, §8.8.5, §9.4)
if (whenParentIs(MethodDeclaration.class, name, (p, c) ->
p.getThrownExceptions().contains(c))) {
return true;
}
if (whenParentIs(ConstructorDeclaration.class, name, (p, c) ->
p.getThrownExceptions().contains(c))) {
return true;
}
// 5. In an extends clause of a type parameter declaration of a generic class, interface, method, or
// constructor (§8.1.2, §9.1.2, §8.4.4, §8.8.4)
//
// 6. The type in a field declaration of a class or interface (§8.3, §9.3)
if (whenParentIs(VariableDeclarator.class, name, (p1, c1) ->
p1.getType() == c1 && whenParentIs(FieldDeclaration.class, p1, (p2, c2) ->
p2.getVariables().contains(c2)))) {
return true;
}
// 7. The type in a formal parameter declaration of a method, constructor, or lambda expression
// (§8.4.1, §8.8.1, §9.4, §15.27.1)
if (whenParentIs(Parameter.class, name, (p, c) ->
p.getType() == c)) {
return true;
}
// 8. The type of the receiver parameter of a method (§8.4.1)
if (whenParentIs(ReceiverParameter.class, name, (p, c) ->
p.getType() == c)) {
return true;
}
// 9. The type in a local variable declaration (§14.4, §14.14.1, §14.14.2, §14.20.3)
if (whenParentIs(VariableDeclarator.class, name, (p1, c1) ->
p1.getType() == c1 && whenParentIs(VariableDeclarationExpr.class, p1, (p2, c2) ->
p2.getVariables().contains(c2)))) {
return true;
}
// 10. A type in an exception parameter declaration (§14.20)
//
// 11. In an explicit type argument list to an explicit constructor invocation statement or class instance
// creation expression or method invocation expression (§8.8.7.1, §15.9, §15.12)
if (whenParentIs(ClassOrInterfaceType.class, name, (p, c) ->
p.getTypeArguments().isPresent() && p.getTypeArguments().get().contains(c))) {
return true;
}
if (whenParentIs(MethodCallExpr.class, name, (p, c) ->
p.getTypeArguments().isPresent() && p.getTypeArguments().get().contains(c))) {
return true;
}
// 12. In an unqualified class instance creation expression, either as the class type to be instantiated (§15.9)
// or as the direct superclass or direct superinterface of an anonymous class to be instantiated (§15.9.5)
if (whenParentIs(ObjectCreationExpr.class, name, (p, c) ->
p.getType() == c)) {
return true;
}
// 13. The element type in an array creation expression (§15.10.1)
if (whenParentIs(ArrayCreationExpr.class, name, (p, c) ->
p.getElementType() == c)) {
return true;
}
// 14. The type in the cast operator of a cast expression (§15.16)
if (whenParentIs(CastExpr.class, name, (p, c) ->
p.getType() == c)) {
return true;
}
// 15. The type that follows the instanceof relational operator (§15.20.2)
if (whenParentIs(InstanceOfExpr.class, name, (p, c) ->
p.getType() == c)) {
return true;
}
// 16. In a method reference expression (§15.13), as the reference type to search for a member method or as the class type or array type to construct.
if (whenParentIs(TypeExpr.class, name, (p1, c1) ->
p1.getType() == c1 && whenParentIs(MethodReferenceExpr.class, p1, (p2, c2) ->
p2.getScope() == c2)
)) {
return true;
}
// The extraction of a TypeName from the identifiers of a ReferenceType in the 16 contexts above is intended to
// apply recursively to all sub-terms of the ReferenceType, such as its element type and any type arguments.
//
// For example, suppose a field declaration uses the type p.q.Foo[]. The brackets of the array type are ignored,
// and the term p.q.Foo is extracted as a dotted sequence of Identifiers to the left of the brackets in an array
// type, and classified as a TypeName. A later step determines which of p, q, and Foo is a type name or a
// package name.
//
// As another example, suppose a cast operator uses the type p.q.Foo<? extends String>. The term p.q.Foo is
// again extracted as a dotted sequence of Identifier terms, this time to the left of the < in a parameterized
// type, and classified as a TypeName. The term String is extracted as an Identifier in an extends clause of a
// wildcard type argument of a parameterized type, and classified as a TypeName.
return false;
}
private static boolean isSyntacticallyAnExpressionName(Node name) {
// A name is syntactically classified as an ExpressionName in these contexts:
//
// 1. As the qualifying expression in a qualified superclass constructor invocation (§8.8.7.1)
if (whenParentIs(NameExpr.class, name, (nameExpr, c) ->
nameExpr.getName() == c && whenParentIs(ExplicitConstructorInvocationStmt.class, nameExpr, (ne, c2) ->
ne.getExpression().isPresent() && ne.getExpression().get() == c2)
)) {
return true;
}
if (whenParentIs(ExplicitConstructorInvocationStmt.class, name, (ne, c2) ->
ne.getExpression().isPresent() && ne.getExpression().get() == c2)) {
return true;
}
// 2. As the qualifying expression in a qualified class instance creation expression (§15.9)
if (whenParentIs(NameExpr.class, name, (nameExpr, c) ->
nameExpr.getName() == c && whenParentIs(ObjectCreationExpr.class, nameExpr, (ne, c2) ->
ne.getScope().isPresent() && ne.getScope().get() == c2)
)) {
return true;
}
if (whenParentIs(ObjectCreationExpr.class, name, (ne, c2) ->
ne.getScope().isPresent() && ne.getScope().get() == c2)) {
return true;
}
// 3. As the array reference expression in an array access expression (§15.10.3)
if (whenParentIs(NameExpr.class, name, (nameExpr, c) ->
nameExpr.getName() == c && whenParentIs(ArrayAccessExpr.class, nameExpr, (ne, c2) ->
ne.getName() == c2)
)) {
return true;
}
if (whenParentIs(ArrayAccessExpr.class, name, (ne, c2) ->
ne.getName() == c2)) {
return true;
}
// 4. As a PostfixExpression (§15.14)
if (whenParentIs(NameExpr.class, name, (nameExpr, c) ->
nameExpr.getName() == c && whenParentIs(UnaryExpr.class, nameExpr, (ne, c2) ->
ne.getExpression() == c2 && ne.isPostfix())
)) {
return true;
}
if (whenParentIs(UnaryExpr.class, name, (ne, c2) ->
ne.getExpression() == c2 && ne.isPostfix())) {
return true;
}
// 5. As the left-hand operand of an assignment operator (§15.26)
if (whenParentIs(NameExpr.class, name, (nameExpr, c) ->
nameExpr.getName() == c && whenParentIs(AssignExpr.class, nameExpr, (ne, c2) ->
ne.getTarget() == c2)
)) {
return true;
}
if (whenParentIs(AssignExpr.class, name, (ne, c2) ->
ne.getTarget() == c2)) {
return true;
}
// 6. As a VariableAccess in a try-with-resources statement (§14.20.3)
if (whenParentIs(NameExpr.class, name, (nameExpr, c) ->
nameExpr.getName() == c && whenParentIs(TryStmt.class, nameExpr, (ne, c2) ->
ne.getResources().contains(c2))
)) {
return true;
}
if (whenParentIs(NameExpr.class, name, (p1 /*NameExpr*/, c1 /*SimpleName*/) ->
p1.getName() == c1 && whenParentIs(VariableDeclarator.class, p1, (p2, c2) ->
p2.getInitializer().isPresent() && p2.getInitializer().get() == c2 && whenParentIs(VariableDeclarationExpr.class, p2, (p3, c3) ->
p3.getVariables().contains(c3) && whenParentIs(TryStmt.class, p3, (p4, c4) ->
p4.getResources().contains(c4)
)
))
)) {
return true;
}
if (whenParentIs(TryStmt.class, name, (ne, c2) ->
ne.getResources().contains(c2))) {
return true;
}
if (whenParentIs(VariableDeclarator.class, name, (p2, c2) ->
p2.getInitializer().isPresent() && p2.getInitializer().get() == c2 && whenParentIs(VariableDeclarationExpr.class, p2, (p3, c3) ->
p3.getVariables().contains(c3) && whenParentIs(TryStmt.class, p3, (p4, c4) ->
p4.getResources().contains(c4)
)
))) {
return true;
}
return false;
}
/**
* Return the string representation of the name
*/
public static String nameAsString(Node name) {
if (!isAName(name)) {
throw new IllegalArgumentException("A name was expected");
}
if (name instanceof Name) {
return ((Name) name).asString();
} else if (name instanceof SimpleName) {
return ((SimpleName) name).getIdentifier();
} else if (name instanceof ClassOrInterfaceType) {
return ((ClassOrInterfaceType) name).asString();
} else if (name instanceof FieldAccessExpr) {
FieldAccessExpr fieldAccessExpr = (FieldAccessExpr) name;
if (isAName(fieldAccessExpr.getScope())) {
return nameAsString(fieldAccessExpr.getScope()) + "." + nameAsString(fieldAccessExpr.getName());
} else {
throw new IllegalArgumentException();
}
} else if (name instanceof NameExpr) {
return ((NameExpr) name).getNameAsString();
} else {
throw new UnsupportedOperationException("Unknown type of name found: " + name + " ("
+ name.getClass().getCanonicalName() + ")");
}
}
private interface PredicateOnParentAndChild<P extends Node, C extends Node> {
boolean isSatisfied(P parent, C child);
}
private static <P extends Node, C extends Node> boolean whenParentIs(Class<P> parentClass, C child) {
return whenParentIs(parentClass, child, (p, c) -> true);
}
private static <P extends Node, C extends Node> boolean whenParentIs(
Class<P> parentClass,
C child,
PredicateOnParentAndChild<P, C> predicate) {
if (child.getParentNode().isPresent()) {
Node parent = child.getParentNode().get();
return parentClass.isInstance(parent) && predicate.isSatisfied(parentClass.cast(parent), child);
} else {
return false;
}
}
}