Skip to content

Commit

Permalink
introducing NameLogic and first tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ftomassetti committed Aug 4, 2018
1 parent b494dbd commit 0f83d70
Show file tree
Hide file tree
Showing 4 changed files with 722 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
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.stmt.ReturnStmt;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.type.TypeParameter;

/**
* 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?
*
* 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;
}
}

/**
* What is the Role of the given name? Does it represent a Declaration or a Reference?
*
* 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(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.DECLARATION;
}
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 (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());
}

/**
* 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,
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;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.github.javaparser.symbolsolver.resolution.naming;

/**
* Each Name can be part either of a Declaration or a Reference to a Declaration.
*/
public enum NameRole {
DECLARATION,
REFERENCE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.github.javaparser.symbolsolver.resolution.naming;

import com.github.javaparser.*;
import com.github.javaparser.ast.Node;
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
import com.github.javaparser.symbolsolver.model.resolution.TypeSolver;
import com.github.javaparser.symbolsolver.resolution.AbstractResolutionTest;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import static org.junit.Assert.assertTrue;

public abstract class AbstractNameLogicTest extends AbstractResolutionTest {

protected Node getNameInCodeTollerant(String code, String name, ParseStart parseStart) {
return getNameInCode(code, name, parseStart, true, Optional.empty());
}

protected Node getNameInCodeTollerant(String code, String name, ParseStart parseStart, TypeSolver typeSolver) {
return getNameInCode(code, name, parseStart, true, Optional.of(typeSolver));
}

protected Node getNameInCode(String code, String name, ParseStart parseStart) {
return getNameInCode(code, name, parseStart, false, Optional.empty());
}

private Node getNameInCode(String code, String name, ParseStart parseStart, boolean tollerant,
Optional<TypeSolver> typeSolver) {
ParserConfiguration parserConfiguration = new ParserConfiguration();
parserConfiguration.setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_10);
if (typeSolver.isPresent()) {
parserConfiguration.setSymbolResolver(new JavaSymbolSolver(typeSolver.get()));
}
ParseResult<? extends Node> parseResult = new JavaParser(parserConfiguration).parse(parseStart, new StringProvider(code));
if (!parseResult.isSuccessful()) {
parseResult.getProblems().forEach(p -> System.out.println("ERR: " + p));
}
assertTrue(parseResult.isSuccessful());
Node root = parseResult.getResult().get();
List<Node> allNames = root.findAll(Node.class).stream()
.filter(NameLogic::isAName)
.collect(Collectors.toList());
List<Node> matchingNames = allNames.stream()
.filter(n -> NameLogic.nameAsString(n).equals(name))
.collect(Collectors.toList());
// In case of one name being contained in other as is, we remove it
for (int i=0;i<matchingNames.size();i++) {
Node container = matchingNames.get(i);
for (int j=i+1;j<matchingNames.size();j++) {
Node contained = matchingNames.get(j);
if (contained.getParentNode().isPresent() && contained.getParentNode().get() == container
&& NameLogic.nameAsString(contained).equals(NameLogic.nameAsString(container))) {
matchingNames.remove(j);
j--;
}
}
}

if (matchingNames.size() == 0) {
throw new IllegalArgumentException("Not found. Names found: " + String.join(", ",
allNames.stream().map(NameLogic::nameAsString).collect(Collectors.toList())));
} else if (matchingNames.size() > 1) {
if (tollerant) {
return matchingNames.get(matchingNames.size() - 1);
} else {
throw new IllegalArgumentException("Ambiguous: there are several matching.");
}
} else {
return matchingNames.get(0);
}
}

}

0 comments on commit 0f83d70

Please sign in to comment.