diff --git a/README.md b/README.md index 0ce70e293fbd049e02b21de2a60784048e8e840b..b9ed10d230a515e4327d39f3273e6f60b81cae25 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Warning: all method calls must resolve to a method declaration. If your Java pro JavaSDGSlicer manages its dependencies through maven, so you need to have the JDK (≥11) and Maven installed, then run ``` -mvn package +mvn package -Dmaven.test.skip ``` A fat jar containing all the project's dependencies can be then located at `./sdg-cli/target/sdg-cli-{version}-jar-with-dependencies.jar`. @@ -57,7 +57,7 @@ java -jar sdg-cli.jar --help Our slicer requires the input Java program to be compilable, so all libraries must be provided using the `-i` flag. For the cases where the source code is not available, you may include the required libraries in the Java classpath by using the following call: ``` -java -cp sdg-cli.jar:your-libraries.jar es.upv.slicing.cli.Slicer -c Example.java#11:sum +java -cp your-libraries.jar -jar sdg-cli.jar -c Example.java#11:sum ``` This approach produces lower quality slices, as the contents of the library calls are unknown. @@ -77,6 +77,4 @@ If the graph is of interest, it can be outputted in `dot` or PDF format via `SDG ## Missing Java features -* Object-oriented features: abstract classes, interfaces, class, method and field inheritance, anonymous classes, lambdas. * Parallel features: threads, shared memory, synchronized methods, etc. -* Exception handling: `finally`, try with resources. diff --git a/javaparser-symbol-solver-core/pom.xml b/javaparser-symbol-solver-core/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..c07194d39a0050bcaaabba52f5b62d024be6b48f --- /dev/null +++ b/javaparser-symbol-solver-core/pom.xml @@ -0,0 +1,69 @@ + + + + sdg + es.upv.mist.slicing + 1.3.0 + + 4.0.0 + + javaparser-symbol-solver-core + com.github.javaparser + 3.23.2 + jar + A Symbol Solver for Java, built on top of JavaParser (core) + + + + GNU Lesser General Public License + http://www.gnu.org/licenses/lgpl-3.0.html + repo + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + A business-friendly OSS license + + + + + 1.8 + ${maven.build.timestamp} + + + + + com.github.javaparser + javaparser-core + 3.23.1 + + + org.javassist + javassist + 3.28.0-GA + + + com.google.guava + guava + 31.0.1-jre + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + com.github.javaparser.symbolsolver.core + + + + + + + diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/JavaSymbolSolver.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/JavaSymbolSolver.java new file mode 100644 index 0000000000000000000000000000000000000000..d93a52cfd3f0f0c87db4666df1cb83982dbaa603 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/JavaSymbolSolver.java @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.*; +import com.github.javaparser.ast.expr.*; +import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt; +import com.github.javaparser.ast.type.Type; +import com.github.javaparser.resolution.SymbolResolver; +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedPrimitiveType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFactory; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.*; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +/** + * This implementation of the SymbolResolver wraps the functionality of the library to make them easily usable + * from JavaParser nodes. + *

+ * An instance of this class should be created once and then injected in all the CompilationUnit for which we + * want to enable symbol resolution. To do so the method inject can be used, or you can use + * {@link com.github.javaparser.ParserConfiguration#setSymbolResolver(SymbolResolver)} and the parser will do the + * injection for you. + * + * @author Federico Tomassetti + */ +public class JavaSymbolSolver implements SymbolResolver { + + private static class ArrayLengthValueDeclaration implements ResolvedValueDeclaration { + + private static final ArrayLengthValueDeclaration INSTANCE = new ArrayLengthValueDeclaration(); + + private ArrayLengthValueDeclaration() { + + } + + @Override + public String getName() { + return "length"; + } + + @Override + public ResolvedType getType() { + return ResolvedPrimitiveType.INT; + } + } + + private TypeSolver typeSolver; + + public JavaSymbolSolver(TypeSolver typeSolver) { + this.typeSolver = typeSolver; + } + + /** + * Register this SymbolResolver into a CompilationUnit, so that symbol resolution becomes available to + * all nodes part of the CompilationUnit. + */ + public void inject(CompilationUnit destination) { + destination.setData(Node.SYMBOL_RESOLVER_KEY, this); + } + + @Override + public T resolveDeclaration(Node node, Class resultClass) { + if (node instanceof MethodDeclaration) { + return resultClass.cast(new JavaParserMethodDeclaration((MethodDeclaration) node, typeSolver)); + } + if (node instanceof ClassOrInterfaceDeclaration) { + ResolvedReferenceTypeDeclaration resolved = JavaParserFactory.toTypeDeclaration(node, typeSolver); + if (resultClass.isInstance(resolved)) { + return resultClass.cast(resolved); + } + } + if (node instanceof EnumDeclaration) { + ResolvedReferenceTypeDeclaration resolved = JavaParserFactory.toTypeDeclaration(node, typeSolver); + if (resultClass.isInstance(resolved)) { + return resultClass.cast(resolved); + } + } + if (node instanceof EnumConstantDeclaration) { + ResolvedEnumDeclaration enumDeclaration = node.findAncestor(EnumDeclaration.class).get().resolve().asEnum(); + ResolvedEnumConstantDeclaration resolved = enumDeclaration.getEnumConstants().stream().filter(c -> ((JavaParserEnumConstantDeclaration) c).getWrappedNode() == node).findFirst().get(); + if (resultClass.isInstance(resolved)) { + return resultClass.cast(resolved); + } + } + if (node instanceof ConstructorDeclaration) { + ConstructorDeclaration constructorDeclaration = (ConstructorDeclaration) node; + TypeDeclaration typeDeclaration = (TypeDeclaration) node.getParentNode().get(); + ResolvedReferenceTypeDeclaration resolvedTypeDeclaration = resolveDeclaration(typeDeclaration, ResolvedReferenceTypeDeclaration.class); + ResolvedConstructorDeclaration resolved = resolvedTypeDeclaration.getConstructors().stream() + .filter(c -> c instanceof JavaParserConstructorDeclaration) + .filter(c -> ((JavaParserConstructorDeclaration) c).getWrappedNode() == constructorDeclaration) + .findFirst() + .orElseThrow(() -> new RuntimeException("This constructor cannot be found in its parent. This seems wrong")); + if (resultClass.isInstance(resolved)) { + return resultClass.cast(resolved); + } + } + if (node instanceof AnnotationDeclaration) { + ResolvedReferenceTypeDeclaration resolved = JavaParserFactory.toTypeDeclaration(node, typeSolver); + if (resultClass.isInstance(resolved)) { + return resultClass.cast(resolved); + } + } + if (node instanceof AnnotationMemberDeclaration) { + ResolvedAnnotationDeclaration annotationDeclaration = node.findAncestor(AnnotationDeclaration.class).get().resolve(); + ResolvedAnnotationMemberDeclaration resolved = annotationDeclaration.getAnnotationMembers().stream().filter(c -> ((JavaParserAnnotationMemberDeclaration) c).getWrappedNode() == node).findFirst().get(); + if (resultClass.isInstance(resolved)) { + return resultClass.cast(resolved); + } + } + if (node instanceof FieldDeclaration) { + FieldDeclaration fieldDeclaration = (FieldDeclaration) node; + if (fieldDeclaration.getVariables().size() != 1) { + throw new RuntimeException("Cannot resolve a Field Declaration including multiple variable declarators. Resolve the single variable declarators"); + } + ResolvedFieldDeclaration resolved = new JavaParserFieldDeclaration(fieldDeclaration.getVariable(0), typeSolver); + if (resultClass.isInstance(resolved)) { + return resultClass.cast(resolved); + } + } + if (node instanceof VariableDeclarator) { + ResolvedValueDeclaration resolved; + if (node.getParentNode().isPresent() && node.getParentNode().get() instanceof FieldDeclaration) { + resolved = new JavaParserFieldDeclaration((VariableDeclarator) node, typeSolver); + } else if (node.getParentNode().isPresent() && node.getParentNode().get() instanceof VariableDeclarationExpr) { + resolved = new JavaParserVariableDeclaration((VariableDeclarator) node, typeSolver); + } else { + throw new UnsupportedOperationException("Parent of VariableDeclarator is: " + node.getParentNode()); + } + if (resultClass.isInstance(resolved)) { + return resultClass.cast(resolved); + } + } + if (node instanceof MethodCallExpr) { + SymbolReference result = JavaParserFacade.get(typeSolver).solve((MethodCallExpr) node); + if (result.isSolved()) { + if (resultClass.isInstance(result.getCorrespondingDeclaration())) { + return resultClass.cast(result.getCorrespondingDeclaration()); + } + } else { + throw new UnsolvedSymbolException("We are unable to find the method declaration corresponding to " + node); + } + } + if (node instanceof ObjectCreationExpr) { + SymbolReference result = JavaParserFacade.get(typeSolver).solve((ObjectCreationExpr) node); + if (result.isSolved()) { + if (resultClass.isInstance(result.getCorrespondingDeclaration())) { + return resultClass.cast(result.getCorrespondingDeclaration()); + } + } else { + throw new UnsolvedSymbolException("We are unable to find the constructor declaration corresponding to " + node); + } + } + if (node instanceof NameExpr) { + SymbolReference result = JavaParserFacade.get(typeSolver).solve((NameExpr) node); + if (result.isSolved()) { + if (resultClass.isInstance(result.getCorrespondingDeclaration())) { + return resultClass.cast(result.getCorrespondingDeclaration()); + } + } else { + throw new UnsolvedSymbolException("We are unable to find the value declaration corresponding to " + node); + } + } + if (node instanceof MethodReferenceExpr) { + SymbolReference result = JavaParserFacade.get(typeSolver).solve((MethodReferenceExpr) node); + if (result.isSolved()) { + if (resultClass.isInstance(result.getCorrespondingDeclaration())) { + return resultClass.cast(result.getCorrespondingDeclaration()); + } + } else { + throw new UnsolvedSymbolException("We are unable to find the method declaration corresponding to " + node); + } + } + if (node instanceof FieldAccessExpr) { + SymbolReference result = JavaParserFacade.get(typeSolver).solve((FieldAccessExpr) node); + if (result.isSolved()) { + if (resultClass.isInstance(result.getCorrespondingDeclaration())) { + return resultClass.cast(result.getCorrespondingDeclaration()); + } + } else { + if (((FieldAccessExpr) node).getName().getId().equals("length")) { + ResolvedType scopeType = ((FieldAccessExpr) node).getScope().calculateResolvedType(); + if (scopeType.isArray()) { + if (resultClass.isInstance(ArrayLengthValueDeclaration.INSTANCE)) { + return resultClass.cast(ArrayLengthValueDeclaration.INSTANCE); + } + } + } + throw new UnsolvedSymbolException("We are unable to find the value declaration corresponding to " + node); + } + } + if (node instanceof ThisExpr) { + SymbolReference result = JavaParserFacade.get(typeSolver).solve((ThisExpr) node); + if (result.isSolved()) { + if (resultClass.isInstance(result.getCorrespondingDeclaration())) { + return resultClass.cast(result.getCorrespondingDeclaration()); + } + } else { + throw new UnsolvedSymbolException("We are unable to find the type declaration corresponding to " + node); + } + } + if (node instanceof ExplicitConstructorInvocationStmt) { + SymbolReference result = JavaParserFacade.get(typeSolver).solve((ExplicitConstructorInvocationStmt) node); + if (result.isSolved()) { + if (resultClass.isInstance(result.getCorrespondingDeclaration())) { + return resultClass.cast(result.getCorrespondingDeclaration()); + } + } else { + throw new UnsolvedSymbolException("We are unable to find the constructor declaration corresponding to " + node); + } + } + if (node instanceof Parameter) { + if (ResolvedParameterDeclaration.class.equals(resultClass)) { + Parameter parameter = (Parameter) node; + CallableDeclaration callableDeclaration = node.findAncestor(CallableDeclaration.class).get(); + ResolvedMethodLikeDeclaration resolvedMethodLikeDeclaration; + if (callableDeclaration.isConstructorDeclaration()) { + resolvedMethodLikeDeclaration = callableDeclaration.asConstructorDeclaration().resolve(); + } else { + resolvedMethodLikeDeclaration = callableDeclaration.asMethodDeclaration().resolve(); + } + for (int i = 0; i < resolvedMethodLikeDeclaration.getNumberOfParams(); i++) { + if (resolvedMethodLikeDeclaration.getParam(i).getName().equals(parameter.getNameAsString())) { + return resultClass.cast(resolvedMethodLikeDeclaration.getParam(i)); + } + } + } + } + if (node instanceof AnnotationExpr) { + SymbolReference result = JavaParserFacade.get(typeSolver).solve((AnnotationExpr) node); + if (result.isSolved()) { + if (resultClass.isInstance(result.getCorrespondingDeclaration())) { + return resultClass.cast(result.getCorrespondingDeclaration()); + } + } else { + throw new UnsolvedSymbolException("We are unable to find the annotation declaration corresponding to " + node); + } + } + if (node instanceof PatternExpr) { + SymbolReference result = JavaParserFacade.get(typeSolver).solve((PatternExpr) node); + if (result.isSolved()) { + if (resultClass.isInstance(result.getCorrespondingDeclaration())) { + return resultClass.cast(result.getCorrespondingDeclaration()); + } + } else { + throw new UnsolvedSymbolException("We are unable to find the method declaration corresponding to " + node); + } + } + throw new UnsupportedOperationException("Unable to find the declaration of type " + resultClass.getSimpleName() + + " from " + node.getClass().getSimpleName()); + } + + @Override + public T toResolvedType(Type javaparserType, Class resultClass) { + ResolvedType resolvedType = JavaParserFacade.get(typeSolver).convertToUsage(javaparserType, javaparserType); + if (resultClass.isInstance(resolvedType)) { + return resultClass.cast(resolvedType); + } + throw new UnsupportedOperationException("Unable to get the resolved type of class " + + resultClass.getSimpleName() + " from " + javaparserType); + } + + @Override + public ResolvedType calculateType(Expression expression) { + return JavaParserFacade.get(typeSolver).getType(expression); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/SourceFileInfoExtractor.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/SourceFileInfoExtractor.java new file mode 100644 index 0000000000000000000000000000000000000000..03f23c498ee817f4f18c1074e3301a59685029b6 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/SourceFileInfoExtractor.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.ImportDeclaration; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.PackageDeclaration; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.body.VariableDeclarator; +import com.github.javaparser.ast.expr.Expression; +import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.ast.stmt.Statement; +import com.github.javaparser.ast.stmt.SwitchEntry; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeDeclaration; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; + +import static com.github.javaparser.StaticJavaParser.parse; +import static com.github.javaparser.symbolsolver.javaparser.Navigator.demandParentNode; +import static java.util.Comparator.comparing; + +/** + * Resolves resolvable nodes from one or more source files, and reports the results. + * It is mainly intended as an example usage of JavaSymbolSolver. + * + * @author Federico Tomassetti + */ +public class SourceFileInfoExtractor { + + private final TypeSolver typeSolver; + + private int successes = 0; + private int failures = 0; + private int unsupported = 0; + private boolean printFileName = true; + private PrintStream out = System.out; + private PrintStream err = System.err; + private boolean verbose = false; + + public SourceFileInfoExtractor(TypeSolver typeSolver) { + this.typeSolver = typeSolver; + } + + public void setVerbose(boolean verbose) { + this.verbose = verbose; + } + + public void setPrintFileName(boolean printFileName) { + this.printFileName = printFileName; + } + + public void setOut(PrintStream out) { + this.out = out; + } + + public void setErr(PrintStream err) { + this.err = err; + } + + public int getSuccesses() { + return successes; + } + + public int getUnsupported() { + return unsupported; + } + + public int getFailures() { + return failures; + } + + private void solveTypeDecl(ClassOrInterfaceDeclaration node) { + ResolvedTypeDeclaration typeDeclaration = JavaParserFacade.get(typeSolver).getTypeDeclaration(node); + if (typeDeclaration.isClass()) { + out.println("\n[ Class " + typeDeclaration.getQualifiedName() + " ]"); + for (ResolvedReferenceType sc : typeDeclaration.asClass().getAllSuperClasses()) { + out.println(" superclass: " + sc.getQualifiedName()); + } + for (ResolvedReferenceType sc : typeDeclaration.asClass().getAllInterfaces()) { + out.println(" interface: " + sc.getQualifiedName()); + } + } + } + + private void solve(Node node) { + if (node instanceof ClassOrInterfaceDeclaration) { + solveTypeDecl((ClassOrInterfaceDeclaration) node); + } else if (node instanceof Expression) { + Node parentNode = demandParentNode(node); + if (parentNode instanceof ImportDeclaration || + parentNode instanceof Expression || + parentNode instanceof MethodDeclaration || + parentNode instanceof PackageDeclaration) { + // skip + return; + } + if (parentNode instanceof Statement || + parentNode instanceof VariableDeclarator || + parentNode instanceof SwitchEntry) { + try { + ResolvedType ref = JavaParserFacade.get(typeSolver).getType(node); + out.println(" Line " + lineNr(node) + ") " + node + " ==> " + ref.describe()); + successes++; + } catch (UnsupportedOperationException upe) { + unsupported++; + err.println(upe.getMessage()); + throw upe; + } catch (RuntimeException re) { + failures++; + err.println(re.getMessage()); + throw re; + } + } + } + } + + private void solveMethodCalls(Node node) { + if (node instanceof MethodCallExpr) { + out.println(" Line " + lineNr(node) + ") " + node + " ==> " + toString((MethodCallExpr) node)); + } + for (Node child : node.getChildNodes()) { + solveMethodCalls(child); + } + } + + private String toString(MethodCallExpr node) { + try { + return toString(JavaParserFacade.get(typeSolver).solve(node)); + } catch (Exception e) { + if (verbose) { + System.err.println("Error resolving call at L" + lineNr(node) + ": " + node); + e.printStackTrace(); + } + return "ERROR"; + } + } + + private String toString(SymbolReference methodDeclarationSymbolReference) { + if (methodDeclarationSymbolReference.isSolved()) { + return methodDeclarationSymbolReference.getCorrespondingDeclaration().getQualifiedSignature(); + } else { + return "UNSOLVED"; + } + } + + private List collectAllNodes(Node node) { + List nodes = new ArrayList<>(); + node.walk(nodes::add); + nodes.sort(comparing(n -> n.getBegin().get())); + return nodes; + } + + public void solve(Path path) throws IOException { + Files.walkFileTree(path, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (file.toString().endsWith(".java")) { + if (printFileName) { + out.println("- parsing " + file.toAbsolutePath()); + } + CompilationUnit cu = parse(file); + List nodes = collectAllNodes(cu); + nodes.forEach(n -> solve(n)); + } + return FileVisitResult.CONTINUE; + } + }); + } + + public void solveMethodCalls(Path path) throws IOException { + Files.walkFileTree(path, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (file.toString().endsWith(".java")) { + if (printFileName) { + out.println("- parsing " + file.toAbsolutePath()); + } + CompilationUnit cu = parse(file); + solveMethodCalls(cu); + } + return FileVisitResult.CONTINUE; + } + }); + } + + private int lineNr(Node node) { + return node.getRange().map(range -> range.begin.line).orElseThrow(IllegalStateException::new); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/cache/Cache.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/cache/Cache.java new file mode 100644 index 0000000000000000000000000000000000000000..d0846e239da2f5c95cf9cd7195526c49a686c889 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/cache/Cache.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2007-2010 Júlio Vilmar Gesser. + * Copyright (C) 2011, 2013-2021 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.cache; + +import java.util.Optional; + +/** + * A contract that defines a semi-persistent mapping of keys and values. + *
+ * Cache entries are manually added using put({@link K}, {@link V}), + * and are stored in the cache until either evicted or manually removed. + * After storing a value, it can be accessed using get({@link K}). + * + * @param The type of the key. + * @param The type of the value. + */ +public interface Cache { + + /** + * Associates value with key in this cache. + *
+ * If the cache previously contained a value associated with key, + * the old value is replaced by value. + * + * @param key The key to be used as index. + * @param value The value to be stored. + */ + void put(K key, V value); + + /** + * Returns the value associated with {@code key} in this cache, + * or empty if there is no cached value for {@code key}. + * + * @param key The key to look for. + * + * @return The value stored in cache if present. + */ + Optional get(K key); + + /** + * Discards any cached value for this key. + * + * @param key The key to be discarded. + */ + void remove(K key); + + /** + * Discards all entries in the cache. + */ + void removeAll(); + + /** + * Returns {@code True} if the cache contains a entry with the key, + * or {@code False} if there is none. + * + * @param key The key to be verified. + * + * @return {@code True} if the key is present. + */ + boolean contains(K key); + + /** + * Returns the number of entries in this cache. + * + * @return The cache size. + */ + long size(); + + /** + * Returns {@code True} if the cache is empty, or {@code False} + * if there's at least a entry stored in cache. + * + * @return {@code True} if is empty. + */ + boolean isEmpty(); + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/cache/GuavaCache.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/cache/GuavaCache.java new file mode 100644 index 0000000000000000000000000000000000000000..88943309a3b9f21898041ef79d866e909a7cab72 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/cache/GuavaCache.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2007-2010 Júlio Vilmar Gesser. + * Copyright (C) 2011, 2013-2021 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.cache; + +import java.util.Objects; +import java.util.Optional; + +/** + * This class is used to wrap a Guava {@link com.google.common.cache.Cache}. + * + * @param The type of the key. + * @param The type of the value. + */ +public class GuavaCache implements Cache { + + /** + * Wrap a Guava cache with a custom cache. + * + * @param guavaCache The guava cache to be wrapped- + * + * @param The expected type for the key. + * @param The expected type for the value. + * + * @return A newly created instance of {@link NoCache}. + */ + public static GuavaCache create(com.google.common.cache.Cache guavaCache) { + return new GuavaCache<>(guavaCache); + } + + private final com.google.common.cache.Cache guavaCache; + + public GuavaCache(com.google.common.cache.Cache guavaCache) { + this.guavaCache = Objects.requireNonNull(guavaCache, "The argument GuavaCache can't be null."); + } + + @Override + public void put(K key, V value) { + guavaCache.put(key, value); + } + + @Override + public Optional get(K key) { + return Optional.ofNullable( + guavaCache.getIfPresent(key) + ); + } + + @Override + public void remove(K key) { + guavaCache.invalidate(key); + } + + @Override + public void removeAll() { + guavaCache.invalidateAll(); + } + + @Override + public boolean contains(K key) { + return get(key).isPresent(); + } + + @Override + public long size() { + return guavaCache.size(); + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/cache/InMemoryCache.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/cache/InMemoryCache.java new file mode 100644 index 0000000000000000000000000000000000000000..d0e4391895b533b180a7b748780ed965aab12d00 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/cache/InMemoryCache.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2007-2010 Júlio Vilmar Gesser. + * Copyright (C) 2011, 2013-2021 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.cache; + +import java.util.Map; +import java.util.Optional; +import java.util.WeakHashMap; + +/** + * A cache implementation that stores the information in memory. + *
+ * The current implementation stores the values in memory in a {@link WeakHashMap}. + * + * @param The type of the key. + * @param The type of the value. + */ +public class InMemoryCache implements Cache { + + /** + * Create a new instance for a cache in memory. + * + * @param The expected type for the key. + * @param The expected type for the value. + * + * @return A newly created instance of {@link InMemoryCache}. + */ + public static InMemoryCache create() { + return new InMemoryCache<>(); + } + + private final Map mappedValues = new WeakHashMap<>(); + + @Override + public void put(K key, V value) { + mappedValues.put(key, value); + } + + @Override + public Optional get(K key) { + return Optional.ofNullable( + mappedValues.get(key) + ); + } + + @Override + public void remove(K key) { + mappedValues.remove(key); + } + + @Override + public void removeAll() { + mappedValues.clear(); + } + + @Override + public boolean contains(K key) { + return mappedValues.containsKey(key); + } + + @Override + public long size() { + return mappedValues.size(); + } + + @Override + public boolean isEmpty() { + return mappedValues.isEmpty(); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/cache/NoCache.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/cache/NoCache.java new file mode 100644 index 0000000000000000000000000000000000000000..3fb4931e0fd80352a68e0afab8cde0ef94c944d3 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/cache/NoCache.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2007-2010 Júlio Vilmar Gesser. + * Copyright (C) 2011, 2013-2021 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.cache; + +import java.util.Optional; + +/** + * A cache implementation that does not store any information. + * + * @param The key type. + * @param The value type. + */ +public class NoCache implements Cache { + + /** + * Create a new instance. + * + * @param The expected type for the key. + * @param The expected type for the value. + * + * @return A newly created instance of {@link NoCache}. + */ + public static NoCache create() { + return new NoCache<>(); + } + + @Override + public void put(K key, V value) { + // Nothing to do here. + } + + @Override + public Optional get(K key) { + return Optional.empty(); + } + + @Override + public void remove(K key) { + // Nothing to do here. + } + + @Override + public void removeAll() { + // Nothing to do here. + } + + @Override + public boolean contains(K key) { + return false; + } + + @Override + public long size() { + return 0; + } + + @Override + public boolean isEmpty() { + return true; + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/core/package-info.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/core/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..2755f8f6630aa70630ad4952f0ebbf7ff34f85a1 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/core/package-info.java @@ -0,0 +1 @@ +package com.github.javaparser.symbolsolver.core; \ No newline at end of file diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/core/resolution/Context.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/core/resolution/Context.java new file mode 100644 index 0000000000000000000000000000000000000000..821ec7d1cb8d4a591cb98cb01be54c26d84a225f --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/core/resolution/Context.java @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.core.resolution; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.Parameter; +import com.github.javaparser.ast.body.VariableDeclarator; +import com.github.javaparser.ast.expr.PatternExpr; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.contexts.AbstractJavaParserContext; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.Value; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * Context is very similar to scope. + * In the context we look for solving symbols. + * + * @author Federico Tomassetti + */ +public interface Context { + + /** + * @return The parent context, if there is one. For example, a method exists within a compilation unit. + */ + Optional getParent(); + + + /* Type resolution */ + + /** + * Default to no generics available in this context, delegating solving to the parent context. + * Contexts which have generics available to it will override this method. + * For example class and method declarations, and method calls. + * + * @param name For example, solving {@code T} within {@code class Foo {}} or + * @return The resolved generic type, if found. + */ + default Optional solveGenericType(String name) { + // Default to solving within the parent context. + return solveGenericTypeInParentContext(name); + } + + default Optional solveGenericTypeInParentContext(String name) { + Optional optionalParentContext = getParent(); + if (!optionalParentContext.isPresent()) { + return Optional.empty(); + } + + // Delegate solving to the parent context. + return optionalParentContext.get().solveGenericType(name); + } + + /** + * Default to being unable to solve any reference in this context, delegating solving to the parent context. + * Contexts which exist as the "parent" of a resolvable type will override this method. + * For example, a compilation unit can contain classes. A class declaration can also contain types (e.g. a subclass). + * + * @param name For example, solving {@code List} or {@code java.util.List}. + * @return The declaration associated with the given type name. + */ + default SymbolReference solveType(String name) { + // Default to solving within the parent context. + return solveTypeInParentContext(name); + } + + default SymbolReference solveTypeInParentContext(String name) { + Optional optionalParentContext = getParent(); + if (!optionalParentContext.isPresent()) { + return SymbolReference.unsolved(ResolvedReferenceTypeDeclaration.class); + } + + // Delegate solving to the parent context. + return optionalParentContext.get().solveType(name); + } + + /* Symbol resolution */ + + /** + * Used where a symbol is being used (e.g. solving {@code x} when used as an argument {@code doubleThis(x)}, or calculation {@code return x * 2;}). + * @param name the variable / reference / identifier used. + * @return // FIXME: Better documentation on how this is different to solveSymbolAsValue() + */ + default SymbolReference solveSymbol(String name) { + // Default to solving within the parent context. + return solveSymbolInParentContext(name); + } + + default SymbolReference solveSymbolInParentContext(String name) { + Optional optionalParentContext = getParent(); + if (!optionalParentContext.isPresent()) { + return SymbolReference.unsolved(ResolvedValueDeclaration.class); + } + + // Delegate solving to the parent context. + return optionalParentContext.get().solveSymbol(name); + } + + /** + * Used where a symbol is being used (e.g. solving {@code x} when used as an argument {@code doubleThis(x)}, or calculation {@code return x * 2;}). + * @param name the variable / reference / identifier used. + * @return // FIXME: Better documentation on how this is different to solveSymbol() + */ + default Optional solveSymbolAsValue(String name) { + SymbolReference ref = solveSymbol(name); + if (!ref.isSolved()) { + return Optional.empty(); + } + + return Optional.of(Value.from(ref.getCorrespondingDeclaration())); + } + + default Optional solveSymbolAsValueInParentContext(String name) { + SymbolReference ref = solveSymbolInParentContext(name); + if (!ref.isSolved()) { + return Optional.empty(); + } + + return Optional.of(Value.from(ref.getCorrespondingDeclaration())); + } + + + /** + * The fields that are declared and in this immediate context made visible to a given child. + * This list could include values which are shadowed. + */ + default List fieldsExposedToChild(Node child) { + return Collections.emptyList(); + } + + /** + * The local variables that are declared in this immediate context and made visible to a given child. + * This list could include values which are shadowed. + */ + default List localVariablesExposedToChild(Node child) { + return Collections.emptyList(); + } + + /** + * The parameters that are declared in this immediate context and made visible to a given child. + * This list could include values which are shadowed. + */ + default List parametersExposedToChild(Node child) { + return Collections.emptyList(); + } + + /** + * The pattern expressions that are declared in this immediate context and made visible to a given child. + * This list could include values which are shadowed. + */ + default List patternExprsExposedToChild(Node child) { + return Collections.emptyList(); + } + + /** + */ + default List patternExprsExposedFromChildren() { + return Collections.emptyList(); + } + + /** + */ + default List negatedPatternExprsExposedFromChildren() { + return Collections.emptyList(); + } + + /** + * Aim to resolve the given name by looking for a variable matching it. + *

+ * To do it consider local variables that are visible in a certain scope as defined in JLS 6.3. Scope of a + * Declaration. + *

+ * 1. The scope of a local variable declaration in a block (§14.4) is the rest of the block in which the + * declaration + * appears, starting with its own initializer and including any further declarators to the right in the local + * variable declaration statement. + *

+ * 2. The scope of a local variable declared in the ForInit part of a basic for statement (§14.14.1) includes all + * of the following: + * 2.1 Its own initializer + * 2.2 Any further declarators to the right in the ForInit part of the for statement + * 2.3 The Expression and ForUpdate parts of the for statement + * 2.4 The contained Statement + *

+ * 3. The scope of a local variable declared in the FormalParameter part of an enhanced for statement (§14.14.2) is + * the contained Statement. + * 4. The scope of a parameter of an exception handler that is declared in a catch clause of a try statement + * (§14.20) is the entire block associated with the catch. + *

+ * 5. The scope of a variable declared in the ResourceSpecification of a try-with-resources statement (§14.20.3) is + * from the declaration rightward over the remainder of the ResourceSpecification and the entire try block + * associated with the try-with-resources statement. + */ + default Optional localVariableDeclarationInScope(String name) { + if (!getParent().isPresent()) { + return Optional.empty(); + } + + // First check if the variable is directly declared within this context. + Node wrappedNode = ((AbstractJavaParserContext) this).getWrappedNode(); + Context parentContext = getParent().get(); + Optional localResolutionResults = parentContext + .localVariablesExposedToChild(wrappedNode) + .stream() + .filter(vd -> vd.getNameAsString().equals(name)) + .findFirst(); + + if (localResolutionResults.isPresent()) { + return localResolutionResults; + } + + + // If we don't find the variable locally, escalate up the scope hierarchy to see if it is declared there. + return parentContext.localVariableDeclarationInScope(name); + } + + default Optional parameterDeclarationInScope(String name) { + if (!getParent().isPresent()) { + return Optional.empty(); + } + + // First check if the parameter is directly declared within this context. + Node wrappedNode = ((AbstractJavaParserContext) this).getWrappedNode(); + Context parentContext = getParent().get(); + Optional localResolutionResults = parentContext + .parametersExposedToChild(wrappedNode) + .stream() + .filter(vd -> vd.getNameAsString().equals(name)) + .findFirst(); + + if (localResolutionResults.isPresent()) { + return localResolutionResults; + } + + // If we don't find the parameter locally, escalate up the scope hierarchy to see if it is declared there. + return parentContext.parameterDeclarationInScope(name); + } + + + /** + * With respect to solving, the AST "parent" of a block statement is not necessarily the same as the scope parent. + *
Example: + *
+ *

{@code
+     *  public String x() {
+     *      if(x) {
+     *          // Parent node: the block attached to the method declaration
+     *          // Scope-parent: the block attached to the method declaration
+     *      } else if {
+     *          // Parent node: the if
+     *          // Scope-parent: the block attached to the method declaration
+     *      } else {
+     *          // Parent node: the elseif
+     *          // Scope-parent: the block attached to the method declaration
+     *      }
+     *  }
+     * }
+ */ + default Optional patternExprInScope(String name) { + if (!getParent().isPresent()) { + return Optional.empty(); + } + Context parentContext = getParent().get(); + + // FIXME: "scroll backwards" from the wrapped node + // FIXME: If there are multiple patterns, throw an error? + + // First check if the pattern is directly declared within this context. + Node wrappedNode = ((AbstractJavaParserContext) this).getWrappedNode(); + Optional localResolutionResults = parentContext + .patternExprsExposedToChild(wrappedNode) + .stream() + .filter(vd -> vd.getNameAsString().equals(name)) + .findFirst(); + + if (localResolutionResults.isPresent()) { + return localResolutionResults; + } + + // If we don't find the parameter locally, escalate up the scope hierarchy to see if it is declared there. + return parentContext.patternExprInScope(name); + } + + default Optional fieldDeclarationInScope(String name) { + if (!getParent().isPresent()) { + return Optional.empty(); + } + Context parentContext = getParent().get(); + // First check if the parameter is directly declared within this context. + Node wrappedNode = ((AbstractJavaParserContext) this).getWrappedNode(); + Optional localResolutionResults = parentContext + .fieldsExposedToChild(wrappedNode) + .stream() + .filter(vd -> vd.getName().equals(name)) + .findFirst(); + + if (localResolutionResults.isPresent()) { + return localResolutionResults; + } + + // If we don't find the field locally, escalate up the scope hierarchy to see if it is declared there. + return parentContext.fieldDeclarationInScope(name); + } + + + /* Constructor resolution */ + + /** + * We find the method declaration which is the best match for the given name and list of typeParametersValues. + */ + default SymbolReference solveConstructor(List argumentsTypes) { + throw new IllegalArgumentException("Constructor resolution is available only on Class Context"); + } + + /* Methods resolution */ + + /** + * We find the method declaration which is the best match for the given name and list of typeParametersValues. + */ + default SymbolReference solveMethod(String name, List argumentsTypes, boolean staticOnly) { + // Default to solving within the parent context. + return solveMethodInParentContext(name, argumentsTypes, staticOnly); + } + + default SymbolReference solveMethodInParentContext(String name, List argumentsTypes, boolean staticOnly) { + Optional optionalParentContext = getParent(); + if (!optionalParentContext.isPresent()) { + return SymbolReference.unsolved(ResolvedMethodDeclaration.class); + } + + // Delegate solving to the parent context. + return optionalParentContext.get().solveMethod(name, argumentsTypes, staticOnly); + } + + /** + * Similar to solveMethod but we return a MethodUsage. + * A MethodUsage corresponds to a MethodDeclaration plus the resolved type variables. + */ + default Optional solveMethodAsUsage(String name, List argumentsTypes) { + SymbolReference methodSolved = solveMethod(name, argumentsTypes, false); + if (methodSolved.isSolved()) { + ResolvedMethodDeclaration methodDeclaration = methodSolved.getCorrespondingDeclaration(); + if (!(methodDeclaration instanceof TypeVariableResolutionCapability)) { + throw new UnsupportedOperationException(String.format( + "Resolved method declarations must implement %s.", + TypeVariableResolutionCapability.class.getName() + )); + } + + MethodUsage methodUsage = ((TypeVariableResolutionCapability) methodDeclaration).resolveTypeVariables(this, argumentsTypes); + return Optional.of(methodUsage); + } else { + return Optional.empty(); + } + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/core/resolution/MethodUsageResolutionCapability.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/core/resolution/MethodUsageResolutionCapability.java new file mode 100644 index 0000000000000000000000000000000000000000..c3ce36dbd0c4b8c1b95f3d44385b1ab4cdab24b3 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/core/resolution/MethodUsageResolutionCapability.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.core.resolution; + +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.types.ResolvedType; + +import java.util.List; +import java.util.Optional; + +public interface MethodUsageResolutionCapability { + Optional solveMethodAsUsage(String name, List argumentTypes, Context invocationContext, + List typeParameters); +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/core/resolution/TypeVariableResolutionCapability.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/core/resolution/TypeVariableResolutionCapability.java new file mode 100644 index 0000000000000000000000000000000000000000..a7dab945067749529cbae4c8850bbf02b54de840 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/core/resolution/TypeVariableResolutionCapability.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.core.resolution; + +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.types.ResolvedType; + +import java.util.List; + +public interface TypeVariableResolutionCapability { + MethodUsage resolveTypeVariables(Context context, List parameterTypes); +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/declarations/common/MethodDeclarationCommonLogic.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/declarations/common/MethodDeclarationCommonLogic.java new file mode 100644 index 0000000000000000000000000000000000000000..e0d0d3bf745de79851659a90f7af004411d22372 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/declarations/common/MethodDeclarationCommonLogic.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.declarations.common; + +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedParameterDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.resolution.types.ResolvedTypeVariable; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.logic.InferenceContext; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.reflectionmodel.MyObjectProvider; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * @author Federico Tomassetti + */ +public class MethodDeclarationCommonLogic { + + private ResolvedMethodDeclaration methodDeclaration; + private TypeSolver typeSolver; + + public MethodDeclarationCommonLogic(ResolvedMethodDeclaration methodDeclaration, TypeSolver typeSolver) { + this.methodDeclaration = methodDeclaration; + this.typeSolver = typeSolver; + } + + public MethodUsage resolveTypeVariables(Context context, List parameterTypes) { + ResolvedType returnType = replaceTypeParams(methodDeclaration.getReturnType(), context); + List params = new ArrayList<>(); + for (int i = 0; i < methodDeclaration.getNumberOfParams(); i++) { + ResolvedType replaced = replaceTypeParams(methodDeclaration.getParam(i).getType(), context); + params.add(replaced); + } + + // We now look at the type parameter for the method which we can derive from the parameter types + // and then we replace them in the return type + // Map determinedTypeParameters = new HashMap<>(); + InferenceContext inferenceContext = new InferenceContext(MyObjectProvider.INSTANCE); + for (int i = 0; i < methodDeclaration.getNumberOfParams(); i++) { + ResolvedParameterDeclaration formalParamDecl = methodDeclaration.getParam(i); + ResolvedType formalParamType = formalParamDecl.getType(); + + // Don't continue if a vararg parameter is reached and there are no arguments left + if (formalParamDecl.isVariadic() && parameterTypes.size() < methodDeclaration.getNumberOfParams()) { + break; + } + + ResolvedType actualParamType = parameterTypes.get(i); + + if (formalParamDecl.isVariadic() && !actualParamType.isArray()) { + formalParamType = formalParamType.asArrayType().getComponentType(); + } + + inferenceContext.addPair(formalParamType, actualParamType); + } + + returnType = inferenceContext.resolve(inferenceContext.addSingle(returnType)); + + return new MethodUsage(methodDeclaration, params, returnType); + } + + private ResolvedType replaceTypeParams(ResolvedType type, Context context) { + if (type.isTypeVariable()) { + ResolvedTypeParameterDeclaration typeParameter = type.asTypeParameter(); + if (typeParameter.declaredOnType()) { + Optional typeParam = typeParamByName(typeParameter.getName(), context); + if (typeParam.isPresent()) { + type = typeParam.get(); + } + } + } + + if (type.isReferenceType()) { + type.asReferenceType().transformTypeParameters(tp -> replaceTypeParams(tp, context)); + } + + return type; + } + + protected Optional typeParamByName(String name, Context context) { + return methodDeclaration.getTypeParameters().stream().filter(tp -> tp.getName().equals(name)).map(tp -> toType(tp)).findFirst(); + } + + protected ResolvedType toType(ResolvedTypeParameterDeclaration typeParameterDeclaration) { + return new ResolvedTypeVariable(typeParameterDeclaration); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparser/Navigator.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparser/Navigator.java new file mode 100644 index 0000000000000000000000000000000000000000..b0a46db2b2917731f2301357ab9c2eb120e88b61 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparser/Navigator.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparser; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.*; +import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.ast.expr.NameExpr; +import com.github.javaparser.ast.expr.SimpleName; +import com.github.javaparser.ast.stmt.ReturnStmt; +import com.github.javaparser.ast.stmt.SwitchStmt; + +import java.util.Optional; + +/** + * This class can be used to easily retrieve nodes from a JavaParser AST. + * + * Note that methods with the prefix `demand` indicate that if the search value is not found, an exception will be thrown. + * + * @author Federico Tomassetti + */ +public final class Navigator { + + private Navigator() { + // prevent instantiation + } + + public static ClassOrInterfaceDeclaration demandClass(CompilationUnit cu, String qualifiedName) { + ClassOrInterfaceDeclaration cd = demandClassOrInterface(cu, qualifiedName); + if (cd.isInterface()) { + throw new IllegalStateException("Type is not a class"); + } + return cd; + } + + public static ClassOrInterfaceDeclaration demandClassOrInterface(CompilationUnit compilationUnit, String qualifiedName) { + return findType(compilationUnit, qualifiedName) + .map(res -> res.toClassOrInterfaceDeclaration().orElseThrow(() -> new IllegalStateException("Type is not a class or an interface, it is " + res.getClass().getCanonicalName()))) + .orElseThrow(() -> new IllegalStateException("No type named '" + qualifiedName + "'found")); + } + + /** + * Returns the {@code (i+1)}'th constructor of the given type declaration, in textual order. The constructor that + * appears first has the index 0, the second one the index 1, and so on. + * + * @param td The type declaration to search in. Note that only classes and enums have constructors. + * @param index The index of the desired constructor. + * @return The desired ConstructorDeclaration if it was found, else an exception is thrown. + */ + public static ConstructorDeclaration demandConstructor(TypeDeclaration td, int index) { + // TODO: Refactor to use `td.findAll(ConstructorDeclaration.class);` - potential difference re: searching only immediate children? + ConstructorDeclaration found = null; + int i = 0; + for (BodyDeclaration bd : td.getMembers()) { + if (bd instanceof ConstructorDeclaration) { + ConstructorDeclaration cd = (ConstructorDeclaration) bd; + if (i == index) { + found = cd; + break; + } + i++; + } + } + if (found == null) { + throw new IllegalStateException("No constructor with index " + index); + } + return found; + } + + public static EnumDeclaration demandEnum(CompilationUnit cu, String qualifiedName) { + Optional> res = findType(cu, qualifiedName); + if (!res.isPresent()) { + throw new IllegalStateException("No type found"); + } + if (!(res.get() instanceof EnumDeclaration)) { + throw new IllegalStateException("Type is not an enum"); + } + return (EnumDeclaration) res.get(); + } + + public static VariableDeclarator demandField(ClassOrInterfaceDeclaration cd, String name) { + for (BodyDeclaration bd : cd.getMembers()) { + if (bd instanceof FieldDeclaration) { + FieldDeclaration fd = (FieldDeclaration) bd; + for (VariableDeclarator vd : fd.getVariables()) { + if (vd.getName().getId().equals(name)) { + return vd; + } + } + } + } + throw new IllegalStateException("No field with given name"); + } + + public static ClassOrInterfaceDeclaration demandInterface(CompilationUnit cu, String qualifiedName) { + ClassOrInterfaceDeclaration cd = demandClassOrInterface(cu, qualifiedName); + if (!cd.isInterface()) { + throw new IllegalStateException("Type is not an interface"); + } + return cd; + } + + public static MethodDeclaration demandMethod(TypeDeclaration cd, String name) { + MethodDeclaration found = null; + for (BodyDeclaration bd : cd.getMembers()) { + if (bd instanceof MethodDeclaration) { + MethodDeclaration md = (MethodDeclaration) bd; + if (md.getNameAsString().equals(name)) { + if (found != null) { + throw new IllegalStateException("Ambiguous getName"); + } + found = md; + } + } + } + if (found == null) { + throw new IllegalStateException("No method called " + name); + } + return found; + } + + public static N demandNodeOfGivenClass(Node node, Class clazz) { + return node.findFirst(clazz).orElseThrow(IllegalArgumentException::new); + } + + public static Node demandParentNode(Node node) { + return node.getParentNode().orElseThrow(() -> new IllegalStateException("Parent not found, the node does not appear to be inserted in a correct AST")); + } + + public static ReturnStmt demandReturnStmt(MethodDeclaration method) { + return demandNodeOfGivenClass(method, ReturnStmt.class); + } + + public static SwitchStmt demandSwitch(Node node) { + return findSwitchHelper(node).orElseThrow(IllegalArgumentException::new); + } + + public static Optional demandVariableDeclaration(Node node, String name) { + return node.findFirst(VariableDeclarator.class, n -> n.getNameAsString().equals(name)); + } + + public static Optional findMethodCall(Node node, String methodName) { + return node.findFirst(MethodCallExpr.class, n -> n.getNameAsString().equals(methodName)); + } + + public static Optional findNameExpression(Node node, String name) { + return node.findFirst(NameExpr.class, n -> n.getNameAsString().equals(name)); + } + + /** + * @deprecated Use {@link #demandNodeOfGivenClass(Node, Class)} + */ + @Deprecated + public static N findNodeOfGivenClass(Node node, Class clazz) { + return demandNodeOfGivenClass(node, clazz); + } + + /** + * @deprecated Use {@link #demandReturnStmt(MethodDeclaration)} + */ + @Deprecated + public static ReturnStmt findReturnStmt(MethodDeclaration method) { + return demandReturnStmt(method); + } + + public static Optional findSimpleName(Node node, String name) { + return node.findFirst(SimpleName.class, n -> n.asString().equals(name)); + } + + /** + * @deprecated Use {@link #demandSwitch(Node)} + */ + @Deprecated + public static SwitchStmt findSwitch(Node node) { + return demandSwitch(node); + } + + private static Optional findSwitchHelper(Node node) { + if (node instanceof SwitchStmt) { + return Optional.of((SwitchStmt) node); + } + + return node.findFirst(SwitchStmt.class); + } + + /** + * Looks among the type declared in the Compilation Unit for one having the specified name. + * The name can be qualified with respect to the compilation unit. For example, if the compilation + * unit is in package a.b; and it contains two top level classes named C and D, with class E being defined inside D + * then the qualifiedName that can be resolved are "C", "D", and "D.E". + */ + public static Optional> findType(CompilationUnit cu, String qualifiedName) { + if (cu.getTypes().isEmpty()) { + return Optional.empty(); + } + + final String typeName = getOuterTypeName(qualifiedName); + Optional> type = cu.getTypes().stream().filter((t) -> t.getName().getId().equals(typeName)).findFirst(); + + final String innerTypeName = getInnerTypeName(qualifiedName); + if (type.isPresent() && !innerTypeName.isEmpty()) { + return findType(type.get(), innerTypeName); + } + return type; + } + + /** + * Looks among the type declared in the TypeDeclaration for one having the specified name. + * The name can be qualified with respect to the TypeDeclaration. For example, if the class declaration defines + * class D and class D contains an internal class named E then the qualifiedName that can be resolved are "D", and + * "D.E". + */ + public static Optional> findType(TypeDeclaration td, String qualifiedName) { + final String typeName = getOuterTypeName(qualifiedName); + + Optional> type = Optional.empty(); + for (Node n : td.getMembers()) { + if (n instanceof TypeDeclaration && ((TypeDeclaration) n).getName().getId().equals(typeName)) { + type = Optional.of((TypeDeclaration) n); + break; + } + } + final String innerTypeName = getInnerTypeName(qualifiedName); + if (type.isPresent() && !innerTypeName.isEmpty()) { + return findType(type.get(), innerTypeName); + } + return type; + } + + private static String getInnerTypeName(String qualifiedName) { + if (qualifiedName.contains(".")) { + return qualifiedName.split("\\.", 2)[1]; + } + return ""; + } + + private static String getOuterTypeName(String qualifiedName) { + return qualifiedName.split("\\.", 2)[0]; + } + + /** + * @deprecated Use {@link #demandParentNode(Node)} + */ + @Deprecated + public static Node requireParentNode(Node node) { + return demandParentNode(node); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparser/package-info.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparser/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..cbeea03613f0283699fcd00999a5bcf6c404954f --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparser/package-info.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +/** + * This package contains utility to use JavaParser. + */ +package com.github.javaparser.symbolsolver.javaparser; diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/DefaultVisitorAdapter.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/DefaultVisitorAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..6975aab383777fb46efc98a8adae19994a298bbf --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/DefaultVisitorAdapter.java @@ -0,0 +1,536 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel; + +import com.github.javaparser.ast.*; +import com.github.javaparser.ast.body.*; +import com.github.javaparser.ast.comments.BlockComment; +import com.github.javaparser.ast.comments.JavadocComment; +import com.github.javaparser.ast.comments.LineComment; +import com.github.javaparser.ast.expr.*; +import com.github.javaparser.ast.modules.*; +import com.github.javaparser.ast.stmt.*; +import com.github.javaparser.ast.type.*; +import com.github.javaparser.ast.visitor.GenericVisitor; +import com.github.javaparser.resolution.types.ResolvedType; + +public class DefaultVisitorAdapter implements GenericVisitor { + @Override + public ResolvedType visit(CompilationUnit node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(PackageDeclaration node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(TypeParameter node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(LineComment node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(BlockComment node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ClassOrInterfaceDeclaration node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(EnumDeclaration node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(EnumConstantDeclaration node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(AnnotationDeclaration node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(AnnotationMemberDeclaration node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(FieldDeclaration node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(VariableDeclarator node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ConstructorDeclaration node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(MethodDeclaration node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(Parameter node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(InitializerDeclaration node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(JavadocComment node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ClassOrInterfaceType node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(PrimitiveType node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ArrayType node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ArrayCreationLevel node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(IntersectionType node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(UnionType node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(VoidType node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(WildcardType node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(UnknownType node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ArrayAccessExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ArrayCreationExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ArrayInitializerExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(AssignExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(BinaryExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(CastExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ClassExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ConditionalExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(EnclosedExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(FieldAccessExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(InstanceOfExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(PatternExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(StringLiteralExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(IntegerLiteralExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(LongLiteralExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(CharLiteralExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(DoubleLiteralExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(BooleanLiteralExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(NullLiteralExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(MethodCallExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(NameExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ObjectCreationExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ThisExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(SuperExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(UnaryExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(VariableDeclarationExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(MarkerAnnotationExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(SingleMemberAnnotationExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(NormalAnnotationExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(MemberValuePair node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ExplicitConstructorInvocationStmt node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(LocalClassDeclarationStmt node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(LocalRecordDeclarationStmt node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(AssertStmt node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(BlockStmt node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(LabeledStmt node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(EmptyStmt node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ExpressionStmt node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(SwitchStmt node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(SwitchEntry node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(BreakStmt node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ReturnStmt node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(IfStmt node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(WhileStmt node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ContinueStmt node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(DoStmt node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ForEachStmt node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ForStmt node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ThrowStmt node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(SynchronizedStmt node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(TryStmt node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(CatchClause node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(LambdaExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(MethodReferenceExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(TypeExpr node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(NodeList node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(Name node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(SimpleName node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ImportDeclaration node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ModuleDeclaration node, Boolean arg) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ModuleRequiresDirective node, Boolean arg) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ModuleExportsDirective node, Boolean arg) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ModuleProvidesDirective node, Boolean arg) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ModuleUsesDirective node, Boolean arg) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ModuleOpensDirective node, Boolean arg) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(UnparsableStmt node, Boolean arg) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(ReceiverParameter node, Boolean arg) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(VarType node, Boolean arg) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(Modifier node, Boolean arg) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(SwitchExpr node, Boolean arg) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(YieldStmt node, Boolean arg) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(TextBlockLiteralExpr node, Boolean arg) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(RecordDeclaration node, Boolean arg) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(CompactConstructorDeclaration node, Boolean aBoolean) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/JavaParserFacade.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/JavaParserFacade.java new file mode 100644 index 0000000000000000000000000000000000000000..b07283fc38d600b19181be9f8323081f5796a07d --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/JavaParserFacade.java @@ -0,0 +1,779 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.DataKey; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.body.*; +import com.github.javaparser.ast.expr.*; +import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt; +import com.github.javaparser.ast.type.*; +import com.github.javaparser.resolution.MethodAmbiguityException; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.*; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.javaparsermodel.contexts.FieldAccessContext; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserAnonymousClassDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserEnumDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserTypeVariableDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionAnnotationDeclaration; +import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionClassDeclaration; +import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionEnumDeclaration; +import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionInterfaceDeclaration; +import com.github.javaparser.symbolsolver.resolution.ConstructorResolutionLogic; +import com.github.javaparser.symbolsolver.resolution.MethodResolutionLogic; +import com.github.javaparser.symbolsolver.resolution.SymbolSolver; +import com.github.javaparser.utils.Log; + +import java.util.*; +import java.util.stream.Collectors; + +import static com.github.javaparser.symbolsolver.javaparser.Navigator.demandParentNode; +import static com.github.javaparser.symbolsolver.model.resolution.SymbolReference.solved; +import static com.github.javaparser.symbolsolver.model.resolution.SymbolReference.unsolved; + +/** + * Class to be used by final users to solve symbols for JavaParser ASTs. + * + * @author Federico Tomassetti + */ +public class JavaParserFacade { + + private static final DataKey TYPE_WITH_LAMBDAS_RESOLVED = new DataKey() { + }; + private static final DataKey TYPE_WITHOUT_LAMBDAS_RESOLVED = new DataKey() { + }; + + private static final Map instances = new WeakHashMap<>(); + + private static String JAVA_LANG_STRING = String.class.getCanonicalName(); + + private final TypeSolver typeSolver; + private final TypeExtractor typeExtractor; + private final SymbolSolver symbolSolver; + + private JavaParserFacade(TypeSolver typeSolver) { + this.typeSolver = typeSolver.getRoot(); + this.symbolSolver = new SymbolSolver(typeSolver); + this.typeExtractor = new TypeExtractor(typeSolver, this); + } + + public TypeSolver getTypeSolver() { + return typeSolver; + } + + public SymbolSolver getSymbolSolver() { + return symbolSolver; + } + + /** + * Note that the addition of the modifier {@code synchronized} is specific and directly in response to issue #2668. + *
This MUST NOT be misinterpreted as a signal that JavaParser is safe to use within a multi-threaded environment. + *
+ *
Additional discussion and context from a user attempting multithreading can be found within issue #2671 . + *
+ * + * @see https://github.com/javaparser/javaparser/issues/2668 + * @see https://github.com/javaparser/javaparser/issues/2671 + */ + public synchronized static JavaParserFacade get(TypeSolver typeSolver) { + return instances.computeIfAbsent(typeSolver, JavaParserFacade::new); + } + + /** + * This method is used to clear internal caches for the sake of releasing memory. + */ + public static void clearInstances() { + instances.clear(); + } + + protected static ResolvedType solveGenericTypes(ResolvedType type, Context context) { + if (type.isTypeVariable()) { + return context.solveGenericType(type.describe()).orElse(type); + } + if (type.isWildcard()) { + if (type.asWildcard().isExtends() || type.asWildcard().isSuper()) { + ResolvedWildcard wildcardUsage = type.asWildcard(); + ResolvedType boundResolved = solveGenericTypes(wildcardUsage.getBoundedType(), context); + if (wildcardUsage.isExtends()) { + return ResolvedWildcard.extendsBound(boundResolved); + } else { + return ResolvedWildcard.superBound(boundResolved); + } + } + } + return type; + } + + public SymbolReference solve(NameExpr nameExpr) { + return symbolSolver.solveSymbol(nameExpr.getName().getId(), nameExpr); + } + + public SymbolReference solve(SimpleName nameExpr) { + return symbolSolver.solveSymbol(nameExpr.getId(), nameExpr); + } + + public SymbolReference solve(Expression expr) { + return expr.toNameExpr().map(this::solve).orElseThrow(() -> new IllegalArgumentException(expr.getClass().getCanonicalName())); + } + + public SymbolReference solve(MethodCallExpr methodCallExpr) { + return solve(methodCallExpr, true); + } + + public SymbolReference solve(MethodReferenceExpr methodReferenceExpr) { + return solve(methodReferenceExpr, true); + } + + public SymbolReference solve(ObjectCreationExpr objectCreationExpr) { + return solve(objectCreationExpr, true); + } + + public SymbolReference solve(ExplicitConstructorInvocationStmt explicitConstructorInvocationStmt) { + return solve(explicitConstructorInvocationStmt, true); + } + + public SymbolReference solve(ExplicitConstructorInvocationStmt explicitConstructorInvocationStmt, boolean solveLambdas) { + // Constructor invocation must exist within a class (not interface). + Optional optAncestorClassOrInterfaceNode = explicitConstructorInvocationStmt.findAncestor(ClassOrInterfaceDeclaration.class); + if (!optAncestorClassOrInterfaceNode.isPresent()) { + return unsolved(ResolvedConstructorDeclaration.class); + } + + ClassOrInterfaceDeclaration classOrInterfaceNode = optAncestorClassOrInterfaceNode.get(); + ResolvedReferenceTypeDeclaration resolvedClassNode = classOrInterfaceNode.resolve(); + if (!resolvedClassNode.isClass()) { + throw new IllegalStateException("Expected to be a class -- cannot call this() or super() within an interface."); + } + + ResolvedTypeDeclaration typeDecl = null; + if (explicitConstructorInvocationStmt.isThis()) { + // this() + typeDecl = resolvedClassNode.asReferenceType(); + } else { + // super() + Optional superClass = resolvedClassNode.asClass().getSuperClass(); + if (superClass.isPresent() && superClass.get().getTypeDeclaration().isPresent()) { + typeDecl = superClass.get().getTypeDeclaration().get(); + } + } + if (typeDecl == null) { + return unsolved(ResolvedConstructorDeclaration.class); + } + + // Solve each of the arguments being passed into this constructor invocation. + List argumentTypes = new LinkedList<>(); + List placeholders = new LinkedList<>(); + solveArguments(explicitConstructorInvocationStmt, explicitConstructorInvocationStmt.getArguments(), solveLambdas, argumentTypes, placeholders); + + // Determine which constructor is referred to, and return it. + SymbolReference res = ConstructorResolutionLogic.findMostApplicable(((ResolvedClassDeclaration) typeDecl).getConstructors(), argumentTypes, typeSolver); + for (LambdaArgumentTypePlaceholder placeholder : placeholders) { + placeholder.setMethod(res); + } + + return res; + } + + public SymbolReference solve(ThisExpr node) { + // If 'this' is prefixed by a class eg. MyClass.this + if (node.getTypeName().isPresent()) { + // Get the class name + String className = node.getTypeName().get().asString(); + // Attempt to resolve using a typeSolver + SymbolReference clazz = typeSolver.tryToSolveType(className); + if (clazz.isSolved()) { + return solved(clazz.getCorrespondingDeclaration()); + } + // Attempt to resolve locally in Compilation unit + Optional cu = node.findAncestor(CompilationUnit.class); + if (cu.isPresent()) { + Optional classByName = cu.get().getClassByName(className); + if (classByName.isPresent()) { + return solved(getTypeDeclaration(classByName.get())); + } + } + } + return solved(getTypeDeclaration(findContainingTypeDeclOrObjectCreationExpr(node))); + } + + /** + * Given a constructor call find out to which constructor declaration it corresponds. + */ + public SymbolReference solve(ObjectCreationExpr objectCreationExpr, boolean solveLambdas) { + List argumentTypes = new LinkedList<>(); + List placeholders = new LinkedList<>(); + + solveArguments(objectCreationExpr, objectCreationExpr.getArguments(), solveLambdas, argumentTypes, placeholders); + + ResolvedReferenceTypeDeclaration typeDecl = null; + if (objectCreationExpr.getAnonymousClassBody().isPresent()) { + typeDecl = new JavaParserAnonymousClassDeclaration(objectCreationExpr, typeSolver); + } else { + ResolvedType classDecl = JavaParserFacade.get(typeSolver).convert(objectCreationExpr.getType(), objectCreationExpr); + if (classDecl.isReferenceType() && classDecl.asReferenceType().getTypeDeclaration().isPresent()) { + typeDecl = classDecl.asReferenceType().getTypeDeclaration().get(); + } + } + if (typeDecl == null) { + return unsolved(ResolvedConstructorDeclaration.class); + } + SymbolReference res = ConstructorResolutionLogic.findMostApplicable(typeDecl.getConstructors(), argumentTypes, typeSolver); + for (LambdaArgumentTypePlaceholder placeholder : placeholders) { + placeholder.setMethod(res); + } + return res; + } + + private void solveArguments(Node node, NodeList args, boolean solveLambdas, List argumentTypes, + List placeholders) { + int i = 0; + for (Expression parameterValue : args) { + if (parameterValue instanceof LambdaExpr || parameterValue instanceof MethodReferenceExpr) { + LambdaArgumentTypePlaceholder placeholder = new LambdaArgumentTypePlaceholder(i); + argumentTypes.add(placeholder); + placeholders.add(placeholder); + } else { + try { + argumentTypes.add(JavaParserFacade.get(typeSolver).getType(parameterValue, solveLambdas)); + } catch (UnsolvedSymbolException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(String.format("Unable to calculate the type of a parameter of a method call. Method call: %s, Parameter: %s", + node, parameterValue), e); + } + } + i++; + } + } + + /** + * Given a method call find out to which method declaration it corresponds. + */ + public SymbolReference solve(MethodCallExpr methodCallExpr, boolean solveLambdas) { + List argumentTypes = new LinkedList<>(); + List placeholders = new LinkedList<>(); + + solveArguments(methodCallExpr, methodCallExpr.getArguments(), solveLambdas, argumentTypes, placeholders); + + SymbolReference res = JavaParserFactory.getContext(methodCallExpr, typeSolver).solveMethod(methodCallExpr.getName().getId(), argumentTypes, false); + for (LambdaArgumentTypePlaceholder placeholder : placeholders) { + placeholder.setMethod(res); + } + return res; + } + + /** + * Given a method reference find out to which method declaration it corresponds. + */ + public SymbolReference solve(MethodReferenceExpr methodReferenceExpr, boolean solveLambdas) { + // pass empty argument list to be populated + List argumentTypes = new LinkedList<>(); + return JavaParserFactory.getContext(methodReferenceExpr, typeSolver).solveMethod(methodReferenceExpr.getIdentifier(), argumentTypes, false); + } + + public SymbolReference solve(AnnotationExpr annotationExpr) { + Context context = JavaParserFactory.getContext(annotationExpr, typeSolver); + SymbolReference typeDeclarationSymbolReference = context.solveType(annotationExpr.getNameAsString()); + if (typeDeclarationSymbolReference.isSolved()) { + ResolvedAnnotationDeclaration annotationDeclaration = (ResolvedAnnotationDeclaration) typeDeclarationSymbolReference.getCorrespondingDeclaration(); + return solved(annotationDeclaration); + } else { + return unsolved(ResolvedAnnotationDeclaration.class); + } + } + + public SymbolReference solve(FieldAccessExpr fieldAccessExpr) { + return ((FieldAccessContext) JavaParserFactory.getContext(fieldAccessExpr, typeSolver)).solveField(fieldAccessExpr.getName().getId()); + } + + /** + * Get the type associated with the node. + *

+ * This method was originally intended to get the type of a value: any value has a type. + *

+ * For example: + *

+     * int foo(int a) {
+     *     return a; // when getType is invoked on "a" it returns the type "int"
+     * }
+     * 
+ *

+ * Now, users started using also of names of types itself, which do not have a type. + *

+ * For example: + *

+     * class A {
+     *     int foo(int a) {
+     *         return A.someStaticField; // when getType is invoked on "A", which represents a class, it returns
+     *             // the type "A" itself while it used to throw UnsolvedSymbolException
+     * }
+     * 
+ *

+ * To accommodate this usage and avoid confusion this method return + * the type itself when used on the name of type. + */ + public ResolvedType getType(Node node) { + try { + return getType(node, true); + } catch (UnsolvedSymbolException e) { + if (node instanceof NameExpr) { + NameExpr nameExpr = (NameExpr) node; + SymbolReference typeDeclaration = JavaParserFactory.getContext(node, typeSolver) + .solveType(nameExpr.getNameAsString()); + if (typeDeclaration.isSolved() && typeDeclaration.getCorrespondingDeclaration() instanceof ResolvedReferenceTypeDeclaration) { + ResolvedReferenceTypeDeclaration resolvedReferenceTypeDeclaration = (ResolvedReferenceTypeDeclaration) typeDeclaration.getCorrespondingDeclaration(); + return ReferenceTypeImpl.undeterminedParameters(resolvedReferenceTypeDeclaration, typeSolver); + } + } + throw e; + } + } + + public ResolvedType getType(Node node, boolean solveLambdas) { + if (solveLambdas) { + if (!node.containsData(TYPE_WITH_LAMBDAS_RESOLVED)) { + ResolvedType res = getTypeConcrete(node, solveLambdas); + + node.setData(TYPE_WITH_LAMBDAS_RESOLVED, res); + + boolean secondPassNecessary = false; + if (node instanceof MethodCallExpr) { + MethodCallExpr methodCallExpr = (MethodCallExpr) node; + for (Node arg : methodCallExpr.getArguments()) { + if (!arg.containsData(TYPE_WITH_LAMBDAS_RESOLVED)) { + getType(arg, true); + secondPassNecessary = true; + } + } + } + if (secondPassNecessary) { + node.removeData(TYPE_WITH_LAMBDAS_RESOLVED); + ResolvedType type = getType(node, true); + node.setData(TYPE_WITH_LAMBDAS_RESOLVED, type); + + } + Log.trace("getType on %s -> %s", () -> node, () -> res); + } + return node.getData(TYPE_WITH_LAMBDAS_RESOLVED); + } else { + Optional res = find(TYPE_WITH_LAMBDAS_RESOLVED, node); + if (res.isPresent()) { + return res.get(); + } + res = find(TYPE_WITHOUT_LAMBDAS_RESOLVED, node); + if (!res.isPresent()) { + ResolvedType resType = getTypeConcrete(node, solveLambdas); + node.setData(TYPE_WITHOUT_LAMBDAS_RESOLVED, resType); + Optional finalRes = res; + Log.trace("getType on %s (no solveLambdas) -> %s", () -> node, () -> finalRes); + return resType; + } + return res.get(); + } + } + + private Optional find(DataKey dataKey, Node node) { + if (node.containsData(dataKey)) { + return Optional.of(node.getData(dataKey)); + } + return Optional.empty(); + } + + protected MethodUsage toMethodUsage(MethodReferenceExpr methodReferenceExpr, List paramTypes) { + Expression scope = methodReferenceExpr.getScope(); + ResolvedType typeOfScope = getType(methodReferenceExpr.getScope()); + if (!typeOfScope.isReferenceType()) { + throw new UnsupportedOperationException(typeOfScope.getClass().getCanonicalName()); + } + + Optional result; + Set allMethods = typeOfScope.asReferenceType().getTypeDeclaration() + .orElseThrow(() -> new RuntimeException("TypeDeclaration unexpectedly empty.")) + .getAllMethods(); + + if (scope instanceof TypeExpr) { + // static methods should match all params + List staticMethodUsages = allMethods.stream() + .filter(it -> it.getDeclaration().isStatic()) + .collect(Collectors.toList()); + + result = MethodResolutionLogic.findMostApplicableUsage(staticMethodUsages, methodReferenceExpr.getIdentifier(), paramTypes, typeSolver); + + if (!paramTypes.isEmpty()) { + // instance methods are called on the first param and should match all other params + List instanceMethodUsages = allMethods.stream() + .filter(it -> !it.getDeclaration().isStatic()) + .collect(Collectors.toList()); + + List instanceMethodParamTypes = new ArrayList<>(paramTypes); + instanceMethodParamTypes.remove(0); // remove the first one + + Optional instanceResult = MethodResolutionLogic.findMostApplicableUsage( + instanceMethodUsages, methodReferenceExpr.getIdentifier(), instanceMethodParamTypes, typeSolver); + if (result.isPresent() && instanceResult.isPresent()) { + throw new MethodAmbiguityException("Ambiguous method call: cannot find a most applicable method for " + methodReferenceExpr.getIdentifier()); + } + + if (instanceResult.isPresent()) { + result = instanceResult; + } + } + } else { + result = MethodResolutionLogic.findMostApplicableUsage(new ArrayList<>(allMethods), methodReferenceExpr.getIdentifier(), paramTypes, typeSolver); + + if (result.isPresent() && result.get().getDeclaration().isStatic()) { + throw new RuntimeException("Invalid static method reference " + methodReferenceExpr.getIdentifier()); + } + } + + if (!result.isPresent()) { + throw new UnsupportedOperationException(); + } + + return result.get(); + } + + protected ResolvedType getBinaryTypeConcrete(Node left, Node right, boolean solveLambdas, BinaryExpr.Operator operator) { + ResolvedType leftType = getTypeConcrete(left, solveLambdas); + ResolvedType rightType = getTypeConcrete(right, solveLambdas); + + // JLS 15.18.1. String Concatenation Operator + + // If only one operand expression is of type String, then string conversion (§5.1.11) is performed on the other + // operand to produce a string at run time. + // + // The result of string concatenation is a reference to a String object that is the concatenation of the two + // operand strings. The characters of the left-hand operand precede the characters of the right-hand operand in + // the newly created string. + + if (operator == BinaryExpr.Operator.PLUS) { + boolean isLeftString = leftType.isReferenceType() && leftType.asReferenceType() + .getQualifiedName().equals(JAVA_LANG_STRING); + boolean isRightString = rightType.isReferenceType() && rightType.asReferenceType() + .getQualifiedName().equals(JAVA_LANG_STRING); + if (isLeftString || isRightString) { + return isLeftString ? leftType : rightType; + } + } + + // JLS 5.6.2. Binary Numeric Promotion + // + // Widening primitive conversion (§5.1.2) is applied to convert either or both operands as specified by the + // following rules: + // + // * If either operand is of type double, the other is converted to double. + // * Otherwise, if either operand is of type float, the other is converted to float. + // * Otherwise, if either operand is of type long, the other is converted to long. + // * Otherwise, both operands are converted to type int. + + boolean isLeftNumeric = leftType.isPrimitive() && leftType.asPrimitive().isNumeric(); + boolean isRightNumeric = rightType.isPrimitive() && rightType.asPrimitive().isNumeric(); + + if (isLeftNumeric && isRightNumeric) { + return leftType.asPrimitive().bnp(rightType.asPrimitive()); + } + + if (rightType.isAssignableBy(leftType)) { + return rightType; + } + return leftType; + } + + + /** + * Should return more like a TypeApplication: a TypeDeclaration and possible typeParametersValues or array + * modifiers. + */ + private ResolvedType getTypeConcrete(Node node, boolean solveLambdas) { + if (node == null) throw new IllegalArgumentException(); + return node.accept(typeExtractor, solveLambdas); + } + + /** + * Where a node has an interface/class/enum declaration as its ancestor, return the nearest one. + *

+ * NOTE: See {@link #findContainingTypeDeclOrObjectCreationExpr} if wanting to include anonymous inner classes. + *

+ * For example, these all return X: + * {@code public interface X { ... node here ... }} + * {@code public class X { ... node here ... }} + * {@code public enum X { ... node here ... }} + * + * @param node The Node whose ancestors will be traversed, + * @return The first class/interface/enum declaration in the Node's ancestry. + */ + protected TypeDeclaration findContainingTypeDecl(Node node) { + Node parent = node; + while (true) { + parent = demandParentNode(parent); + if (parent instanceof TypeDeclaration) { + return (TypeDeclaration) parent; + } + } + } + + /** + * Where a node has an interface/class/enum declaration -- or an object creation expression (anonymous inner class) + * -- as its ancestor, return the nearest one. + *

+ * NOTE: See {@link #findContainingTypeDecl} if wanting to not include anonymous inner classes. + *

+ * For example, these all return X: + *

    + *
  • {@code public interface X { ... node here ... }}
  • + *
  • {@code public class X { ... node here ... }}
  • + *
  • {@code public enum X { ... node here ... }}
  • + *
  • {@code
    +     *     new ActionListener() {
    +     *          ... node here ...
    +     *          public void actionPerformed(ActionEvent e) {
    +     *               ... or node here ...
    +     *          }
    +     *     }
    +     *     }
  • + *
+ *

+ * + * @param node The Node whose ancestors will be traversed, + * @return The first class/interface/enum declaration -- or object creation expression (anonymous inner class) -- in + * the Node's ancestry. + */ + protected Node findContainingTypeDeclOrObjectCreationExpr(Node node) { + Node parent = node; + boolean detachFlag = false; + while (true) { + parent = demandParentNode(parent); + if (parent instanceof BodyDeclaration) { + if (parent instanceof TypeDeclaration) { + return parent; + } else { + detachFlag = true; + } + } else if (parent instanceof ObjectCreationExpr) { + if (detachFlag) { + return parent; + } + } + } + } + + /** + * Where a node has an interface/class/enum declaration -- or an object creation expression in an inner class + * references an outer class -- as its ancestor, return the declaration corresponding to the class name specified. + */ + protected Node findContainingTypeDeclOrObjectCreationExpr(Node node, String className) { + Node parent = node; + boolean detachFlag = false; + while (true) { + parent = demandParentNode(parent); + if (parent instanceof BodyDeclaration) { + if (parent instanceof TypeDeclaration && ((TypeDeclaration) parent).getFullyQualifiedName().get().endsWith(className)) { + return parent; + } else { + detachFlag = true; + } + } else if (parent instanceof ObjectCreationExpr) { + if (detachFlag) { + return parent; + } + } + } + } + + + public ResolvedType convertToUsageVariableType(VariableDeclarator var) { + return get(typeSolver).convertToUsage(var.getType(), var); + } + + public ResolvedType convertToUsage(Type type, Node context) { + if (type.isUnknownType()) { + throw new IllegalArgumentException("Inferred lambda parameter type"); + } + return convertToUsage(type, JavaParserFactory.getContext(context, typeSolver)); + } + + public ResolvedType convertToUsage(Type type) { + return convertToUsage(type, type); + } + + // This is an hack around an issue in JavaParser + private String qName(ClassOrInterfaceType classOrInterfaceType) { + String name = classOrInterfaceType.getName().getId(); + if (classOrInterfaceType.getScope().isPresent()) { + return qName(classOrInterfaceType.getScope().get()) + "." + name; + } + return name; + } + + protected ResolvedType convertToUsage(Type type, Context context) { + if (context == null) { + throw new NullPointerException("Context should not be null"); + } + if (type instanceof ClassOrInterfaceType) { + ClassOrInterfaceType classOrInterfaceType = (ClassOrInterfaceType) type; + String name = qName(classOrInterfaceType); + SymbolReference ref = context.solveType(name); + if (!ref.isSolved()) { + throw new UnsolvedSymbolException(name); + } + ResolvedTypeDeclaration typeDeclaration = ref.getCorrespondingDeclaration(); + List typeParameters = Collections.emptyList(); + if (classOrInterfaceType.getTypeArguments().isPresent()) { + typeParameters = classOrInterfaceType.getTypeArguments().get().stream().map((pt) -> convertToUsage(pt, context)).collect(Collectors.toList()); + } + if (typeDeclaration.isTypeParameter()) { + if (typeDeclaration instanceof ResolvedTypeParameterDeclaration) { + return new ResolvedTypeVariable((ResolvedTypeParameterDeclaration) typeDeclaration); + } else { + JavaParserTypeVariableDeclaration javaParserTypeVariableDeclaration = (JavaParserTypeVariableDeclaration) typeDeclaration; + return new ResolvedTypeVariable(javaParserTypeVariableDeclaration.asTypeParameter()); + } + } else { + return new ReferenceTypeImpl((ResolvedReferenceTypeDeclaration) typeDeclaration, typeParameters, typeSolver); + } + } else if (type instanceof PrimitiveType) { + return ResolvedPrimitiveType.byName(((PrimitiveType) type).getType().name()); + } else if (type instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) type; + if (wildcardType.getExtendedType().isPresent() && !wildcardType.getSuperType().isPresent()) { + return ResolvedWildcard.extendsBound(convertToUsage(wildcardType.getExtendedType().get(), context)); // removed (ReferenceTypeImpl) + } else if (!wildcardType.getExtendedType().isPresent() && wildcardType.getSuperType().isPresent()) { + return ResolvedWildcard.superBound(convertToUsage(wildcardType.getSuperType().get(), context)); // removed (ReferenceTypeImpl) + } else if (!wildcardType.getExtendedType().isPresent() && !wildcardType.getSuperType().isPresent()) { + return ResolvedWildcard.UNBOUNDED; + } else { + throw new UnsupportedOperationException(wildcardType.toString()); + } + } else if (type instanceof VoidType) { + return ResolvedVoidType.INSTANCE; + } else if (type instanceof ArrayType) { + ArrayType jpArrayType = (ArrayType) type; + return new ResolvedArrayType(convertToUsage(jpArrayType.getComponentType(), context)); + } else if (type instanceof UnionType) { + UnionType unionType = (UnionType) type; + return new ResolvedUnionType(unionType.getElements().stream().map(el -> convertToUsage(el, context)).collect(Collectors.toList())); + } else if (type instanceof VarType) { + Node parent = type.getParentNode().get(); + if (!(parent instanceof VariableDeclarator)) { + throw new IllegalStateException("Trying to resolve a `var` which is not in a variable declaration."); + } + final VariableDeclarator variableDeclarator = (VariableDeclarator) parent; + return variableDeclarator.getInitializer() + .map(Expression::calculateResolvedType) + .orElseThrow(() -> new IllegalStateException("Cannot resolve `var` which has no initializer.")); + } else { + throw new UnsupportedOperationException(type.getClass().getCanonicalName()); + } + } + + + public ResolvedType convert(Type type, Node node) { + return convert(type, JavaParserFactory.getContext(node, typeSolver)); + } + + public ResolvedType convert(Type type, Context context) { + return convertToUsage(type, context); + } + + public MethodUsage solveMethodAsUsage(MethodCallExpr call) { + List params = new ArrayList<>(); + if (call.getArguments() != null) { + for (Expression param : call.getArguments()) { + //getTypeConcrete(Node node, boolean solveLambdas) + try { + params.add(getType(param, false)); + } catch (Exception e) { + throw new RuntimeException(String.format("Error calculating the type of parameter %s of method call %s", param, call), e); + } + //params.add(getTypeConcrete(param, false)); + } + } + Context context = JavaParserFactory.getContext(call, typeSolver); + Optional methodUsage = context.solveMethodAsUsage(call.getName().getId(), params); + if (!methodUsage.isPresent()) { + throw new RuntimeException("Method '" + call.getName() + "' cannot be resolved in context " + + call + " (line: " + call.getRange().map(r -> "" + r.begin.line).orElse("??") + ") " + context + ". Parameter types: " + params); + } + return methodUsage.get(); + } + + public ResolvedReferenceTypeDeclaration getTypeDeclaration(Node node) { + if (node instanceof TypeDeclaration) { + return getTypeDeclaration((TypeDeclaration) node); + } else if (node instanceof ObjectCreationExpr) { + return new JavaParserAnonymousClassDeclaration((ObjectCreationExpr) node, typeSolver); + } else { + throw new IllegalArgumentException(); + } + } + + public ResolvedReferenceTypeDeclaration getTypeDeclaration(ClassOrInterfaceDeclaration classOrInterfaceDeclaration) { + return JavaParserFactory.toTypeDeclaration(classOrInterfaceDeclaration, typeSolver); + } + + /** + * "this" inserted in the given point, which type would have? + */ + public ResolvedType getTypeOfThisIn(Node node) { + // TODO consider static methods + if (node instanceof ClassOrInterfaceDeclaration) { + return new ReferenceTypeImpl(getTypeDeclaration((ClassOrInterfaceDeclaration) node), typeSolver); + } else if (node instanceof EnumDeclaration) { + JavaParserEnumDeclaration enumDeclaration = new JavaParserEnumDeclaration((EnumDeclaration) node, typeSolver); + return new ReferenceTypeImpl(enumDeclaration, typeSolver); + } else if (node instanceof ObjectCreationExpr && ((ObjectCreationExpr) node).getAnonymousClassBody().isPresent()) { + JavaParserAnonymousClassDeclaration anonymousDeclaration = new JavaParserAnonymousClassDeclaration((ObjectCreationExpr) node, typeSolver); + return new ReferenceTypeImpl(anonymousDeclaration, typeSolver); + } + return getTypeOfThisIn(demandParentNode(node)); + } + + public ResolvedReferenceTypeDeclaration getTypeDeclaration(TypeDeclaration typeDeclaration) { + return JavaParserFactory.toTypeDeclaration(typeDeclaration, typeSolver); + } + + public ResolvedType classToResolvedType(Class clazz) { + if (clazz.isPrimitive()) { + return ResolvedPrimitiveType.byName(clazz.getName()); + } + + ResolvedReferenceTypeDeclaration declaration; + if (clazz.isAnnotation()) { + declaration = new ReflectionAnnotationDeclaration(clazz, typeSolver); + } else if (clazz.isEnum()) { + declaration = new ReflectionEnumDeclaration(clazz, typeSolver); + } else if (clazz.isInterface()) { + declaration = new ReflectionInterfaceDeclaration(clazz, typeSolver); + } else { + declaration = new ReflectionClassDeclaration(clazz, typeSolver); + } + return new ReferenceTypeImpl(declaration, typeSolver); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/JavaParserFactory.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/JavaParserFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..ea00fbaab4f48f07851f0af6f77b42fb9ec7c170 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/JavaParserFactory.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.*; +import com.github.javaparser.ast.expr.*; +import com.github.javaparser.ast.stmt.*; +import com.github.javaparser.ast.type.ClassOrInterfaceType; +import com.github.javaparser.ast.type.TypeParameter; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.javaparsermodel.contexts.*; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.*; +import com.github.javaparser.symbolsolver.javaparsermodel.declarators.*; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.resolution.SymbolDeclarator; + +import static com.github.javaparser.symbolsolver.javaparser.Navigator.demandParentNode; + +/** + * @author Federico Tomassetti + */ +public class JavaParserFactory { + + public static Context getContext(Node node, TypeSolver typeSolver) { + if (node == null) { + throw new NullPointerException("Node should not be null"); + } + + // TODO: Is order important here? + if (node instanceof ArrayAccessExpr) { + return new ArrayAccessExprContext((ArrayAccessExpr) node, typeSolver); + } else if (node instanceof AnnotationDeclaration) { + return new AnnotationDeclarationContext((AnnotationDeclaration) node, typeSolver); + } else if (node instanceof BinaryExpr) { + return new BinaryExprContext((BinaryExpr) node, typeSolver); + } else if (node instanceof BlockStmt) { + return new BlockStmtContext((BlockStmt) node, typeSolver); + } else if (node instanceof CompilationUnit) { + return new CompilationUnitContext((CompilationUnit) node, typeSolver); + } else if (node instanceof EnclosedExpr) { + return new EnclosedExprContext((EnclosedExpr) node, typeSolver); + } else if (node instanceof ForEachStmt) { + return new ForEachStatementContext((ForEachStmt) node, typeSolver); + } else if (node instanceof ForStmt) { + return new ForStatementContext((ForStmt) node, typeSolver); + } else if (node instanceof IfStmt) { + return new IfStatementContext((IfStmt) node, typeSolver); + } else if (node instanceof InstanceOfExpr) { + return new InstanceOfExprContext((InstanceOfExpr) node, typeSolver); + } else if (node instanceof LambdaExpr) { + return new LambdaExprContext((LambdaExpr) node, typeSolver); + } else if (node instanceof MethodDeclaration) { + return new MethodContext((MethodDeclaration) node, typeSolver); + } else if (node instanceof ConstructorDeclaration) { + return new ConstructorContext((ConstructorDeclaration) node, typeSolver); + } else if (node instanceof ClassOrInterfaceDeclaration) { + return new ClassOrInterfaceDeclarationContext((ClassOrInterfaceDeclaration) node, typeSolver); + } else if (node instanceof MethodCallExpr) { + return new MethodCallExprContext((MethodCallExpr) node, typeSolver); + } else if (node instanceof MethodReferenceExpr) { + return new MethodReferenceExprContext((MethodReferenceExpr) node, typeSolver); + } else if (node instanceof EnumDeclaration) { + return new EnumDeclarationContext((EnumDeclaration) node, typeSolver); + } else if (node instanceof FieldAccessExpr) { + return new FieldAccessContext((FieldAccessExpr) node, typeSolver); + } else if (node instanceof SwitchEntry) { + return new SwitchEntryContext((SwitchEntry) node, typeSolver); + } else if (node instanceof TryStmt) { + return new TryWithResourceContext((TryStmt) node, typeSolver); + } else if (node instanceof Statement) { + return new StatementContext<>((Statement) node, typeSolver); + } else if (node instanceof CatchClause) { + return new CatchClauseContext((CatchClause) node, typeSolver); + } else if (node instanceof UnaryExpr) { + return new UnaryExprContext((UnaryExpr) node, typeSolver); + } else if (node instanceof VariableDeclarator) { + return new VariableDeclaratorContext((VariableDeclarator) node, typeSolver); + } else if (node instanceof VariableDeclarationExpr) { + return new VariableDeclarationExprContext((VariableDeclarationExpr) node, typeSolver); + } else if (node instanceof ObjectCreationExpr && + ((ObjectCreationExpr) node).getAnonymousClassBody().isPresent()) { + return new AnonymousClassDeclarationContext((ObjectCreationExpr) node, typeSolver); + } else if (node instanceof ObjectCreationExpr) { + return new ObjectCreationContext((ObjectCreationExpr)node, typeSolver); + } else { + if (node instanceof NameExpr) { + // to resolve a name when in a fieldAccess context, we can go up until we get a node other than FieldAccessExpr, + // in order to prevent a infinite loop if the name is the same as the field (ie x.x, x.y.x, or x.y.z.x) + if (node.getParentNode().isPresent() && node.getParentNode().get() instanceof FieldAccessExpr) { + Node ancestor = node.getParentNode().get(); + while (ancestor.getParentNode().isPresent()) { + ancestor = ancestor.getParentNode().get(); + if (!(ancestor instanceof FieldAccessExpr)) { + break; + } + } + return getContext(ancestor, typeSolver); + } + if (node.getParentNode().isPresent() && node.getParentNode().get() instanceof ObjectCreationExpr && node.getParentNode().get().getParentNode().isPresent()) { + return getContext(node.getParentNode().get().getParentNode().get(), typeSolver); + } + } + final Node parentNode = demandParentNode(node); + if (node instanceof ClassOrInterfaceType && parentNode instanceof ClassOrInterfaceDeclaration) { + ClassOrInterfaceDeclaration parentDeclaration = (ClassOrInterfaceDeclaration) parentNode; + if (parentDeclaration.getImplementedTypes().contains(node) || + parentDeclaration.getExtendedTypes().contains(node)) { + // When resolving names in implements and extends the body of the declaration + // should not be searched so use limited context. + return new ClassOrInterfaceDeclarationExtendsContext(parentDeclaration, typeSolver); + } + } + return getContext(parentNode, typeSolver); + } + } + + public static SymbolDeclarator getSymbolDeclarator(Node node, TypeSolver typeSolver) { + if (node instanceof FieldDeclaration) { + return new FieldSymbolDeclarator((FieldDeclaration) node, typeSolver); + } else if (node instanceof Parameter) { + return new ParameterSymbolDeclarator((Parameter) node, typeSolver); + } else if (node instanceof PatternExpr) { + return new PatternSymbolDeclarator((PatternExpr) node, typeSolver); + } else if (node instanceof ExpressionStmt) { + ExpressionStmt expressionStmt = (ExpressionStmt) node; + if (expressionStmt.getExpression() instanceof VariableDeclarationExpr) { + return new VariableSymbolDeclarator((VariableDeclarationExpr) (expressionStmt.getExpression()), typeSolver); + } else { + return new NoSymbolDeclarator<>(expressionStmt, typeSolver); + } + } else if (node instanceof ForEachStmt) { + ForEachStmt foreachStmt = (ForEachStmt) node; + return new VariableSymbolDeclarator(foreachStmt.getVariable(), typeSolver); + } else { + return new NoSymbolDeclarator<>(node, typeSolver); + } + } + + public static ResolvedReferenceTypeDeclaration toTypeDeclaration(Node node, TypeSolver typeSolver) { + if (node instanceof ClassOrInterfaceDeclaration) { + if (((ClassOrInterfaceDeclaration) node).isInterface()) { + return new JavaParserInterfaceDeclaration((ClassOrInterfaceDeclaration) node, typeSolver); + } else { + return new JavaParserClassDeclaration((ClassOrInterfaceDeclaration) node, typeSolver); + } + } else if (node instanceof TypeParameter) { + return new JavaParserTypeParameter((TypeParameter) node, typeSolver); + } else if (node instanceof EnumDeclaration) { + return new JavaParserEnumDeclaration((EnumDeclaration) node, typeSolver); + } else if (node instanceof AnnotationDeclaration) { + return new JavaParserAnnotationDeclaration((AnnotationDeclaration) node, typeSolver); + } else if (node instanceof EnumConstantDeclaration) { + return new JavaParserEnumDeclaration((EnumDeclaration) demandParentNode((EnumConstantDeclaration) node), typeSolver); + } else { + throw new IllegalArgumentException(node.getClass().getCanonicalName()); + } + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/LambdaArgumentTypePlaceholder.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/LambdaArgumentTypePlaceholder.java new file mode 100644 index 0000000000000000000000000000000000000000..ed8eebea68119625419c3a477c841671dbde9da3 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/LambdaArgumentTypePlaceholder.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel; + +import com.github.javaparser.resolution.declarations.ResolvedMethodLikeDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; + +/** + * Placeholder used to represent a lambda argument type while it is being + * calculated. + * + * @author Federico Tomassetti + */ +public class LambdaArgumentTypePlaceholder implements ResolvedType { + + private int pos; + private SymbolReference method; + + public LambdaArgumentTypePlaceholder(int pos) { + this.pos = pos; + } + + @Override + public boolean isArray() { + return false; + } + + @Override + public boolean isPrimitive() { + return false; + } + + @Override + public boolean isReferenceType() { + return false; + } + + @Override + public String describe() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isTypeVariable() { + return false; + } + + public void setMethod(SymbolReference method) { + this.method = method; + } + + @Override + public boolean isAssignableBy(ResolvedType other) { + throw new UnsupportedOperationException(); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/TypeExtractor.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/TypeExtractor.java new file mode 100644 index 0000000000000000000000000000000000000000..88b8f727d93ec21083f513adec943d6948d77475 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/TypeExtractor.java @@ -0,0 +1,794 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.FieldDeclaration; +import com.github.javaparser.ast.body.Parameter; +import com.github.javaparser.ast.body.VariableDeclarator; +import com.github.javaparser.ast.expr.*; +import com.github.javaparser.ast.stmt.BlockStmt; +import com.github.javaparser.ast.stmt.ExpressionStmt; +import com.github.javaparser.ast.stmt.ReturnStmt; +import com.github.javaparser.ast.type.ClassOrInterfaceType; +import com.github.javaparser.ast.type.Type; +import com.github.javaparser.ast.type.UnknownType; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedArrayType; +import com.github.javaparser.resolution.types.ResolvedPrimitiveType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.resolution.types.ResolvedVoidType; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserSymbolDeclaration; +import com.github.javaparser.symbolsolver.logic.FunctionalInterfaceLogic; +import com.github.javaparser.symbolsolver.logic.InferenceContext; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.resolution.Value; +import com.github.javaparser.symbolsolver.model.typesystem.NullType; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.symbolsolver.reflectionmodel.MyObjectProvider; +import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionClassDeclaration; +import com.github.javaparser.symbolsolver.resolution.SymbolSolver; +import com.github.javaparser.symbolsolver.resolution.typeinference.TypeHelper; +import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver; +import com.github.javaparser.utils.Log; +import com.github.javaparser.utils.Pair; +import com.google.common.collect.ImmutableList; + +import java.util.List; +import java.util.Optional; + +import static com.github.javaparser.symbolsolver.javaparser.Navigator.demandParentNode; +import static com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade.solveGenericTypes; + +public class TypeExtractor extends DefaultVisitorAdapter { + + private static final String JAVA_LANG_STRING = String.class.getCanonicalName(); + + private TypeSolver typeSolver; + private JavaParserFacade facade; + + private ReferenceTypeImpl StringReferenceType; + + public TypeExtractor(TypeSolver typeSolver, JavaParserFacade facade) { + this.typeSolver = typeSolver; + this.facade = facade; + //pre-calculate the String reference (optimization) + StringReferenceType = new ReferenceTypeImpl(new ReflectionTypeSolver().solveType(JAVA_LANG_STRING), typeSolver); + } + + @Override + public ResolvedType visit(VariableDeclarator node, Boolean solveLambdas) { + if (demandParentNode(node) instanceof FieldDeclaration) { + return facade.convertToUsageVariableType(node); + } else if (demandParentNode(node) instanceof VariableDeclarationExpr) { + return facade.convertToUsageVariableType(node); + } + throw new UnsupportedOperationException(demandParentNode(node).getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(Parameter node, Boolean solveLambdas) { + if (node.getType() instanceof UnknownType) { + throw new IllegalStateException("Parameter has unknown type: " + node); + } + return facade.convertToUsage(node.getType(), node); + } + + + @Override + public ResolvedType visit(ArrayAccessExpr node, Boolean solveLambdas) { + ResolvedType arrayUsageType = node.getName().accept(this, solveLambdas); + if (arrayUsageType.isArray()) { + return ((ResolvedArrayType) arrayUsageType).getComponentType(); + } + return arrayUsageType; + } + + @Override + public ResolvedType visit(ArrayCreationExpr node, Boolean solveLambdas) { + ResolvedType res = facade.convertToUsage(node.getElementType(), JavaParserFactory.getContext(node, typeSolver)); + for (int i = 0; i < node.getLevels().size(); i++) { + res = new ResolvedArrayType(res); + } + return res; + } + + @Override + public ResolvedType visit(ArrayInitializerExpr node, Boolean solveLambdas) { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + + @Override + public ResolvedType visit(AssignExpr node, Boolean solveLambdas) { + return node.getTarget().accept(this, solveLambdas); + } + + @Override + public ResolvedType visit(BinaryExpr node, Boolean solveLambdas) { + switch (node.getOperator()) { + case PLUS: + case MINUS: + case DIVIDE: + case MULTIPLY: + case REMAINDER: + case BINARY_AND: + case BINARY_OR: + case XOR: + return facade.getBinaryTypeConcrete(node.getLeft(), node.getRight(), solveLambdas, node.getOperator()); + case LESS_EQUALS: + case LESS: + case GREATER: + case GREATER_EQUALS: + case EQUALS: + case NOT_EQUALS: + case OR: + case AND: + return ResolvedPrimitiveType.BOOLEAN; + case SIGNED_RIGHT_SHIFT: + case UNSIGNED_RIGHT_SHIFT: + case LEFT_SHIFT: + ResolvedType rt = node.getLeft().accept(this, solveLambdas); + // apply unary primitive promotion + return ResolvedPrimitiveType.unp(rt); + default: + throw new UnsupportedOperationException("Operator " + node.getOperator().name()); + } + } + + @Override + public ResolvedType visit(CastExpr node, Boolean solveLambdas) { + return facade.convertToUsage(node.getType(), JavaParserFactory.getContext(node, typeSolver)); + } + + @Override + public ResolvedType visit(ClassExpr node, Boolean solveLambdas) { + // This implementation does not regard the actual type argument of the ClassExpr. + Type astType = node.getType(); + ResolvedType jssType = facade.convertToUsage(astType, node.getType()); + return new ReferenceTypeImpl(new ReflectionClassDeclaration(Class.class, typeSolver), ImmutableList.of(jssType), typeSolver); + } + + /* + * The conditional operator has three operand expressions. ? appears between the first and second expressions, and + * : appears between the second and third expressions. + * There are three kinds of conditional expressions, classified according to the second and third operand + * expressions: boolean conditional expressions, numeric conditional expressions, and reference conditional + * expressions. + * The classification rules are as follows: + * 1/ If both the second and the third operand expressions are boolean expressions, the conditional expression is a + * boolean conditional expression. + * 2/ If both the second and the third operand expressions are numeric expressions, the conditional expression is a + * numeric conditional expression. + * 3/ Otherwise, the conditional expression is a reference conditional expression + */ + @Override + public ResolvedType visit(ConditionalExpr node, Boolean solveLambdas) { + ResolvedType thenExpr = node.getThenExpr().accept(this, solveLambdas); + ResolvedType elseExpr = node.getElseExpr().accept(this, solveLambdas); + + // manage null expression + if ( thenExpr.isNull()) { + return elseExpr; + } + if ( elseExpr.isNull()) { + return thenExpr; + } + /* + * Boolean conditional expressions are standalone expressions + * The type of a boolean conditional expression is determined as follows: + * If the second and third operands are both of type Boolean, the conditional expression has type Boolean. + * Otherwise, the conditional expression has type boolean. + */ + if ( thenExpr.isAssignableBy(ResolvedPrimitiveType.BOOLEAN) + && elseExpr.isAssignableBy(ResolvedPrimitiveType.BOOLEAN)) { + if (thenExpr.isReferenceType() && elseExpr.isReferenceType()) { + return thenExpr.asReferenceType(); + } + return thenExpr.isPrimitive() ? thenExpr : elseExpr; + } + + /* + * Numeric conditional expressions are standalone expressions (§15.2). + * The type of a numeric conditional expression is determined as follows: + * If the second and third operands have the same type, then that is the type of the conditional expression. + * If one of the second and third operands is of primitive type T, and the type of the other is the result of + * applying boxing conversion (§5.1.7) to T, then the type of the conditional expression is T. + * If one of the operands is of type byte or Byte and the other is of type short or Short, then the type of the + * conditional expression is short. + * If one of the operands is of type T where T is byte, short, or char, and the other operand is a constant + * expression (§15.28) of type int whose value is representable in type T, then the type of the conditional + * expression is T. + * If one of the operands is of type T, where T is Byte, Short, or Character, and the other operand is a + * constant expression of type int whose value is representable in the type U which is the result of applying + * unboxing conversion to T, then the type of the conditional expression is U. + * Otherwise, binary numeric promotion (§5.6.2) is applied to the operand types, and the type of the + * conditional expression is the promoted type of the second and third operands. + */ + if (thenExpr.isNumericType() && elseExpr.isNumericType()) { + ResolvedPrimitiveType[] resolvedPrimitiveTypeSubList = new ResolvedPrimitiveType[] {ResolvedPrimitiveType.BYTE, ResolvedPrimitiveType.SHORT, ResolvedPrimitiveType.CHAR}; + /* + * If the second and third operands have the same type, then that is the type of the conditional expression. + */ + String qnameTypeThenExpr = thenExpr.isPrimitive() ? thenExpr.asPrimitive().describe() + : thenExpr.asReferenceType().describe(); + String qnameTypeElseExpr = elseExpr.isPrimitive() ? elseExpr.asPrimitive().describe() + : elseExpr.asReferenceType().describe(); + if (qnameTypeThenExpr.equals(qnameTypeElseExpr)) { + return thenExpr; + } + /* + * If one of the second and third operands is of primitive type T, and the type of the other is the result of + * applying boxing conversion (§5.1.7) to T, then the type of the conditional expression is T. + */ + else if ((thenExpr.isPrimitive() && elseExpr.isReferenceType() + && isCompatible(elseExpr.asReferenceType(), thenExpr.asPrimitive()))) { + return thenExpr; + } else if ((elseExpr.isPrimitive() && thenExpr.isReferenceType() + && isCompatible(thenExpr.asReferenceType(), elseExpr.asPrimitive()))) { + return elseExpr; + } + /* + * If one of the operands is of type byte or Byte and the other is of type short or Short, then the type of the + * conditional expression is short. + */ + else if ((isCompatible(thenExpr, ResolvedPrimitiveType.BYTE) && isCompatible(elseExpr, ResolvedPrimitiveType.SHORT)) + || (isCompatible(elseExpr, ResolvedPrimitiveType.BYTE) && isCompatible(thenExpr, ResolvedPrimitiveType.SHORT))) { + return ResolvedPrimitiveType.SHORT; + } + /* + * If one of the operands is of type T where T is byte, short, or char, and the + * other operand is a constant expression (§15.28) of type int whose value is + * representable in type T, then the type of the conditional expression is T + * How can we know if the constant expression of type int is representable in type T ? + * "The constant expression of type int is representable in type T" is a runtime decision! + */ + else if (thenExpr.isPrimitive() && elseExpr.isPrimitive()) { + if (((ResolvedPrimitiveType)thenExpr).in(resolvedPrimitiveTypeSubList) + && ((ResolvedPrimitiveType)elseExpr).equals(ResolvedPrimitiveType.INT)) { + return thenExpr; + } else if (((ResolvedPrimitiveType)elseExpr).in(resolvedPrimitiveTypeSubList) + && ((ResolvedPrimitiveType)thenExpr).equals(ResolvedPrimitiveType.INT)) { + return elseExpr; + } + } + /* If one of the operands is of type T, where T is Byte, Short, or Character, + * and the other operand is a constant expression of type int whose value is + * representable in the type U which is the result of applying unboxing + * conversion to T, then the type of the conditional expression is U. + * A priori this is a runtime decision! + */ + else if (thenExpr.isReference() && elseExpr.isPrimitive() + && thenExpr.asReferenceType().isUnboxable() + && thenExpr.asReferenceType().toUnboxedType().get().in(resolvedPrimitiveTypeSubList) + && ((ResolvedPrimitiveType)elseExpr).equals(ResolvedPrimitiveType.INT)) { + return thenExpr.asReferenceType().toUnboxedType().get(); + } else if (elseExpr.isReference() && thenExpr.isPrimitive() + && elseExpr.asReferenceType().isUnboxable() + && elseExpr.asReferenceType().toUnboxedType().get().in(resolvedPrimitiveTypeSubList) + && ((ResolvedPrimitiveType)thenExpr).equals(ResolvedPrimitiveType.INT)) { + return elseExpr.asReferenceType().toUnboxedType().get(); + } + + /* Otherwise, binary numeric promotion (§5.6.2) is applied to the operand types, + * and the type of the conditional expression is the promoted type of the second + * and third operands. + */ + ResolvedPrimitiveType PrimitiveThenExpr = thenExpr.isPrimitive() ? thenExpr.asPrimitive() + : thenExpr.asReferenceType().toUnboxedType().get(); + ResolvedPrimitiveType PrimitiveElseExpr = elseExpr.isPrimitive() ? elseExpr.asPrimitive() + : elseExpr.asReferenceType().toUnboxedType().get(); + return PrimitiveThenExpr.bnp(PrimitiveElseExpr); + } + + /* + * Otherwise, the conditional expression is a reference conditional expression. + * A reference conditional expression is a poly expression if it appears in an assignment context or an + * invocation context (§5.2. §5.3). + * Otherwise, it is a standalone expression. + * The type of a poly reference conditional expression is the same as its target type. + * The type of a standalone reference conditional expression is determined as follows: + * If the second and third operands have the same type (which may be the null type), then that is the type of + * the conditional expression. + * If the type of one of the second and third operands is the null type, and the type of the other operand is a + * reference type, then the type of the conditional expression is that reference type. + * Otherwise, the second and third operands are of types S1 and S2 respectively. Let T1 be the type that + * results from applying boxing conversion to S1, and let T2 be the type that results from applying boxing + * conversion to S2. The type of the conditional expression is the result of applying capture conversion + * (§5.1.10) to lub(T1, T2). + * TODO : must be implemented + */ + if (node.isPolyExpression()) { + // The type of a poly reference conditional expression is the same as its target type. + Optional parentNode = node.getParentNode(); + if (parentNode.isPresent()) { + Node parent = parentNode.get(); + if (parent instanceof AssignExpr) { + return visit((AssignExpr)parent, solveLambdas); + } else if (parent instanceof MethodCallExpr) { + // how to define the target type? + // a priori it is the type of the parameter of the method which takes the value of the conditional expression + // TODO for the moment we keep the original return type + return thenExpr; + } + throw new RuntimeException("Cannot resolve type of poly expression "+ node.toString()); + } else { + throw new RuntimeException("Parent node unexpectedly empty"); + } + + } + + // The type of a standalone reference conditional expression is determined as follows: + + // If the second and third operands have the same type (which may be the null type), then that is the type of + // the conditional expression. + if (thenExpr.equals(elseExpr)) { + return thenExpr; + } + // If the type of one of the second and third operands is the null type, and the type of the other operand is a + // reference type, then the type of the conditional expression is that reference type. + // this case is already supported above + + // Otherwise, the second and third operands are of types S1 and S2 respectively. Let T1 be the type that + // results from applying boxing conversion to S1, and let T2 be the type that results from applying boxing + // conversion to S2. The type of the conditional expression is the result of applying capture conversion + // (§5.1.10) to lub(T1, T2). + ResolvedType resolvedThenType = thenExpr.isPrimitive() ? TypeHelper.toBoxedType(thenExpr.asPrimitive(), typeSolver) : thenExpr; + ResolvedType resolvedElseType = elseExpr.isPrimitive() ? TypeHelper.toBoxedType(elseExpr.asPrimitive(), typeSolver) : elseExpr; + + // TypeHelper.leastUpperBound method is not yet implemented so for the moment we keep the original return type of this method + // TODO implement TypeHelper.leastUpperBound method + // return TypeHelper.leastUpperBound(new HashSet(Arrays.asList(resolvedThenType, resolvedElseType))); + return node.getThenExpr().accept(this, solveLambdas); + } + + private boolean isCompatible(ResolvedType resolvedType, ResolvedPrimitiveType primitiveType) { + return (resolvedType.isPrimitive() && resolvedType.asPrimitive().equals(primitiveType)) + || (resolvedType.isReferenceType() && resolvedType.asReferenceType().isUnboxableTo(primitiveType)); + } + + @Override + public ResolvedType visit(EnclosedExpr node, Boolean solveLambdas) { + return node.getInner().accept(this, solveLambdas); + } + + /** + * Java Parser can't differentiate between packages, internal types, and fields. + * All three are lumped together into FieldAccessExpr. We need to differentiate them. + */ + private ResolvedType solveDotExpressionType(ResolvedReferenceTypeDeclaration parentType, FieldAccessExpr node) { + // Fields and internal type declarations cannot have the same name. + // Thus, these checks will always be mutually exclusive. + if (parentType.isEnum() && parentType.asEnum().hasEnumConstant(node.getName().getId())) { + return parentType.asEnum().getEnumConstant(node.getName().getId()).getType(); + } else if (parentType.hasField(node.getName().getId())) { + return parentType.getField(node.getName().getId()).getType(); + } else if (parentType.hasInternalType(node.getName().getId())) { + return new ReferenceTypeImpl(parentType.getInternalType(node.getName().getId()), typeSolver); + } else { + throw new UnsolvedSymbolException(node.getName().getId()); + } + } + + @Override + public ResolvedType visit(FieldAccessExpr node, Boolean solveLambdas) { + // We should understand if this is a static access + if (node.getScope() instanceof NameExpr || + node.getScope() instanceof FieldAccessExpr) { + Expression staticValue = node.getScope(); + SymbolReference typeAccessedStatically = JavaParserFactory.getContext(node, typeSolver).solveType(staticValue.toString()); + if (typeAccessedStatically.isSolved()) { + // TODO here maybe we have to substitute type typeParametersValues + return solveDotExpressionType( + typeAccessedStatically.getCorrespondingDeclaration().asReferenceType(), node); + } + } else if (node.getScope() instanceof ThisExpr) { + // If we are accessing through a 'this' expression, first resolve the type + // corresponding to 'this' + SymbolReference solve = facade.solve((ThisExpr) node.getScope()); + // If found get it's declaration and get the field in there + if (solve.isSolved()) { + ResolvedTypeDeclaration correspondingDeclaration = solve.getCorrespondingDeclaration(); + if (correspondingDeclaration instanceof ResolvedReferenceTypeDeclaration) { + return solveDotExpressionType(correspondingDeclaration.asReferenceType(), node); + } + } + + } else if (node.getScope().toString().indexOf('.') > 0) { + // try to find fully qualified name + SymbolReference sr = typeSolver.tryToSolveType(node.getScope().toString()); + if (sr.isSolved()) { + return solveDotExpressionType(sr.getCorrespondingDeclaration(), node); + } + } + Optional value = Optional.empty(); + try { + value = new SymbolSolver(typeSolver).solveSymbolAsValue(node.getName().getId(), node); + } catch (UnsolvedSymbolException use) { + // This node may have a package name as part of its fully qualified name. + // We should solve for the type declaration inside this package. + SymbolReference sref = typeSolver.tryToSolveType(node.toString()); + if (sref.isSolved()) { + return new ReferenceTypeImpl(sref.getCorrespondingDeclaration(), typeSolver); + } + } + if (value.isPresent()) { + return value.get().getType(); + } + throw new UnsolvedSymbolException(node.getName().getId()); + } + + @Override + public ResolvedType visit(InstanceOfExpr node, Boolean solveLambdas) { + return ResolvedPrimitiveType.BOOLEAN; + } + + @Override + public ResolvedType visit(StringLiteralExpr node, Boolean solveLambdas) { + return StringReferenceType; + } + + @Override + public ResolvedType visit(IntegerLiteralExpr node, Boolean solveLambdas) { + return ResolvedPrimitiveType.INT; + } + + @Override + public ResolvedType visit(LongLiteralExpr node, Boolean solveLambdas) { + return ResolvedPrimitiveType.LONG; + } + + @Override + public ResolvedType visit(CharLiteralExpr node, Boolean solveLambdas) { + return ResolvedPrimitiveType.CHAR; + } + + @Override + public ResolvedType visit(DoubleLiteralExpr node, Boolean solveLambdas) { + if (node.getValue().toLowerCase().endsWith("f")) { + return ResolvedPrimitiveType.FLOAT; + } + return ResolvedPrimitiveType.DOUBLE; + } + + @Override + public ResolvedType visit(BooleanLiteralExpr node, Boolean solveLambdas) { + return ResolvedPrimitiveType.BOOLEAN; + } + + @Override + public ResolvedType visit(NullLiteralExpr node, Boolean solveLambdas) { + return NullType.INSTANCE; + } + + @Override + public ResolvedType visit(MethodCallExpr node, Boolean solveLambdas) { + Log.trace("getType on method call %s", ()-> node); + // first solve the method + MethodUsage ref = facade.solveMethodAsUsage(node); + Log.trace("getType on method call %s resolved to %s", ()-> node, ()-> ref); + Log.trace("getType on method call %s return type is %s", ()-> node, ref::returnType); + return ref.returnType(); + // the type is the return type of the method + } + + @Override + public ResolvedType visit(NameExpr node, Boolean solveLambdas) { + Log.trace("getType on name expr %s", ()-> node); + Optional value = new SymbolSolver(typeSolver).solveSymbolAsValue(node.getName().getId(), node); + if (!value.isPresent()) { + throw new UnsolvedSymbolException("Solving " + node, node.getName().getId()); + } else { + return value.get().getType(); + } + } + + @Override + public ResolvedType visit(TypeExpr node, Boolean solveLambdas) { + Log.trace("getType on type expr %s", ()-> node); + if (!(node.getType() instanceof ClassOrInterfaceType)) { + throw new UnsupportedOperationException(node.getType().getClass().getCanonicalName()); + } + + ClassOrInterfaceType classOrInterfaceType = (ClassOrInterfaceType) node.getType(); + String nameWithScope = classOrInterfaceType.getNameWithScope(); + + // JLS 15.13 - ReferenceType :: [TypeArguments] Identifier + SymbolReference typeDeclarationSymbolReference = JavaParserFactory + .getContext(classOrInterfaceType, typeSolver) + .solveType(nameWithScope); + if (typeDeclarationSymbolReference.isSolved()) { + return new ReferenceTypeImpl(typeDeclarationSymbolReference.getCorrespondingDeclaration().asReferenceType(), typeSolver); + } + + // JLS 15.13 - ExpressionName :: [TypeArguments] Identifier + Optional value = new SymbolSolver(typeSolver).solveSymbolAsValue(nameWithScope, node); + if (value.isPresent()) { + return value.get().getType(); + } + + throw new UnsolvedSymbolException("Solving " + node, classOrInterfaceType.getName().getId()); + } + + @Override + public ResolvedType visit(ObjectCreationExpr node, Boolean solveLambdas) { + return facade.convertToUsage(node.getType(), node); + } + + @Override + public ResolvedType visit(ThisExpr node, Boolean solveLambdas) { + // If 'this' is prefixed by a class eg. MyClass.this + if (node.getTypeName().isPresent()) { + // Get the class name + String className = node.getTypeName().get().asString(); + // Attempt to resolve locally in Compilation unit + // first try a buttom/up approach + try { + return new ReferenceTypeImpl( + facade.getTypeDeclaration(facade.findContainingTypeDeclOrObjectCreationExpr(node, className)), + typeSolver); + } catch (IllegalStateException e) { + // trying another approach from type solver + Optional cu = node.findAncestor(CompilationUnit.class); + SymbolReference clazz = typeSolver.tryToSolveType(className); + if (clazz.isSolved()) { + return new ReferenceTypeImpl(clazz.getCorrespondingDeclaration(), typeSolver); + } + } + } + return new ReferenceTypeImpl(facade.getTypeDeclaration(facade.findContainingTypeDeclOrObjectCreationExpr(node)), typeSolver); + } + + @Override + public ResolvedType visit(SuperExpr node, Boolean solveLambdas) { + // If 'super' is prefixed by a class eg. MyClass.this + if (node.getTypeName().isPresent()) { + String className = node.getTypeName().get().asString(); + SymbolReference resolvedTypeNameRef = JavaParserFactory.getContext(node, typeSolver).solveType(className); + if (resolvedTypeNameRef.isSolved()) { + // Cfr JLS $15.12.1 + ResolvedTypeDeclaration resolvedTypeName = resolvedTypeNameRef.getCorrespondingDeclaration(); + if (resolvedTypeName.isInterface()) { + return new ReferenceTypeImpl(resolvedTypeName.asInterface(), typeSolver); + } else if (resolvedTypeName.isClass()) { + // TODO: Maybe include a presence check? e.g. in the case of `java.lang.Object` there will be no superclass. + return resolvedTypeName.asClass().getSuperClass().orElseThrow(() -> new RuntimeException("super class unexpectedly empty")); + } else { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + } else { + throw new UnsolvedSymbolException(className); + } + } + + ResolvedTypeDeclaration typeOfNode = facade.getTypeDeclaration(facade.findContainingTypeDeclOrObjectCreationExpr(node)); + if (typeOfNode instanceof ResolvedClassDeclaration) { + // TODO: Maybe include a presence check? e.g. in the case of `java.lang.Object` there will be no superclass. + return ((ResolvedClassDeclaration) typeOfNode).getSuperClass().orElseThrow(() -> new RuntimeException("super class unexpectedly empty")); + } else { + throw new UnsupportedOperationException(node.getClass().getCanonicalName()); + } + } + + @Override + public ResolvedType visit(UnaryExpr node, Boolean solveLambdas) { + switch (node.getOperator()) { + case MINUS: + case PLUS: + return ResolvedPrimitiveType.unp(node.getExpression().accept(this, solveLambdas)); + case LOGICAL_COMPLEMENT: + return ResolvedPrimitiveType.BOOLEAN; + case POSTFIX_DECREMENT: + case PREFIX_DECREMENT: + case POSTFIX_INCREMENT: + case PREFIX_INCREMENT: + case BITWISE_COMPLEMENT: + return node.getExpression().accept(this, solveLambdas); + default: + throw new UnsupportedOperationException(node.getOperator().name()); + } + } + + @Override + public ResolvedType visit(VariableDeclarationExpr node, Boolean solveLambdas) { + if (node.getVariables().size() != 1) { + throw new UnsupportedOperationException(); + } + return facade.convertToUsageVariableType(node.getVariables().get(0)); + } + + + @Override + public ResolvedType visit(LambdaExpr node, Boolean solveLambdas) { + if (demandParentNode(node) instanceof MethodCallExpr) { + MethodCallExpr callExpr = (MethodCallExpr) demandParentNode(node); + int pos = JavaParserSymbolDeclaration.getParamPos(node); + SymbolReference refMethod = facade.solve(callExpr); + if (!refMethod.isSolved()) { + throw new UnsolvedSymbolException(demandParentNode(node).toString(), callExpr.getName().getId()); + } + Log.trace("getType on lambda expr %s", ()-> refMethod.getCorrespondingDeclaration().getName()); + if (solveLambdas) { + + // The type parameter referred here should be the java.util.stream.Stream.T + ResolvedType result = refMethod.getCorrespondingDeclaration().getParam(pos).getType(); + + if (callExpr.hasScope()) { + Expression scope = callExpr.getScope().get(); + + // If it is a static call we should not try to get the type of the scope + boolean staticCall = false; + if (scope instanceof NameExpr) { + NameExpr nameExpr = (NameExpr) scope; + try { + SymbolReference type = JavaParserFactory.getContext(nameExpr, typeSolver).solveType(nameExpr.getName().getId()); + if (type.isSolved()) { + staticCall = true; + } + } catch (Exception e) { + + } + } + + if (!staticCall) { + ResolvedType scopeType = facade.getType(scope); + if (scopeType.isReferenceType()) { + result = scopeType.asReferenceType().useThisTypeParametersOnTheGivenType(result); + } + } + } + + // We need to replace the type variables + Context ctx = JavaParserFactory.getContext(node, typeSolver); + result = solveGenericTypes(result, ctx); + + //We should find out which is the functional method (e.g., apply) and replace the params of the + //solveLambdas with it, to derive so the values. We should also consider the value returned by the + //lambdas + Optional functionalMethod = FunctionalInterfaceLogic.getFunctionalMethod(result); + if (functionalMethod.isPresent()) { + LambdaExpr lambdaExpr = node; + + InferenceContext lambdaCtx = new InferenceContext(MyObjectProvider.INSTANCE); + InferenceContext funcInterfaceCtx = new InferenceContext(MyObjectProvider.INSTANCE); + + // At this point parameterType + // if Function + // we should replace Stream.T + ResolvedType functionalInterfaceType = ReferenceTypeImpl.undeterminedParameters(functionalMethod.get().getDeclaration().declaringType(), typeSolver); + + lambdaCtx.addPair(result, functionalInterfaceType); + + ResolvedType actualType; + + if (lambdaExpr.getBody() instanceof ExpressionStmt) { + actualType = facade.getType(((ExpressionStmt) lambdaExpr.getBody()).getExpression()); + } else if (lambdaExpr.getBody() instanceof BlockStmt) { + BlockStmt blockStmt = (BlockStmt) lambdaExpr.getBody(); + + // Get all the return statements in the lambda block + List returnStmts = blockStmt.findAll(ReturnStmt.class); + + if (returnStmts.size() > 0) { + actualType = returnStmts.stream() + .map(returnStmt -> returnStmt.getExpression().map(e -> facade.getType(e)).orElse(ResolvedVoidType.INSTANCE)) + .filter(x -> x != null && !x.isVoid() && !x.isNull()) + .findFirst() + .orElse(ResolvedVoidType.INSTANCE); + + } else { + actualType = ResolvedVoidType.INSTANCE; + } + + + } else { + throw new UnsupportedOperationException(); + } + + ResolvedType formalType = functionalMethod.get().returnType(); + + // Infer the functional interfaces' return vs actual type + funcInterfaceCtx.addPair(formalType, actualType); + // Substitute to obtain a new type + ResolvedType functionalTypeWithReturn = funcInterfaceCtx.resolve(funcInterfaceCtx.addSingle(functionalInterfaceType)); + + // if the functional method returns void anyway + // we don't need to bother inferring types + if (!(formalType instanceof ResolvedVoidType)) { + lambdaCtx.addPair(result, functionalTypeWithReturn); + result = lambdaCtx.resolve(lambdaCtx.addSingle(result)); + } + } + + return result; + } else { + return refMethod.getCorrespondingDeclaration().getParam(pos).getType(); + } + } else { + throw new UnsupportedOperationException("The type of a lambda expr depends on the position and its return value"); + } + } + + @Override + public ResolvedType visit(MethodReferenceExpr node, Boolean solveLambdas) { + if (demandParentNode(node) instanceof MethodCallExpr) { + MethodCallExpr callExpr = (MethodCallExpr) demandParentNode(node); + int pos = JavaParserSymbolDeclaration.getParamPos(node); + SymbolReference refMethod = facade.solve(callExpr, false); + if (!refMethod.isSolved()) { + throw new UnsolvedSymbolException(demandParentNode(node).toString(), callExpr.getName().getId()); + } + Log.trace("getType on method reference expr %s", ()-> refMethod.getCorrespondingDeclaration().getName()); + if (solveLambdas) { + MethodUsage usage = facade.solveMethodAsUsage(callExpr); + ResolvedType result = usage.getParamType(pos); + // We need to replace the type variables + Context ctx = JavaParserFactory.getContext(node, typeSolver); + result = solveGenericTypes(result, ctx); + + //We should find out which is the functional method (e.g., apply) and replace the params of the + //solveLambdas with it, to derive so the values. We should also consider the value returned by the + //lambdas + Optional functionalMethodOpt = FunctionalInterfaceLogic.getFunctionalMethod(result); + if (functionalMethodOpt.isPresent()) { + MethodUsage functionalMethod = functionalMethodOpt.get(); + + for (Pair typeParamDecl : result.asReferenceType().getTypeParametersMap()) { + functionalMethod = functionalMethod.replaceTypeParameter(typeParamDecl.a, typeParamDecl.b); + } + + // replace wildcards + for (int i = 0; i < functionalMethod.getNoParams(); i++) { + ResolvedType type = functionalMethod.getParamType(i); + if (type.isWildcard()) { + ResolvedType boundedType = type.asWildcard().getBoundedType(); + functionalMethod = functionalMethod.replaceParamType(i, boundedType); + } + } + + ResolvedType actualType = facade.toMethodUsage(node, functionalMethod.getParamTypes()).returnType(); + ResolvedType formalType = functionalMethod.returnType(); + + InferenceContext inferenceContext = new InferenceContext(MyObjectProvider.INSTANCE); + inferenceContext.addPair(formalType, actualType); + result = inferenceContext.resolve(inferenceContext.addSingle(result)); + } + + return result; + } + return refMethod.getCorrespondingDeclaration().getParam(pos).getType(); + } + throw new UnsupportedOperationException("The type of a method reference expr depends on the position and its return value"); + } + + @Override + public ResolvedType visit(FieldDeclaration node, Boolean solveLambdas) { + if (node.getVariables().size() == 1) { + return node.getVariables().get(0).accept(this, solveLambdas); + } + throw new IllegalArgumentException("Cannot resolve the type of a field with multiple variable declarations. Pick one"); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/AbstractJavaParserContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/AbstractJavaParserContext.java new file mode 100644 index 0000000000000000000000000000000000000000..b71ea5c13f5cc501fba763baaa378d99247ebb1f --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/AbstractJavaParserContext.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.expr.*; +import com.github.javaparser.ast.nodeTypes.NodeWithOptionalScope; +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFactory; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserPatternDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserSymbolDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.resolution.Value; +import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionClassDeclaration; +import com.github.javaparser.symbolsolver.resolution.SymbolDeclarator; + +import java.util.*; + +import static com.github.javaparser.symbolsolver.javaparser.Navigator.demandParentNode; +import static java.util.Collections.singletonList; + +/** + * @author Federico Tomassetti + */ +public abstract class AbstractJavaParserContext implements Context { + + protected N wrappedNode; + protected TypeSolver typeSolver; + + /// + /// Static methods + /// + + protected static boolean isQualifiedName(String name) { + return name.contains("."); + } + + public static SymbolReference solveWith(SymbolDeclarator symbolDeclarator, String name) { + for (ResolvedValueDeclaration decl : symbolDeclarator.getSymbolDeclarations()) { + if (decl.getName().equals(name)) { + return SymbolReference.solved(decl); + } + } + return SymbolReference.unsolved(ResolvedValueDeclaration.class); + } + + /// + /// Constructors + /// + + public AbstractJavaParserContext(N wrappedNode, TypeSolver typeSolver) { + if (wrappedNode == null) { + throw new NullPointerException(); + } + this.wrappedNode = wrappedNode; + this.typeSolver = typeSolver; + } + + /// + /// Public methods + /// + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + AbstractJavaParserContext that = (AbstractJavaParserContext) o; + + return wrappedNode != null ? wrappedNode.equals(that.wrappedNode) : that.wrappedNode == null; + } + + @Override + public int hashCode() { + return wrappedNode == null ? 0 : wrappedNode.hashCode(); + } + + @Override + public final Optional getParent() { + Node parentNode = wrappedNode.getParentNode().orElse(null); + + // TODO/FiXME: Document why the method call expression is treated differently. + if (parentNode instanceof MethodCallExpr) { + MethodCallExpr parentCall = (MethodCallExpr) parentNode; + // TODO: Can this be replaced with: boolean found = parentCall.getArguments().contains(wrappedNode); + boolean found = false; + for (Expression expression : parentCall.getArguments()) { + if (expression == wrappedNode) { + found = true; + break; + } + } + if (found) { + Node notMethod = parentNode; + while (notMethod instanceof MethodCallExpr) { + notMethod = demandParentNode(notMethod); + } + return Optional.of(JavaParserFactory.getContext(notMethod, typeSolver)); + } + } + Node notMethodNode = parentNode; + // to avoid an infinite loop if parent scope is the same as wrapped node + while (notMethodNode instanceof MethodCallExpr || notMethodNode instanceof FieldAccessExpr + || (notMethodNode != null && notMethodNode.hasScope() && getScope(notMethodNode).equals(wrappedNode)) ) { + notMethodNode = notMethodNode.getParentNode().orElse(null); + } + if (notMethodNode == null) { + return Optional.empty(); + } + Context parentContext = JavaParserFactory.getContext(notMethodNode, typeSolver); + return Optional.of(parentContext); + } + + // before to call this method verify the node has a scope + protected Node getScope(Node node) { + return (Node) ((NodeWithOptionalScope)node).getScope().get(); + } + + + @Override + public SymbolReference solveSymbolInParentContext(String name) { + Optional optionalParentContext = getParent(); + if (!optionalParentContext.isPresent()) { + return SymbolReference.unsolved(ResolvedValueDeclaration.class); + } + + // First check if there are any pattern expressions available to this node. + Context parentContext = optionalParentContext.get(); + if(parentContext instanceof BinaryExprContext || parentContext instanceof IfStatementContext) { + List patternExprs = parentContext.patternExprsExposedToChild(this.getWrappedNode()); + + Optional localResolutionResults = patternExprs + .stream() + .filter(vd -> vd.getNameAsString().equals(name)) + .findFirst(); + + if (localResolutionResults.isPresent()) { + if(patternExprs.size() == 1) { + JavaParserPatternDeclaration decl = JavaParserSymbolDeclaration.patternVar(localResolutionResults.get(), typeSolver); + return SymbolReference.solved(decl); + } else if(patternExprs.size() > 1) { + throw new IllegalStateException("Unexpectedly more than one reference in scope"); + } + } + } + + // Delegate solving to the parent context. + return parentContext.solveSymbol(name); + } + + /// + /// Protected methods + /// + + protected Optional solveWithAsValue(SymbolDeclarator symbolDeclarator, String name) { + return symbolDeclarator.getSymbolDeclarations().stream() + .filter(d -> d.getName().equals(name)) + .map(Value::from) + .findFirst(); + } + + protected Collection findTypeDeclarations(Optional optScope) { + if (optScope.isPresent()) { + Expression scope = optScope.get(); + + // consider static methods + if (scope instanceof NameExpr) { + NameExpr scopeAsName = scope.asNameExpr(); + SymbolReference symbolReference = this.solveType(scopeAsName.getName().getId()); + if (symbolReference.isSolved() && symbolReference.getCorrespondingDeclaration().isType()) { + return singletonList(symbolReference.getCorrespondingDeclaration().asReferenceType()); + } + } + + ResolvedType typeOfScope; + try { + typeOfScope = JavaParserFacade.get(typeSolver).getType(scope); + } catch (Exception e) { + // If the scope corresponds to a type we should treat it differently + if (scope instanceof FieldAccessExpr) { + FieldAccessExpr scopeName = (FieldAccessExpr) scope; + if (this.solveType(scopeName.toString()).isSolved()) { + return Collections.emptyList(); + } + } + throw new UnsolvedSymbolException(scope.toString(), wrappedNode.toString(), e); + } + if (typeOfScope.isWildcard()) { + if (typeOfScope.asWildcard().isExtends() || typeOfScope.asWildcard().isSuper()) { + // TODO: Figure out if it is appropriate to remove the orElseThrow() -- if so, how... + return singletonList( + typeOfScope.asWildcard() + .getBoundedType() + .asReferenceType() + .getTypeDeclaration() + .orElseThrow(() -> new RuntimeException("TypeDeclaration unexpectedly empty.")) + ); + } else { + return singletonList(new ReflectionClassDeclaration(Object.class, typeSolver).asReferenceType()); + } + } else if (typeOfScope.isArray()) { + // method call on array are Object methods + return singletonList(new ReflectionClassDeclaration(Object.class, typeSolver).asReferenceType()); + } else if (typeOfScope.isTypeVariable()) { + Collection result = new ArrayList<>(); + for (ResolvedTypeParameterDeclaration.Bound bound : typeOfScope.asTypeParameter().getBounds()) { + // TODO: Figure out if it is appropriate to remove the orElseThrow() -- if so, how... + result.add( + bound.getType() + .asReferenceType() + .getTypeDeclaration() + .orElseThrow(() -> new RuntimeException("TypeDeclaration unexpectedly empty.")) + ); + } + return result; + } else if (typeOfScope.isConstraint()) { + // TODO: Figure out if it is appropriate to remove the orElseThrow() -- if so, how... + return singletonList( + typeOfScope.asConstraintType() + .getBound() + .asReferenceType() + .getTypeDeclaration() + .orElseThrow(() -> new RuntimeException("TypeDeclaration unexpectedly empty.")) + ); + } else if (typeOfScope.isUnionType()) { + return typeOfScope.asUnionType().getCommonAncestor() + .flatMap(ResolvedReferenceType::getTypeDeclaration) + .map(Collections::singletonList) + .orElseThrow(() -> new UnsolvedSymbolException("No common ancestor available for UnionType" + typeOfScope.describe())); + } + + // TODO: Figure out if it is appropriate to remove the orElseThrow() -- if so, how... + return singletonList( + typeOfScope.asReferenceType() + .getTypeDeclaration() + .orElseThrow(() -> new RuntimeException("TypeDeclaration unexpectedly empty.")) + ); + } + + ResolvedType typeOfScope = JavaParserFacade.get(typeSolver).getTypeOfThisIn(wrappedNode); + + // TODO: Figure out if it is appropriate to remove the orElseThrow() -- if so, how... + return singletonList( + typeOfScope.asReferenceType() + .getTypeDeclaration() + .orElseThrow(() -> new RuntimeException("TypeDeclaration unexpectedly empty.")) + ); + } + + public N getWrappedNode() { + return wrappedNode; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/AbstractMethodLikeDeclarationContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/AbstractMethodLikeDeclarationContext.java new file mode 100644 index 0000000000000000000000000000000000000000..6a3b8af92f3525f8603bb25fccae92a6631a21ce --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/AbstractMethodLikeDeclarationContext.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.Parameter; +import com.github.javaparser.ast.body.TypeDeclaration; +import com.github.javaparser.ast.nodeTypes.NodeWithParameters; +import com.github.javaparser.ast.nodeTypes.NodeWithTypeParameters; +import com.github.javaparser.ast.type.TypeParameter; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.resolution.types.ResolvedTypeVariable; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFactory; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserTypeParameter; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.resolution.Value; +import com.github.javaparser.symbolsolver.resolution.SymbolDeclarator; + +import java.util.List; +import java.util.Optional; + +/** + * @author Federico Tomassetti + */ +public abstract class AbstractMethodLikeDeclarationContext + & NodeWithTypeParameters> extends AbstractJavaParserContext { + + public AbstractMethodLikeDeclarationContext(T wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + @Override + public final SymbolReference solveSymbol(String name) { + for (Parameter parameter : wrappedNode.getParameters()) { + SymbolDeclarator sb = JavaParserFactory.getSymbolDeclarator(parameter, typeSolver); + SymbolReference symbolReference = AbstractJavaParserContext.solveWith(sb, name); + if (symbolReference.isSolved()) { + return symbolReference; + } + } + + // if nothing is found we should ask the parent context + return solveSymbolInParentContext(name); + } + + @Override + public final Optional solveGenericType(String name) { + // First check if the method-like declaration has type parameters defined. + // For example: {@code public boolean containsAll(Collection c);} + for (TypeParameter tp : wrappedNode.getTypeParameters()) { + if (tp.getName().getId().equals(name)) { + return Optional.of(new ResolvedTypeVariable(new JavaParserTypeParameter(tp, typeSolver))); + } + } + + // If no generic types on the method declaration, continue to solve elsewhere as usual. + return solveGenericTypeInParentContext(name); + } + + @Override + public final Optional solveSymbolAsValue(String name) { + for (Parameter parameter : wrappedNode.getParameters()) { + SymbolDeclarator sb = JavaParserFactory.getSymbolDeclarator(parameter, typeSolver); + Optional symbolReference = solveWithAsValue(sb, name); + if (symbolReference.isPresent()) { + // Perform parameter type substitution as needed + return symbolReference; + } + } + + // if nothing is found we should ask the parent context + return solveSymbolAsValueInParentContext(name); + } + + @Override + public final SymbolReference solveType(String name) { + // TODO: Is null check required? + if (wrappedNode.getTypeParameters() != null) { + for (TypeParameter tp : wrappedNode.getTypeParameters()) { + if (tp.getName().getId().equals(name)) { + return SymbolReference.solved(new JavaParserTypeParameter(tp, typeSolver)); + } + } + } + + // Local types + List localTypes = wrappedNode.findAll(TypeDeclaration.class); + for (TypeDeclaration localType : localTypes) { + if (localType.getName().getId().equals(name)) { + return SymbolReference.solved(JavaParserFacade.get(typeSolver) + .getTypeDeclaration(localType)); + } else if (name.startsWith(String.format("%s.", localType.getName()))) { + return JavaParserFactory.getContext(localType, typeSolver) + .solveType(name.substring(localType.getName().getId().length() + 1)); + } + } + + return solveTypeInParentContext(name); + } + + @Override + public final SymbolReference solveMethod(String name, List argumentsTypes, boolean staticOnly) { + // TODO: Document why staticOnly is forced to be false. + return solveMethodInParentContext(name, argumentsTypes, false); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/AnnotationDeclarationContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/AnnotationDeclarationContext.java new file mode 100644 index 0000000000000000000000000000000000000000..d189976790d0a6da121aadbf9999f113fb9c6c8e --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/AnnotationDeclarationContext.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.body.AnnotationDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserAnnotationDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.List; + +/** + * @author Takeshi D. Itoh + */ +public class AnnotationDeclarationContext extends AbstractJavaParserContext { + + private JavaParserTypeDeclarationAdapter javaParserTypeDeclarationAdapter; + + public AnnotationDeclarationContext(AnnotationDeclaration wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + this.javaParserTypeDeclarationAdapter = new JavaParserTypeDeclarationAdapter(wrappedNode, typeSolver, + getDeclaration(), this); + } + + @Override + public SymbolReference solveSymbol(String name) { + if (typeSolver == null) throw new IllegalArgumentException(); + + if (this.getDeclaration().hasField(name)) { + return SymbolReference.solved(this.getDeclaration().getField(name)); + } + + // then to parent + return solveSymbolInParentContext(name); + } + + @Override + public SymbolReference solveType(String name) { + return javaParserTypeDeclarationAdapter.solveType(name); + } + + @Override + public SymbolReference solveMethod(String name, List argumentsTypes, boolean staticOnly) { + return javaParserTypeDeclarationAdapter.solveMethod(name, argumentsTypes, staticOnly); + } + + /// + /// Private methods + /// + + private ResolvedReferenceTypeDeclaration getDeclaration() { + return new JavaParserAnnotationDeclaration(this.wrappedNode, typeSolver); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/AnonymousClassDeclarationContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/AnonymousClassDeclarationContext.java new file mode 100644 index 0000000000000000000000000000000000000000..ca4ea87c768201302ad6c0e964b95115adf23868 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/AnonymousClassDeclarationContext.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.body.TypeDeclaration; +import com.github.javaparser.ast.expr.ObjectCreationExpr; +import com.github.javaparser.ast.nodeTypes.NodeWithTypeArguments; +import com.github.javaparser.ast.type.TypeParameter; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFactory; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserAnonymousClassDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserTypeParameter; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionClassDeclaration; +import com.github.javaparser.symbolsolver.resolution.MethodResolutionLogic; +import com.google.common.base.Preconditions; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * A symbol resolution context for an object creation node. + */ +public class AnonymousClassDeclarationContext extends AbstractJavaParserContext { + + private final JavaParserAnonymousClassDeclaration myDeclaration = + new JavaParserAnonymousClassDeclaration(wrappedNode, typeSolver); + + public AnonymousClassDeclarationContext(ObjectCreationExpr node, TypeSolver typeSolver) { + super(node, typeSolver); + Preconditions.checkArgument(node.getAnonymousClassBody().isPresent(), + "An anonymous class must have a body"); + } + + @Override + public SymbolReference solveMethod(String name, + List argumentsTypes, + boolean staticOnly) { + List candidateMethods = myDeclaration + .getDeclaredMethods() + .stream() + .filter(m -> m.getName().equals(name) && (!staticOnly || m.isStatic())) + .collect(Collectors.toList()); + + if (!myDeclaration.isJavaLangObject()) { + for (ResolvedReferenceType ancestor : myDeclaration.getAncestors()) { + ancestor.getTypeDeclaration().ifPresent(ancestorTypeDeclaration -> { + SymbolReference res = MethodResolutionLogic.solveMethodInType( + ancestorTypeDeclaration, + name, + argumentsTypes, + staticOnly + ); + + // consider methods from superclasses and only default methods from interfaces : + // not true, we should keep abstract as a valid candidate + // abstract are removed in MethodResolutionLogic.isApplicable is necessary + if (res.isSolved()) { + candidateMethods.add(res.getCorrespondingDeclaration()); + } + }); + } + } + + // We want to avoid infinite recursion when a class is using its own method + // see issue #75 + if (candidateMethods.isEmpty()) { + SymbolReference parentSolution = + getParent() + .orElseThrow(() -> new RuntimeException("Parent context unexpectedly empty.")) + .solveMethod(name, argumentsTypes, staticOnly); + if (parentSolution.isSolved()) { + candidateMethods.add(parentSolution.getCorrespondingDeclaration()); + } + } + + // if is interface and candidate method list is empty, we should check the Object Methods + if (candidateMethods.isEmpty() && myDeclaration.getSuperTypeDeclaration().isInterface()) { + SymbolReference res = + MethodResolutionLogic.solveMethodInType(new ReflectionClassDeclaration(Object.class, + typeSolver), + name, + argumentsTypes, + false); + if (res.isSolved()) { + candidateMethods.add(res.getCorrespondingDeclaration()); + } + } + + return MethodResolutionLogic.findMostApplicable(candidateMethods, + name, + argumentsTypes, + typeSolver); + } + + @Override + public SymbolReference solveType(String name) { + List typeDeclarations = myDeclaration.findMembersOfKind(TypeDeclaration.class); + + Optional> exactMatch = + typeDeclarations + .stream() + .filter(internalType -> internalType.getName().getId().equals(name)) + .findFirst() + .map(internalType -> + SymbolReference.solved( + JavaParserFacade.get(typeSolver).getTypeDeclaration(internalType))); + + if(exactMatch.isPresent()){ + return exactMatch.get(); + } + + Optional> recursiveMatch = + typeDeclarations + .stream() + .filter(internalType -> name.startsWith(String.format("%s.", internalType.getName()))) + .findFirst() + .map(internalType -> + JavaParserFactory + .getContext(internalType, typeSolver) + .solveType(name.substring(internalType.getName().getId().length() + 1))); + + if (recursiveMatch.isPresent()) { + return recursiveMatch.get(); + } + + Optional> typeArgumentsMatch = + wrappedNode + .getTypeArguments() + .map(nodes -> + ((NodeWithTypeArguments) nodes).getTypeArguments() + .orElse(new NodeList<>())) + .orElse(new NodeList<>()) + .stream() + .filter(type -> type.toString().equals(name)) + .findFirst() + .map(matchingType -> + SymbolReference.solved( + new JavaParserTypeParameter(new TypeParameter(matchingType.toString()), + typeSolver))); + + if (typeArgumentsMatch.isPresent()) { + return typeArgumentsMatch.get(); + } + + // Look into extended classes and implemented interfaces + for (ResolvedReferenceType ancestor : myDeclaration.getAncestors()) { + // look at names of extended classes and implemented interfaces (this may not be important because they are checked in CompilationUnitContext) + Optional optionalTypeDeclaration = ancestor.getTypeDeclaration(); + if (optionalTypeDeclaration.isPresent()) { + ResolvedReferenceTypeDeclaration typeDeclaration = optionalTypeDeclaration.get(); + if (typeDeclaration.getName().equals(name)) { + return SymbolReference.solved(typeDeclaration); + } + // look into internal types of extended classes and implemented interfaces + try { + for (ResolvedTypeDeclaration internalTypeDeclaration : typeDeclaration.internalTypes()) { + if (internalTypeDeclaration.getName().equals(name)) { + return SymbolReference.solved(internalTypeDeclaration); + } + } + } catch (UnsupportedOperationException e) { + // just continue using the next ancestor + } + } + } + + return solveTypeInParentContext(name); + } + + @Override + public SymbolReference solveSymbol(String name) { + Preconditions.checkArgument(typeSolver != null); + + if (myDeclaration.hasField(name)) { + return SymbolReference.solved(myDeclaration.getField(name)); + } + + return solveSymbolInParentContext(name); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ArrayAccessExprContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ArrayAccessExprContext.java new file mode 100644 index 0000000000000000000000000000000000000000..400b89ed4d648c7fac99b2fc2c35c51aab9fbf29 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ArrayAccessExprContext.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.expr.ArrayAccessExpr; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +/** + *

+ * Required to prevent recursive access to the "parent node" (not necessarily the same as the "parent context"). + *

+ * Consider, for example, this code where the cursor is currently at the node of type {@code ArrayAccessExpr}: + *

+ *
{@code
+ *     var1.perPriority[index].recovered
+ *     ^^^^^^^^^^^^^^^^^^^^^^^             - ArrayAccessExpr
+ * }
+ * + *

The AST for this snippet:

+ * + *
{@code
+ *                            FieldAccessExpr                       // This FieldAccessExpr is accessing the field `recovered`
+ *                             /           \
+ *               **ArrayAccessExpr**      SimpleName(recovered)
+ *                  /          \
+ *          FieldAccessExpr  NameExpr(index)                        // This FieldAccessExpr is accessing the field `perPriority`
+ *            /         \
+ *    NameExpr(var1)   SimpleName (perPriority)
+ * }
+ * + *

In this example:

+ *
    + *
  • + * The parent node for {@code ArrayAccessExpr} is {@code FieldAccessExpr} ({@code variable1.perPriority[index].recovered}). + *
    {@code
    + *     // "Parent Node" of the ArrayAccessExpr
    + *     var.perPriority[index].recovered
    + *     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^   - FieldAccessExpr
    + *     ^^^^^^^^^^^^^^^^^^^^^^             - ArrayAccessExpr
    + *                            ^^^^^^^^^   - SimpleName
    + * }
    + *
  • + *
  • + * The parent context is the {@code FieldAccessExpr} to the left of the outer array-access, which is actually a child node. + *
    {@code
    + *
    + *     // "Parent Context" of the ArrayAccessExpr
    + *     var1.perPriority[index].recovered
    + *     ^^^^^^^^^^^^^^^^^^^^^^^             - ArrayAccessExpr
    + *     ^^^^^^^^^^^^^^^^                    - FieldAccessExpr
    + *                      ^^^^^              - NameExpr
    + * }
    + *
  • + *
+ * + * + * + * + * @author Roger Howell + */ +public class ArrayAccessExprContext extends AbstractJavaParserContext { + + public ArrayAccessExprContext(ArrayAccessExpr wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + public SymbolReference solveSymbolInParentContext(String name) { + /* + * Simple implementation, included explicitly here for clarity: + * - Delegate to parent context per the documentation for ArrayAccessExprContext + * - Required to prevent recursive access to the "parent node" (not necessarily the same as the "parent context") + */ + return super.solveSymbolInParentContext(name); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/BinaryExprContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/BinaryExprContext.java new file mode 100644 index 0000000000000000000000000000000000000000..4f7e1e3398b196218f7fac0bf2becbbf2579b3b4 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/BinaryExprContext.java @@ -0,0 +1,228 @@ +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.expr.BinaryExpr; +import com.github.javaparser.ast.expr.Expression; +import com.github.javaparser.ast.expr.PatternExpr; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFactory; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class BinaryExprContext extends AbstractJavaParserContext { + + public BinaryExprContext(BinaryExpr wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + @Override + public List patternExprsExposedFromChildren() { + + BinaryExpr binaryExpr = wrappedNode; + Expression leftBranch = binaryExpr.getLeft(); + Expression rightBranch = binaryExpr.getRight(); + + List results = new ArrayList<>(); + + if (binaryExpr.getOperator().equals(BinaryExpr.Operator.EQUALS)) { + if (rightBranch.isBooleanLiteralExpr()) { + if (rightBranch.asBooleanLiteralExpr().getValue() == true) { + // "x" instanceof String s == true + results.addAll(patternExprsExposedToDirectParentFromBranch(leftBranch)); + } else { + // "x" instanceof String s == false + } + } else if (leftBranch.isBooleanLiteralExpr()) { + if (leftBranch.asBooleanLiteralExpr().getValue() == true) { + // true == "x" instanceof String s + results.addAll(patternExprsExposedToDirectParentFromBranch(rightBranch)); + } else { + // false == "x" instanceof String s + } + } + } else if (binaryExpr.getOperator().equals(BinaryExpr.Operator.NOT_EQUALS)) { + if (rightBranch.isBooleanLiteralExpr()) { + if (rightBranch.asBooleanLiteralExpr().getValue() == true) { + // "x" instanceof String s != true + } else { + // "x" instanceof String s != false + results.addAll(patternExprsExposedToDirectParentFromBranch(leftBranch)); + } + } else if (leftBranch.isBooleanLiteralExpr()) { + if (leftBranch.asBooleanLiteralExpr().getValue() == true) { + // true != "x" instanceof String s + } else { + // false != "x" instanceof String s + results.addAll(patternExprsExposedToDirectParentFromBranch(rightBranch)); + } + } + + // TODO/FIXME: There are other cases where it may be ambiguously true until runtime e.g. `"x" instanceof String s == (new Random().nextBoolean())` + + } else if (binaryExpr.getOperator().equals(BinaryExpr.Operator.AND)) { + // "x" instanceof String s && s.length() > 0 + // "x" instanceof String s && "x" instanceof String s2 + results.addAll(patternExprsExposedToDirectParentFromBranch(leftBranch)); + results.addAll(patternExprsExposedToDirectParentFromBranch(rightBranch)); + } else { + return new ArrayList<>(); + } + + return results; + } + + @Override + public List negatedPatternExprsExposedFromChildren() { + + BinaryExpr binaryExpr = wrappedNode; + Expression leftBranch = binaryExpr.getLeft(); + Expression rightBranch = binaryExpr.getRight(); + + List results = new ArrayList<>(); + + // FIXME: Redo the `.getValue() == true` to take more complex code into account when determining if definitively true (e.g. ` + if (binaryExpr.getOperator().equals(BinaryExpr.Operator.EQUALS)) { + if (rightBranch.isBooleanLiteralExpr()) { + if (isDefinitivelyTrue(rightBranch)) { + // "x" instanceof String s == true + // "x" instanceof String s == !(false) + // No negations. + } else { + // "x" instanceof String s == false + // "x" instanceof String s == !(true) + results.addAll(patternExprsExposedToDirectParentFromBranch(leftBranch)); + } + } else if (leftBranch.isBooleanLiteralExpr()) { + if (isDefinitivelyTrue(leftBranch)) { + // true == "x" instanceof String s + // !(false) == "x" instanceof String s + // No negations. + } else { + // false == "x" instanceof String s + // !(true) == "x" instanceof String s + results.addAll(patternExprsExposedToDirectParentFromBranch(rightBranch)); + } + } + } else if (binaryExpr.getOperator().equals(BinaryExpr.Operator.NOT_EQUALS)) { + if (rightBranch.isBooleanLiteralExpr()) { + if (isDefinitivelyTrue(rightBranch)) { + // "x" instanceof String s != true + // "x" instanceof String s != !(false) + results.addAll(patternExprsExposedToDirectParentFromBranch(leftBranch)); + } else { + // "x" instanceof String s != false + // "x" instanceof String s != !(true) + } + } else if (leftBranch.isBooleanLiteralExpr()) { + if (isDefinitivelyTrue(leftBranch)) { + // true != "x" instanceof String s + // !(false) != "x" instanceof String s + results.addAll(patternExprsExposedToDirectParentFromBranch(rightBranch)); + } else { + // false != "x" instanceof String s + // !(true) != "x" instanceof String s + } + } + + // TODO/FIXME: There are other cases where it may be ambiguously true until runtime e.g. `"x" instanceof String s == (new Random().nextBoolean())` + + } else if (binaryExpr.getOperator().equals(BinaryExpr.Operator.AND)) { + // "x" instanceof String s && s.length() > 0 + // "x" instanceof String s && "x" instanceof String s2 + results.addAll(negatedPatternExprsExposedToDirectParentFromBranch(leftBranch)); + results.addAll(negatedPatternExprsExposedToDirectParentFromBranch(rightBranch)); + } else { + return new ArrayList<>(); + } + + return results; + } + + private List patternExprsExposedToDirectParentFromBranch(Expression branch) { + if (branch.isEnclosedExpr() || branch.isBinaryExpr() || branch.isUnaryExpr() || branch.isInstanceOfExpr()) { + Context branchContext = JavaParserFactory.getContext(branch, typeSolver); + return branchContext.patternExprsExposedFromChildren(); + } + + return new ArrayList<>(); + } + + private List negatedPatternExprsExposedToDirectParentFromBranch(Expression branch) { + if (branch.isEnclosedExpr() || branch.isBinaryExpr() || branch.isUnaryExpr() || branch.isInstanceOfExpr()) { + Context branchContext = JavaParserFactory.getContext(branch, typeSolver); + return branchContext.negatedPatternExprsExposedFromChildren(); + } + + return new ArrayList<>(); + } + + public List patternExprsExposedToChild(Node child) { + BinaryExpr binaryExpr = wrappedNode; + Expression leftBranch = binaryExpr.getLeft(); + Expression rightBranch = binaryExpr.getRight(); + + List results = new ArrayList<>(); + if (child == leftBranch) { + results.addAll(patternExprsExposedToDirectParentFromBranch(leftBranch)); + } else if (child == rightBranch) { + if (binaryExpr.getOperator().equals(BinaryExpr.Operator.AND)) { + // "" instanceof String s && "" instanceof String s2 + results.addAll(patternExprsExposedToDirectParentFromBranch(leftBranch)); + } + } +// else if (binaryExpr.getOperator().equals(BinaryExpr.Operator.AND) && rightBranch.isAncestorOf(child)) { +// // "" instanceof String s && "" instanceof String s2 +// results.addAll(patternExprsExposedToDirectParentFromBranch(leftBranch)); +// } + + return results; + } + + + public Optional patternExprInScope(String name) { + BinaryExpr binaryExpr = wrappedNode; + Expression leftBranch = binaryExpr.getLeft(); + Expression rightBranch = binaryExpr.getRight(); + + List patternExprs = patternExprsExposedToDirectParentFromBranch(leftBranch); + Optional localResolutionResults = patternExprs + .stream() + .filter(vd -> vd.getNameAsString().equals(name)) + .findFirst(); + + if (localResolutionResults.isPresent()) { + return localResolutionResults; + } + + + // If we don't find the parameter locally, escalate up the scope hierarchy to see if it is declared there. + if (!getParent().isPresent()) { + return Optional.empty(); + } + Context parentContext = getParent().get(); + return parentContext.patternExprInScope(name); + } + + private boolean isDefinitivelyTrue(Expression expression) { + // TODO: Consider combinations of literal true/false, enclosed expressions, and negations. + if (expression.isBooleanLiteralExpr()) { + if (expression.asBooleanLiteralExpr().getValue() == true) { + return true; + } + } + return false; + } + + private boolean isDefinitivelyFalse(Expression expression) { + // TODO: Consider combinations of literal true/false, enclosed expressions, and negations. + if (expression.isBooleanLiteralExpr()) { + if (expression.asBooleanLiteralExpr().getValue() == false) { + return true; + } + } + return false; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/BlockStmtContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/BlockStmtContext.java new file mode 100644 index 0000000000000000000000000000000000000000..ccaed5ae609589144aec20417d1f5fb2d99d4906 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/BlockStmtContext.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.VariableDeclarator; +import com.github.javaparser.ast.expr.VariableDeclarationExpr; +import com.github.javaparser.ast.stmt.BlockStmt; +import com.github.javaparser.ast.stmt.ExpressionStmt; +import com.github.javaparser.ast.stmt.Statement; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserSymbolDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; + +public class BlockStmtContext extends AbstractJavaParserContext { + + public BlockStmtContext(BlockStmt wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + @Override + public List localVariablesExposedToChild(Node child) { + int position = wrappedNode.getStatements().indexOf(child); + if (position == -1) { + throw new RuntimeException(); + } + List variableDeclarators = new LinkedList<>(); + for (int i = position - 1; i >= 0; i--) { + variableDeclarators.addAll(localVariablesDeclaredIn(wrappedNode.getStatement(i))); + } + return variableDeclarators; + } + + private List localVariablesDeclaredIn(Statement statement) { + if (statement instanceof ExpressionStmt) { + ExpressionStmt expressionStmt = (ExpressionStmt) statement; + if (expressionStmt.getExpression() instanceof VariableDeclarationExpr) { + VariableDeclarationExpr variableDeclarationExpr = (VariableDeclarationExpr) expressionStmt.getExpression(); + List variableDeclarators = new LinkedList<>(); + variableDeclarators.addAll(variableDeclarationExpr.getVariables()); + return variableDeclarators; + } + } + return Collections.emptyList(); + } + + @Override + public SymbolReference solveSymbol(String name) { + Optional optionalParent = getParent(); + if (!optionalParent.isPresent()) { + return SymbolReference.unsolved(ResolvedValueDeclaration.class); + } + + if (wrappedNode.getStatements().size() > 0) { + // tries to resolve a declaration from local variables defined in child statements + // or from parent node context + // for example resolve declaration for the MethodCallExpr a.method() in + // A a = this; + // { + // a.method(); + // } + + List variableDeclarators = new LinkedList<>(); + // find all variable declarators exposed to child + // given that we don't know the statement we are trying to resolve, we look for all variable declarations + // defined in the context of the wrapped node whether it is located before or after the statement that interests us + // because a variable cannot be (re)defined after having been used + wrappedNode.getStatements().getLast().ifPresent(stmt -> variableDeclarators.addAll(localVariablesExposedToChild(stmt))); + if (!variableDeclarators.isEmpty()) { + // FIXME: Work backwards from the current statement, to only consider declarations prior to this statement. + for (VariableDeclarator vd : variableDeclarators) { + if (vd.getNameAsString().equals(name)) { + return SymbolReference.solved(JavaParserSymbolDeclaration.localVar(vd, typeSolver)); + } + } + } + } + + // Otherwise continue as normal... + return solveSymbolInParentContext(name); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/CatchClauseContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/CatchClauseContext.java new file mode 100644 index 0000000000000000000000000000000000000000..f909267c0cc1513b7d8e4df103e4692f3ca194d7 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/CatchClauseContext.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.Parameter; +import com.github.javaparser.ast.body.VariableDeclarator; +import com.github.javaparser.ast.stmt.CatchClause; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +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.symbolsolver.model.resolution.Value; +import com.github.javaparser.symbolsolver.resolution.SymbolDeclarator; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * @author Fred Lefévère-Laoide + */ +public class CatchClauseContext extends AbstractJavaParserContext { + + public CatchClauseContext(CatchClause wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + @Override + public final SymbolReference solveSymbol(String name) { + SymbolDeclarator sb = JavaParserFactory.getSymbolDeclarator(wrappedNode.getParameter(), typeSolver); + SymbolReference symbolReference = AbstractJavaParserContext.solveWith(sb, name); + if (symbolReference.isSolved()) { + return symbolReference; + } + + // if nothing is found we should ask the parent context + return solveSymbolInParentContext(name); + } + + @Override + public final Optional solveSymbolAsValue(String name) { + SymbolDeclarator sb = JavaParserFactory.getSymbolDeclarator(wrappedNode.getParameter(), typeSolver); + Optional symbolReference = solveWithAsValue(sb, name); + if (symbolReference.isPresent()) { + // Perform parameter type substitution as needed + return symbolReference; + } + + // if nothing is found we should ask the parent context + return solveSymbolAsValueInParentContext(name); + } + + @Override + public final SymbolReference solveMethod(String name, List argumentsTypes, boolean staticOnly) { + // TODO: Document why staticOnly is forced to be false. + return solveMethodInParentContext(name, argumentsTypes, false); + } + + @Override + public List localVariablesExposedToChild(Node child) { + return Collections.emptyList(); + } + + @Override + public List parametersExposedToChild(Node child) { + // TODO/FIXME: Presumably the parameters must be exposed to all children and their descendants, not just the direct child? + if (child == getWrappedNode().getBody()) { + return Collections.singletonList(getWrappedNode().getParameter()); + } + return Collections.emptyList(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ClassOrInterfaceDeclarationContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ClassOrInterfaceDeclarationContext.java new file mode 100644 index 0000000000000000000000000000000000000000..308fb7b0defa3e4a5ded3c6cae946964cc5f83cf --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ClassOrInterfaceDeclarationContext.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.type.TypeParameter; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.resolution.types.ResolvedTypeVariable; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserTypeParameter; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.resolution.Value; + +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; + +/** + * @author Federico Tomassetti + */ +public class ClassOrInterfaceDeclarationContext extends AbstractJavaParserContext { + + private JavaParserTypeDeclarationAdapter javaParserTypeDeclarationAdapter; + + /// + /// Constructors + /// + + public ClassOrInterfaceDeclarationContext(ClassOrInterfaceDeclaration wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + this.javaParserTypeDeclarationAdapter = new JavaParserTypeDeclarationAdapter(wrappedNode, typeSolver, + getDeclaration(), this); + } + + /// + /// Public methods + /// + + @Override + public SymbolReference solveSymbol(String name) { + if (typeSolver == null) throw new IllegalArgumentException(); + + if (this.getDeclaration().hasVisibleField(name)) { + return SymbolReference.solved(this.getDeclaration().getVisibleField(name)); + } + + // then to parent + return solveSymbolInParentContext(name); + } + + @Override + public Optional solveSymbolAsValue(String name) { + if (typeSolver == null) throw new IllegalArgumentException(); + + if (this.getDeclaration().hasField(name)) { + return Optional.of(Value.from(this.getDeclaration().getField(name))); + } + + // then to parent + return solveSymbolAsValueInParentContext(name); + } + + @Override + public Optional solveGenericType(String name) { + // First check if the method-like declaration has type parameters defined. + // For example: {@code public boolean containsAll(Collection c);} + for (TypeParameter tp : wrappedNode.getTypeParameters()) { + if (tp.getName().getId().equals(name)) { + return Optional.of(new ResolvedTypeVariable(new JavaParserTypeParameter(tp, typeSolver))); + } + } + + // If no generic types on the method declaration, continue to solve as usual. + return solveGenericTypeInParentContext(name); + } + + @Override + public SymbolReference solveType(String name) { + return javaParserTypeDeclarationAdapter.solveType(name); + } + + @Override + public SymbolReference solveMethod(String name, List argumentsTypes, boolean staticOnly) { + return javaParserTypeDeclarationAdapter.solveMethod(name, argumentsTypes, staticOnly); + } + + public SymbolReference solveConstructor(List argumentsTypes) { + return javaParserTypeDeclarationAdapter.solveConstructor(argumentsTypes); + } + + @Override + public List fieldsExposedToChild(Node child) { + List fields = new LinkedList<>(); + fields.addAll(this.wrappedNode.resolve().getDeclaredFields()); + this.wrappedNode.getExtendedTypes().forEach(i -> fields.addAll(i.resolve().getAllFieldsVisibleToInheritors())); + this.wrappedNode.getImplementedTypes().forEach(i -> fields.addAll(i.resolve().getAllFieldsVisibleToInheritors())); + return fields; + } + + /// + /// Private methods + /// + + private ResolvedReferenceTypeDeclaration getDeclaration() { + return JavaParserFacade.get(typeSolver).getTypeDeclaration(this.wrappedNode); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ClassOrInterfaceDeclarationExtendsContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ClassOrInterfaceDeclarationExtendsContext.java new file mode 100644 index 0000000000000000000000000000000000000000..62b344c7c3e3b8c913834e004da9081c11272278 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ClassOrInterfaceDeclarationExtendsContext.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2007-2010 Júlio Vilmar Gesser. + * Copyright (C) 2011, 2013-2021 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.type.TypeParameter; +import com.github.javaparser.resolution.declarations.ResolvedTypeDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserTypeParameter; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +/** + * Limited version of ClassOrInterfaceDeclarationContext that only resolves type parameters for use by + * extends and implements part of declaration. + */ +public class ClassOrInterfaceDeclarationExtendsContext extends AbstractJavaParserContext { + public ClassOrInterfaceDeclarationExtendsContext(ClassOrInterfaceDeclaration wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + @Override + public SymbolReference solveType(String name) { + for (TypeParameter typeParameter : wrappedNode.getTypeParameters()) { + if (typeParameter.getName().getId().equals(name)) { + return SymbolReference.solved(new JavaParserTypeParameter(typeParameter, typeSolver)); + } + } + + return super.solveType(name); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/CompilationUnitContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/CompilationUnitContext.java new file mode 100644 index 0000000000000000000000000000000000000000..0eaf871934eee19bbca55f474452c94b44867eb8 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/CompilationUnitContext.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.ImportDeclaration; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.AnnotationDeclaration; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.EnumDeclaration; +import com.github.javaparser.ast.body.TypeDeclaration; +import com.github.javaparser.ast.expr.Name; +import com.github.javaparser.ast.type.ClassOrInterfaceType; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserAnnotationDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserClassDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserEnumDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserInterfaceDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.resolution.MethodResolutionLogic; +import com.github.javaparser.symbolsolver.resolution.SymbolSolver; + +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +public class CompilationUnitContext extends AbstractJavaParserContext { + + /// + /// Static methods + /// + + /// + /// Constructors + /// + + public CompilationUnitContext(CompilationUnit wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + /// + /// Public methods + /// + + @Override + public SymbolReference solveSymbol(String name) { + + // solve absolute references + String itName = name; + while (itName.contains(".")) { + String typeName = getType(itName); + String memberName = getMember(itName); + SymbolReference type = this.solveType(typeName); + if (type.isSolved()) { + return new SymbolSolver(typeSolver).solveSymbolInType(type.getCorrespondingDeclaration(), memberName); + } else { + itName = typeName; + } + } + + // Look among statically imported values + for (ImportDeclaration importDecl : wrappedNode.getImports()) { + if (importDecl.isStatic()) { + if (importDecl.isAsterisk()) { + String qName = importDecl.getNameAsString(); + ResolvedTypeDeclaration importedType = typeSolver.solveType(qName); + + // avoid infinite recursion + if (!isAncestorOf(importedType)) { + SymbolReference ref = new SymbolSolver(typeSolver).solveSymbolInType(importedType, name); + if (ref.isSolved()) { + return ref; + } + } + } else { + String whole = importDecl.getNameAsString(); + + // split in field/method name and type name + String memberName = getMember(whole); + String typeName = getType(whole); + + if (memberName.equals(name)) { + ResolvedTypeDeclaration importedType = typeSolver.solveType(typeName); + return new SymbolSolver(typeSolver).solveSymbolInType(importedType, memberName); + } + } + } + } + + return SymbolReference.unsolved(ResolvedValueDeclaration.class); + } + + @Override + public SymbolReference solveType(String name) { + + if (wrappedNode.getTypes() != null) { + // Look for types in this compilation unit. For instance, if the given name is "A", there may be a class or + // interface in this compilation unit called "A". + for (TypeDeclaration type : wrappedNode.getTypes()) { + if (type.getName().getId().equals(name) + || type.getFullyQualifiedName().map(qualified -> qualified.equals(name)).orElse(false)) { + if (type instanceof ClassOrInterfaceDeclaration) { + return SymbolReference.solved(JavaParserFacade.get(typeSolver).getTypeDeclaration((ClassOrInterfaceDeclaration) type)); + } else if (type instanceof AnnotationDeclaration) { + return SymbolReference.solved(new JavaParserAnnotationDeclaration((AnnotationDeclaration) type, typeSolver)); + } else if (type instanceof EnumDeclaration) { + return SymbolReference.solved(new JavaParserEnumDeclaration((EnumDeclaration) type, typeSolver)); + } else { + throw new UnsupportedOperationException(type.getClass().getCanonicalName()); + } + } + } + + // Look for member classes/interfaces of types in this compilation unit. For instance, if the given name is + // "A.B", there may be a class or interface in this compilation unit called "A" which has another member + // class or interface called "B". Since the type that we're looking for can be nested arbitrarily deeply + // ("A.B.C.D"), we look for the outermost type ("A" in the previous example) first, then recursively invoke + // this method for the remaining part of the given name. + if (name.indexOf('.') > -1) { + SymbolReference ref = null; + SymbolReference outerMostRef = + solveType(name.substring(0, name.indexOf("."))); + if (outerMostRef != null && outerMostRef.isSolved() && + outerMostRef.getCorrespondingDeclaration() instanceof JavaParserClassDeclaration) { + ref = ((JavaParserClassDeclaration) outerMostRef.getCorrespondingDeclaration()) + .solveType(name.substring(name.indexOf(".") + 1)); + } else if (outerMostRef != null && outerMostRef.isSolved() && + outerMostRef.getCorrespondingDeclaration() instanceof JavaParserInterfaceDeclaration) { + ref = ((JavaParserInterfaceDeclaration) outerMostRef.getCorrespondingDeclaration()) + .solveType(name.substring(name.indexOf(".") + 1)); + } + if (ref != null && ref.isSolved()) { + return ref; + } + } + } + + // Inspect imports for matches, prior to inspecting other classes within the package (per issue #1526) + int dotPos = name.indexOf('.'); + String prefix = null; + if (dotPos > -1) { + prefix = name.substring(0, dotPos); + } + // look into single type imports + for (ImportDeclaration importDecl : wrappedNode.getImports()) { + if (!importDecl.isAsterisk()) { + String qName = importDecl.getNameAsString(); + boolean defaultPackage = !importDecl.getName().getQualifier().isPresent(); + boolean found = !defaultPackage && importDecl.getName().getIdentifier().equals(name); + if (!found && prefix != null) { + found = qName.endsWith("." + prefix); + if (found) { + qName = qName + name.substring(dotPos); + } + } + if (found) { + SymbolReference ref = typeSolver.tryToSolveType(qName); + if (ref != null && ref.isSolved()) { + return SymbolReference.adapt(ref, ResolvedTypeDeclaration.class); + } + } + } + } + + // Look in current package + if (this.wrappedNode.getPackageDeclaration().isPresent()) { + String qName = this.wrappedNode.getPackageDeclaration().get().getName().toString() + "." + name; + SymbolReference ref = typeSolver.tryToSolveType(qName); + if (ref != null && ref.isSolved()) { + return SymbolReference.adapt(ref, ResolvedTypeDeclaration.class); + } + } else { + // look for classes in the default package + String qName = name; + SymbolReference ref = typeSolver.tryToSolveType(qName); + if (ref != null && ref.isSolved()) { + return SymbolReference.adapt(ref, ResolvedTypeDeclaration.class); + } + } + + // look into asterisk imports on demand + for (ImportDeclaration importDecl : wrappedNode.getImports()) { + if (importDecl.isAsterisk()) { + String qName = importDecl.getNameAsString() + "." + name; + SymbolReference ref = typeSolver.tryToSolveType(qName); + if (ref != null && ref.isSolved()) { + return SymbolReference.adapt(ref, ResolvedTypeDeclaration.class); + } + } + } + + // Look in the java.lang package + SymbolReference ref = typeSolver.tryToSolveType("java.lang." + name); + if (ref != null && ref.isSolved()) { + return SymbolReference.adapt(ref, ResolvedTypeDeclaration.class); + } + + // DO NOT look for absolute name if this name is not qualified: you cannot import classes from the default package + if (isQualifiedName(name)) { + return SymbolReference.adapt(typeSolver.tryToSolveType(name), ResolvedTypeDeclaration.class); + } else { + return SymbolReference.unsolved(ResolvedReferenceTypeDeclaration.class); + } + } + + private String qName(ClassOrInterfaceType type) { + if (type.getScope().isPresent()) { + return qName(type.getScope().get()) + "." + type.getName().getId(); + } else { + return type.getName().getId(); + } + } + + private String qName(Name name) { + if (name.getQualifier().isPresent()) { + return qName(name.getQualifier().get()) + "." + name.getId(); + } else { + return name.getId(); + } + } + + private String toSimpleName(String qName) { + String[] parts = qName.split("\\."); + return parts[parts.length - 1]; + } + + private String packageName(String qName) { + int lastDot = qName.lastIndexOf('.'); + if (lastDot == -1) { + throw new UnsupportedOperationException(); + } else { + return qName.substring(0, lastDot); + } + } + + @Override + public SymbolReference solveMethod(String name, List argumentsTypes, boolean staticOnly) { + for (ImportDeclaration importDecl : wrappedNode.getImports()) { + if (importDecl.isStatic()) { + if (importDecl.isAsterisk()) { + String importString = importDecl.getNameAsString(); + + if (this.wrappedNode.getPackageDeclaration().isPresent() + && this.wrappedNode.getPackageDeclaration().get().getName().getIdentifier().equals(packageName(importString)) + && this.wrappedNode.getTypes().stream().anyMatch(it -> it.getName().getIdentifier().equals(toSimpleName(importString)))) { + // We are using a static import on a type defined in this file. It means the value was not found at + // a lower level so this will fail + return SymbolReference.unsolved(ResolvedMethodDeclaration.class); + } + + ResolvedTypeDeclaration ref = typeSolver.solveType(importString); + // avoid infinite recursion + if (!isAncestorOf(ref)) { + SymbolReference method = MethodResolutionLogic.solveMethodInType(ref, name, argumentsTypes, true); + if (method.isSolved()) { + return method; + } + } + } else { + String qName = importDecl.getNameAsString(); + + if (qName.equals(name) || qName.endsWith("." + name)) { + String typeName = getType(qName); + ResolvedTypeDeclaration ref = typeSolver.solveType(typeName); + SymbolReference method = MethodResolutionLogic.solveMethodInType(ref, name, argumentsTypes, true); + if (method.isSolved()) { + return method; + } else { + return SymbolReference.unsolved(ResolvedMethodDeclaration.class); + } + } + } + } + } + return SymbolReference.unsolved(ResolvedMethodDeclaration.class); + } + + @Override + public List fieldsExposedToChild(Node child) { + List res = new LinkedList<>(); + // Consider the static imports for static fields + for (ImportDeclaration importDeclaration : wrappedNode.getImports()) { + if (importDeclaration.isStatic()) { + Name typeNameAsNode = importDeclaration.isAsterisk() ? importDeclaration.getName() : importDeclaration.getName().getQualifier().get(); + String typeName = typeNameAsNode.asString(); + ResolvedReferenceTypeDeclaration typeDeclaration = typeSolver.solveType(typeName); + res.addAll(typeDeclaration.getAllFields().stream() + .filter(f -> f.isStatic()) + .filter(f -> importDeclaration.isAsterisk() || importDeclaration.getName().getIdentifier().equals(f.getName())) + .collect(Collectors.toList())); + } + } + return res; + } + + /// + /// Private methods + /// + + private String getType(String qName) { + int index = qName.lastIndexOf('.'); + if (index == -1) { + throw new UnsupportedOperationException(); + } + String typeName = qName.substring(0, index); + return typeName; + } + + private String getMember(String qName) { + int index = qName.lastIndexOf('.'); + if (index == -1) { + throw new UnsupportedOperationException(); + } + String memberName = qName.substring(index + 1); + return memberName; + } + + private boolean isAncestorOf(ResolvedTypeDeclaration descendant) { + if (descendant instanceof AssociableToAST) { + Optional astOpt = ((AssociableToAST) descendant).toAst(); + if (astOpt.isPresent()) { + return wrappedNode.isAncestorOf(astOpt.get()); + } else { + return false; + } + } else if (descendant instanceof JavaParserEnumDeclaration) { + return wrappedNode.isAncestorOf(((JavaParserEnumDeclaration) descendant).getWrappedNode()); + } else { + throw new UnsupportedOperationException(); + } + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ConstructorContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ConstructorContext.java new file mode 100644 index 0000000000000000000000000000000000000000..1c67561659b5a2383bd8f4a111c9b9a8303cb190 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ConstructorContext.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.ConstructorDeclaration; +import com.github.javaparser.ast.body.Parameter; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.Collections; +import java.util.List; + +/** + * @author Federico Tomassetti + */ +public class ConstructorContext extends AbstractMethodLikeDeclarationContext { + + /// + /// Constructors + /// + + public ConstructorContext(ConstructorDeclaration wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + @Override + public List parametersExposedToChild(Node child) { + // TODO/FIXME: Presumably the parameters must be exposed to all children and their descendants, not just the direct child? + if (child == wrappedNode.getBody()) { + return wrappedNode.getParameters(); + } + return Collections.emptyList(); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ContextHelper.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ContextHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..86360c1ca70cfce6a406f7633cd39f12cd03cfcb --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ContextHelper.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.declarations.ResolvedTypeDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.core.resolution.MethodUsageResolutionCapability; + +import java.util.List; +import java.util.Optional; + +/** + * @author Federico Tomassetti + */ +public class ContextHelper { + + private ContextHelper() { + // prevent instantiation + } + + public static Optional solveMethodAsUsage(ResolvedTypeDeclaration typeDeclaration, String name, + List argumentsTypes, Context invokationContext, + List typeParameters) { + + if (typeDeclaration instanceof MethodUsageResolutionCapability) { + return ((MethodUsageResolutionCapability) typeDeclaration) + .solveMethodAsUsage(name, argumentsTypes, invokationContext, typeParameters); + } else { + throw new UnsupportedOperationException(typeDeclaration.toString()); + } + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/EnclosedExprContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/EnclosedExprContext.java new file mode 100644 index 0000000000000000000000000000000000000000..e875bfab01422d532e972cea59c2876c8be7e97a --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/EnclosedExprContext.java @@ -0,0 +1,61 @@ +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.expr.EnclosedExpr; +import com.github.javaparser.ast.expr.PatternExpr; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFactory; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.ArrayList; +import java.util.List; + + +public class EnclosedExprContext extends AbstractJavaParserContext { + + public EnclosedExprContext(EnclosedExpr wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + @Override + public List patternExprsExposedFromChildren() { + List results = new ArrayList<>(); + + /* + * Test for an assignment expression + * Example: + * while ((numChars = reader.read(buffer, 0, buffer.length)) > 0) { + * result.append(buffer, 0, numChars); + * } + */ + if(!wrappedNode.getInner().isAssignExpr()) { + // Propagate any pattern expressions "up" without modification + Context innerContext = JavaParserFactory.getContext(wrappedNode.getInner(), typeSolver); + if (!this.equals(innerContext)) { + results = new ArrayList<>(innerContext.patternExprsExposedFromChildren()); + } + } + return results; + } + + @Override + public List negatedPatternExprsExposedFromChildren() { + List results = new ArrayList<>(); + + /* + * Test for an assignment expression + * Example: + * while ((numChars = reader.read(buffer, 0, buffer.length)) > 0) { + * result.append(buffer, 0, numChars); + * } + */ + if(!wrappedNode.getInner().isAssignExpr()) { + // Propagate any pattern expressions "up" without modification + Context innerContext = JavaParserFactory.getContext(wrappedNode.getInner(), typeSolver); + if (!this.equals(innerContext)) { + results = new ArrayList<>(innerContext.negatedPatternExprsExposedFromChildren()); + } + } + return results; + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/EnumDeclarationContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/EnumDeclarationContext.java new file mode 100644 index 0000000000000000000000000000000000000000..9060e0335362e00357f24e34c3dee729b954747c --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/EnumDeclarationContext.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.body.EnumConstantDeclaration; +import com.github.javaparser.ast.body.EnumDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserEnumConstantDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserEnumDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.List; + +/** + * @author Federico Tomassetti + */ +public class EnumDeclarationContext extends AbstractJavaParserContext { + + private JavaParserTypeDeclarationAdapter javaParserTypeDeclarationAdapter; + + public EnumDeclarationContext(EnumDeclaration wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + this.javaParserTypeDeclarationAdapter = new JavaParserTypeDeclarationAdapter(wrappedNode, typeSolver, + getDeclaration(), this); + } + + @Override + public SymbolReference solveSymbol(String name) { + if (typeSolver == null) throw new IllegalArgumentException(); + + // among constants + for (EnumConstantDeclaration constant : wrappedNode.getEntries()) { + if (constant.getName().getId().equals(name)) { + return SymbolReference.solved(new JavaParserEnumConstantDeclaration(constant, typeSolver)); + } + } + + if (this.getDeclaration().hasField(name)) { + return SymbolReference.solved(this.getDeclaration().getField(name)); + } + + // then to parent + return solveSymbolInParentContext(name); + } + + @Override + public SymbolReference solveType(String name) { + return javaParserTypeDeclarationAdapter.solveType(name); + } + + @Override + public SymbolReference solveMethod(String name, List argumentsTypes, boolean staticOnly) { + return javaParserTypeDeclarationAdapter.solveMethod(name, argumentsTypes, staticOnly); + } + + /// + /// Private methods + /// + + private ResolvedReferenceTypeDeclaration getDeclaration() { + return new JavaParserEnumDeclaration(this.wrappedNode, typeSolver); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/FieldAccessContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/FieldAccessContext.java new file mode 100644 index 0000000000000000000000000000000000000000..0519278963395e0ae8c2aa7e6c326ed8b66c8d92 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/FieldAccessContext.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.expr.Expression; +import com.github.javaparser.ast.expr.FieldAccessExpr; +import com.github.javaparser.ast.expr.ThisExpr; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedPrimitiveType; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +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.symbolsolver.model.resolution.Value; +import com.github.javaparser.symbolsolver.resolution.SymbolSolver; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import static com.github.javaparser.symbolsolver.javaparser.Navigator.demandParentNode; + +/** + * @author Federico Tomassetti + */ +public class FieldAccessContext extends AbstractJavaParserContext { + + private static final String ARRAY_LENGTH_FIELD_NAME = "length"; + + public FieldAccessContext(FieldAccessExpr wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + @Override + public SymbolReference solveSymbol(String name) { + if (wrappedNode.getName().toString().equals(name)) { + if (wrappedNode.getScope() instanceof ThisExpr) { + ResolvedType typeOfThis = JavaParserFacade.get(typeSolver).getTypeOfThisIn(wrappedNode); + if(typeOfThis.asReferenceType().getTypeDeclaration().isPresent()) { + return new SymbolSolver(typeSolver).solveSymbolInType( + typeOfThis.asReferenceType().getTypeDeclaration().get(), + name + ); + } + } + } + return JavaParserFactory.getContext(demandParentNode(wrappedNode), typeSolver).solveSymbol(name); + } + + @Override + public SymbolReference solveType(String name) { + return JavaParserFactory.getContext(demandParentNode(wrappedNode), typeSolver).solveType(name); + } + + @Override + public SymbolReference solveMethod(String name, List parameterTypes, boolean staticOnly) { + return JavaParserFactory.getContext(demandParentNode(wrappedNode), typeSolver).solveMethod(name, parameterTypes, false); + } + + @Override + public Optional solveSymbolAsValue(String name) { + Expression scope = wrappedNode.getScope(); + if (wrappedNode.getName().toString().equals(name)) { + ResolvedType typeOfScope = JavaParserFacade.get(typeSolver).getType(scope); + if (typeOfScope.isArray() && name.equals(ARRAY_LENGTH_FIELD_NAME)) { + return Optional.of(new Value(ResolvedPrimitiveType.INT, ARRAY_LENGTH_FIELD_NAME)); + } + if (typeOfScope.isReferenceType()) { + return solveSymbolAsValue(name, typeOfScope.asReferenceType()); + } else if (typeOfScope.isConstraint()) { + return solveSymbolAsValue(name, typeOfScope.asConstraintType().getBound().asReferenceType()); + } else { + return Optional.empty(); + } + } else { + return solveSymbolAsValueInParentContext(name); + } + } + + /* + * Try to resolve the name parameter as a field of the reference type + */ + private Optional solveSymbolAsValue(String name, ResolvedReferenceType type) { + Optional optionalTypeDeclaration = type.getTypeDeclaration(); + if (optionalTypeDeclaration.isPresent()) { + ResolvedReferenceTypeDeclaration typeDeclaration = optionalTypeDeclaration.get(); + if (typeDeclaration.isEnum()) { + ResolvedEnumDeclaration enumDeclaration = (ResolvedEnumDeclaration) typeDeclaration; + if (enumDeclaration.hasEnumConstant(name)) { + return Optional.of(new Value(enumDeclaration.getEnumConstant(name).getType(), name)); + } + } + } + Optional typeUsage = type.getFieldType(name); + return typeUsage.map(resolvedType -> new Value(resolvedType, name)); + } + + public SymbolReference solveField(String name) { + Collection rrtds = findTypeDeclarations(Optional.of(wrappedNode.getScope())); + for (ResolvedReferenceTypeDeclaration rrtd : rrtds) { + if (rrtd.isEnum()) { + Optional enumConstant = rrtd.asEnum().getEnumConstants().stream().filter(c -> c.getName().equals(name)).findFirst(); + if (enumConstant.isPresent()) { + return SymbolReference.solved(enumConstant.get()); + } + } + try { + return SymbolReference.solved(rrtd.getField(wrappedNode.getName().getId())); + } catch (Throwable t) { + } + } + return SymbolReference.unsolved(ResolvedFieldDeclaration.class); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ForEachStatementContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ForEachStatementContext.java new file mode 100644 index 0000000000000000000000000000000000000000..8a30658b0b82e0e359000e133ae5736fd2581661 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ForEachStatementContext.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.VariableDeclarator; +import com.github.javaparser.ast.stmt.BlockStmt; +import com.github.javaparser.ast.stmt.ForEachStmt; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserSymbolDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.Collections; +import java.util.List; + +import static com.github.javaparser.symbolsolver.javaparser.Navigator.demandParentNode; + +public class ForEachStatementContext extends AbstractJavaParserContext { + + public ForEachStatementContext(ForEachStmt wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + @Override + public SymbolReference solveSymbol(String name) { + if (wrappedNode.getVariable().getVariables().size() != 1) { + throw new IllegalStateException(); + } + VariableDeclarator variableDeclarator = wrappedNode.getVariable().getVariables().get(0); + if (variableDeclarator.getName().getId().equals(name)) { + return SymbolReference.solved(JavaParserSymbolDeclaration.localVar(variableDeclarator, typeSolver)); + } else { + if (demandParentNode(wrappedNode) instanceof BlockStmt) { + return StatementContext.solveInBlock(name, typeSolver, wrappedNode); + } else { + return solveSymbolInParentContext(name); + } + } + } + + @Override + public SymbolReference solveMethod(String name, List argumentsTypes, boolean staticOnly) { + // TODO: Document why staticOnly is forced to be false. + return solveMethodInParentContext(name, argumentsTypes, false); + } + + @Override + public List localVariablesExposedToChild(Node child) { + if (child == wrappedNode.getBody()) { + return wrappedNode.getVariable().getVariables(); + } + return Collections.emptyList(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ForStatementContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ForStatementContext.java new file mode 100644 index 0000000000000000000000000000000000000000..51e0e71c5c52877e2a888ce0cbb594a04d32537d --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ForStatementContext.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.VariableDeclarator; +import com.github.javaparser.ast.expr.*; +import com.github.javaparser.ast.nodeTypes.NodeWithStatements; +import com.github.javaparser.ast.stmt.ForStmt; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserSymbolDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.LinkedList; +import java.util.List; + +import static com.github.javaparser.symbolsolver.javaparser.Navigator.demandParentNode; + +public class ForStatementContext extends AbstractJavaParserContext { + + public ForStatementContext(ForStmt wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + @Override + public SymbolReference solveSymbol(String name) { + for (Expression expression : wrappedNode.getInitialization()) { + if (expression instanceof VariableDeclarationExpr) { + VariableDeclarationExpr variableDeclarationExpr = (VariableDeclarationExpr) expression; + for (VariableDeclarator variableDeclarator : variableDeclarationExpr.getVariables()) { + if (variableDeclarator.getName().getId().equals(name)) { + return SymbolReference.solved(JavaParserSymbolDeclaration.localVar(variableDeclarator, typeSolver)); + } + } + } else if (!(expression instanceof AssignExpr || expression instanceof MethodCallExpr || expression instanceof UnaryExpr)) { + throw new UnsupportedOperationException(expression.getClass().getCanonicalName()); + } + } + + if (demandParentNode(wrappedNode) instanceof NodeWithStatements) { + return StatementContext.solveInBlock(name, typeSolver, wrappedNode); + } else { + return solveSymbolInParentContext(name); + } + } + + @Override + public SymbolReference solveMethod(String name, List argumentsTypes, boolean staticOnly) { + // TODO: Document why staticOnly is forced to be false. + return solveMethodInParentContext(name, argumentsTypes, false); + } + + @Override + public List localVariablesExposedToChild(Node child) { + List res = new LinkedList<>(); + for (Expression expression : wrappedNode.getInitialization()) { + if (expression instanceof VariableDeclarationExpr) { + VariableDeclarationExpr variableDeclarationExpr = (VariableDeclarationExpr) expression; + res.addAll(variableDeclarationExpr.getVariables()); + } + } + return res; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/IfStatementContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/IfStatementContext.java new file mode 100644 index 0000000000000000000000000000000000000000..893fc09ad7f920408ad16461687795d99739bb9b --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/IfStatementContext.java @@ -0,0 +1,163 @@ +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.expr.Expression; +import com.github.javaparser.ast.expr.PatternExpr; +import com.github.javaparser.ast.stmt.IfStmt; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFactory; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.ArrayList; +import java.util.List; + +public class IfStatementContext extends StatementContext { +//public class IfStatementContext extends AbstractJavaParserContext { + + public IfStatementContext(IfStmt wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + + @Override + public List patternExprsExposedToChild(Node child) { + Expression condition = wrappedNode.getCondition(); + Context conditionContext = JavaParserFactory.getContext(condition, typeSolver); + + List results = new ArrayList<>(); + + boolean givenNodeIsWithinThenStatement = wrappedNode.getThenStmt().containsWithinRange(child); + if(givenNodeIsWithinThenStatement) { + results.addAll(conditionContext.patternExprsExposedFromChildren()); + } + + wrappedNode.getElseStmt().ifPresent(elseStatement -> { + boolean givenNodeIsWithinElseStatement = elseStatement.containsWithinRange(child); + if(givenNodeIsWithinElseStatement) { + results.addAll(conditionContext.negatedPatternExprsExposedFromChildren()); + } + }); + + return results; + } + + + /** + *
{@code
+     * if() {
+     *     // Does not match here (doesn't need to, as stuff inside of the if() is likely in context..)
+     * } else if() {
+     *     // Matches here
+     * } else {
+     *     // Matches here
+     * }
+     * }
+ * + * @return true, If this is an if inside of an if... + */ + public boolean nodeContextIsChainedIfElseIf(Context parentContext) { + return parentContext instanceof AbstractJavaParserContext + && ((AbstractJavaParserContext) this).getWrappedNode() instanceof IfStmt + && ((AbstractJavaParserContext) parentContext).getWrappedNode() instanceof IfStmt; + } + + /** + *
{@code
+     * if() {
+     *     // Does not match here (doesn't need to, as stuff inside of the if() is likely in context..)
+     * } else {
+     *     // Does not match here, as the else block is a field inside of an ifstmt as opposed to child
+     * }
+     * }
+ * + * @return true, If this is an else inside of an if... + */ + public boolean nodeContextIsImmediateChildElse(Context parentContext) { + if (!(parentContext instanceof AbstractJavaParserContext)) { + return false; + } + if (!(this instanceof AbstractJavaParserContext)) { + return false; + } + + AbstractJavaParserContext abstractContext = (AbstractJavaParserContext) this; + AbstractJavaParserContext abstractParentContext = (AbstractJavaParserContext) parentContext; + + Node wrappedNode = abstractContext.getWrappedNode(); + Node wrappedParentNode = abstractParentContext.getWrappedNode(); + + if (wrappedParentNode instanceof IfStmt) { + IfStmt parentIfStmt = (IfStmt) wrappedParentNode; + if (parentIfStmt.getElseStmt().isPresent()) { + boolean currentNodeIsAnElseBlock = parentIfStmt.getElseStmt().get() == wrappedNode; + if (currentNodeIsAnElseBlock) { + return true; + } + } + } + + return false; + } + + /** + *
{@code
+     * if() {
+     *     // Does not match here (doesn't need to, as stuff inside of the if() is likely in context..)
+     * } else {
+     *     // Does not match here, as the else block is a field inside of an ifstmt as opposed to child
+     * }
+     * }
+ * + * @return true, If this is an else inside of an if... + */ + public boolean nodeContextIsThenOfIfStmt(Context parentContext) { + if (!(parentContext instanceof AbstractJavaParserContext)) { + return false; + } + if (!(this instanceof AbstractJavaParserContext)) { + return false; + } + + AbstractJavaParserContext abstractContext = (AbstractJavaParserContext) this; + AbstractJavaParserContext abstractParentContext = (AbstractJavaParserContext) parentContext; + + Node wrappedNode = abstractContext.getWrappedNode(); + Node wrappedParentNode = abstractParentContext.getWrappedNode(); + + if (wrappedParentNode instanceof IfStmt) { + IfStmt parentIfStmt = (IfStmt) wrappedParentNode; + boolean currentNodeIsAnElseBlock = parentIfStmt.getThenStmt() == wrappedNode; + if (currentNodeIsAnElseBlock) { + return true; + } + } + + return false; + } + + + public boolean nodeContextIsConditionOfIfStmt(Context parentContext) { + if (!(parentContext instanceof AbstractJavaParserContext)) { + return false; + } + if (!(this instanceof AbstractJavaParserContext)) { + return false; + } + + AbstractJavaParserContext abstractContext = (AbstractJavaParserContext) this; + AbstractJavaParserContext abstractParentContext = (AbstractJavaParserContext) parentContext; + + Node wrappedNode = abstractContext.getWrappedNode(); + Node wrappedParentNode = abstractParentContext.getWrappedNode(); + + if (wrappedParentNode instanceof IfStmt) { + IfStmt parentIfStmt = (IfStmt) wrappedParentNode; + boolean currentNodeIsCondition = parentIfStmt.getCondition() == wrappedNode; + if (currentNodeIsCondition) { + return true; + } + } + + return false; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/InstanceOfExprContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/InstanceOfExprContext.java new file mode 100644 index 0000000000000000000000000000000000000000..c63f6b399622980be2bd196898b5d71b54ab22a5 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/InstanceOfExprContext.java @@ -0,0 +1,67 @@ +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.expr.InstanceOfExpr; +import com.github.javaparser.ast.expr.PatternExpr; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserPatternDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserSymbolDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * @author Roger Howell + */ +public class InstanceOfExprContext extends AbstractJavaParserContext { + + public InstanceOfExprContext(InstanceOfExpr wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + @Override + public SymbolReference solveSymbol(String name) { + Optional optionalPatternExpr = wrappedNode.getPattern(); + if(optionalPatternExpr.isPresent()) { + if(optionalPatternExpr.get().getNameAsString().equals(name)) { + JavaParserPatternDeclaration decl = JavaParserSymbolDeclaration.patternVar(optionalPatternExpr.get(), typeSolver); + return SymbolReference.solved(decl); + } + } + + + Optional optionalParentContext = getParent(); + if (!optionalParentContext.isPresent()) { + return SymbolReference.unsolved(ResolvedValueDeclaration.class); + } + + Context parentContext = optionalParentContext.get(); + if(parentContext instanceof BinaryExprContext) { + Optional optionalPatternExpr1 = parentContext.patternExprInScope(name); + if(optionalPatternExpr1.isPresent()) { + JavaParserPatternDeclaration decl = JavaParserSymbolDeclaration.patternVar(optionalPatternExpr1.get(), typeSolver); + return SymbolReference.solved(decl); + } + } // TODO: Also consider unary expr context + + + // if nothing is found we should ask the parent context + return solveSymbolInParentContext(name); + } + + @Override + public List patternExprsExposedFromChildren() { + List results = new ArrayList<>(); + + // If this instanceof expression has a pattern, add it to the list. + wrappedNode.getPattern().ifPresent(results::add); + + return results; + } + + + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/JavaParserTypeDeclarationAdapter.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/JavaParserTypeDeclarationAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..ebc50939a1a1fd65458f725d474293fae3966e8e --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/JavaParserTypeDeclarationAdapter.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.ast.body.BodyDeclaration; +import com.github.javaparser.ast.body.TypeDeclaration; +import com.github.javaparser.ast.nodeTypes.NodeWithExtends; +import com.github.javaparser.ast.nodeTypes.NodeWithImplements; +import com.github.javaparser.ast.nodeTypes.NodeWithTypeParameters; +import com.github.javaparser.ast.type.ClassOrInterfaceType; +import com.github.javaparser.ast.type.TypeParameter; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFactory; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserTypeParameter; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionClassDeclaration; +import com.github.javaparser.symbolsolver.resolution.ConstructorResolutionLogic; +import com.github.javaparser.symbolsolver.resolution.MethodResolutionLogic; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +public class JavaParserTypeDeclarationAdapter { + + private com.github.javaparser.ast.body.TypeDeclaration wrappedNode; + private TypeSolver typeSolver; + private Context context; + private ResolvedReferenceTypeDeclaration typeDeclaration; + + public JavaParserTypeDeclarationAdapter(com.github.javaparser.ast.body.TypeDeclaration wrappedNode, TypeSolver typeSolver, + ResolvedReferenceTypeDeclaration typeDeclaration, + Context context) { + this.wrappedNode = wrappedNode; + this.typeSolver = typeSolver; + this.typeDeclaration = typeDeclaration; + this.context = context; + } + + public SymbolReference solveType(String name) { + if (this.wrappedNode.getName().getId().equals(name)) { + return SymbolReference.solved(JavaParserFacade.get(typeSolver).getTypeDeclaration(wrappedNode)); + } + + // Internal classes + for (BodyDeclaration member : this.wrappedNode.getMembers()) { + if (member instanceof TypeDeclaration) { + TypeDeclaration internalType = (TypeDeclaration) member; + if (internalType.getName().getId().equals(name)) { + return SymbolReference.solved(JavaParserFacade.get(typeSolver).getTypeDeclaration(internalType)); + } else if (name.startsWith(wrappedNode.getName().getId() + "." + internalType.getName().getId())) { + return JavaParserFactory.getContext(internalType, typeSolver).solveType(name.substring(wrappedNode.getName().getId().length() + 1)); + } else if (name.startsWith(internalType.getName().getId() + ".")) { + return JavaParserFactory.getContext(internalType, typeSolver).solveType(name.substring(internalType.getName().getId().length() + 1)); + } + } + } + + if (wrappedNode instanceof NodeWithTypeParameters) { + NodeWithTypeParameters nodeWithTypeParameters = (NodeWithTypeParameters) wrappedNode; + for (TypeParameter astTpRaw : nodeWithTypeParameters.getTypeParameters()) { + TypeParameter astTp = astTpRaw; + if (astTp.getName().getId().equals(name)) { + return SymbolReference.solved(new JavaParserTypeParameter(astTp, typeSolver)); + } + } + } + + if (wrappedNode instanceof NodeWithImplements) { + NodeWithImplements nodeWithImplements = (NodeWithImplements) wrappedNode; + for (ClassOrInterfaceType implementedType : nodeWithImplements.getImplementedTypes()) { + if (implementedType.getName().getId().equals(name)) { + return context.getParent() + .orElseThrow(() -> new RuntimeException("Parent context unexpectedly empty.")) + .solveType(implementedType.getNameWithScope()); + } + } + } + + if (wrappedNode instanceof NodeWithExtends) { + NodeWithExtends nodeWithExtends = (NodeWithExtends) wrappedNode; + for (ClassOrInterfaceType extendedType : nodeWithExtends.getExtendedTypes()) { + if (extendedType.getName().getId().equals(name)) { + return context.getParent() + .orElseThrow(() -> new RuntimeException("Parent context unexpectedly empty.")) + .solveType(extendedType.getNameWithScope()); + } + } + } + + // Look into extended classes and implemented interfaces + ResolvedTypeDeclaration type = checkAncestorsForType(name, this.typeDeclaration); + if (type != null) { + return SymbolReference.solved(type); + } + + // Else check parents + return context.getParent() + .orElseThrow(() -> new RuntimeException("Parent context unexpectedly empty.")) + .solveType(name); + } + + /** + * Recursively checks the ancestors of the {@param declaration} if an internal type is declared with a name equal + * to {@param name}. + * TODO: Edit to remove return of null (favouring a return of optional) + * @return A ResolvedTypeDeclaration matching the {@param name}, null otherwise + */ + private ResolvedTypeDeclaration checkAncestorsForType(String name, ResolvedReferenceTypeDeclaration declaration) { + for (ResolvedReferenceType ancestor : declaration.getAncestors(true)) { + try { + // TODO: Figure out if it is appropriate to remove the orElseThrow() -- if so, how... + ResolvedReferenceTypeDeclaration ancestorReferenceTypeDeclaration = ancestor + .getTypeDeclaration() + .orElseThrow(() -> new RuntimeException("TypeDeclaration unexpectedly empty.")); + + for (ResolvedTypeDeclaration internalTypeDeclaration : ancestorReferenceTypeDeclaration.internalTypes()) { + boolean visible = true; + if (internalTypeDeclaration instanceof ResolvedReferenceTypeDeclaration) { + ResolvedReferenceTypeDeclaration resolvedReferenceTypeDeclaration = internalTypeDeclaration.asReferenceType(); + if (resolvedReferenceTypeDeclaration instanceof HasAccessSpecifier) { + visible = ((HasAccessSpecifier) resolvedReferenceTypeDeclaration).accessSpecifier() != AccessSpecifier.PRIVATE; + } + } + if (internalTypeDeclaration.getName().equals(name)) { + if (visible) { + return internalTypeDeclaration; + } else { + return null; // FIXME -- Avoid returning null. + } + } + } + + // check recursively the ancestors of this ancestor + ResolvedTypeDeclaration ancestorTypeDeclaration = checkAncestorsForType(name, ancestorReferenceTypeDeclaration); + if (ancestorTypeDeclaration != null) { + return ancestorTypeDeclaration; + } + } catch (UnsupportedOperationException e) { + // just continue using the next ancestor + } + } + return null; // FIXME -- Avoid returning null. + } + + public SymbolReference solveMethod(String name, List argumentsTypes, boolean staticOnly) { + + // Begin by locating methods declared "here" + List candidateMethods = typeDeclaration.getDeclaredMethods().stream() + .filter(m -> m.getName().equals(name)) + .filter(m -> !staticOnly || m.isStatic()) + .collect(Collectors.toList()); + + // Next, consider methods declared within ancestors. + // Note that we only consider ancestors when we are not currently at java.lang.Object (avoiding infinite recursion). + if (!typeDeclaration.isJavaLangObject()) { + for (ResolvedReferenceType ancestor : typeDeclaration.getAncestors(true)) { + Optional ancestorTypeDeclaration = ancestor.getTypeDeclaration(); + + // Avoid recursion on self + if (ancestor.getTypeDeclaration().isPresent() && typeDeclaration != ancestorTypeDeclaration.get()) { + // Consider methods declared on self + candidateMethods.addAll(ancestor.getAllMethodsVisibleToInheritors() + .stream() + .filter(m -> m.getName().equals(name)) + .collect(Collectors.toList())); + + // consider methods from superclasses and only default methods from interfaces : + // not true, we should keep abstract as a valid candidate + // abstract are removed in MethodResolutionLogic.isApplicable is necessary + SymbolReference res = MethodResolutionLogic.solveMethodInType(ancestorTypeDeclaration.get(), name, argumentsTypes, staticOnly); + if (res.isSolved()) { + candidateMethods.add(res.getCorrespondingDeclaration()); + } + } + } + } + + // If we haven't located any candidates that are declared on this type or its ancestors, consider the parent context. + // This is relevant e.g. with nested classes. + // Note that we want to avoid infinite recursion when a class is using its own method - see issue #75 + if (candidateMethods.isEmpty()) { + SymbolReference parentSolution = context.getParent() + .orElseThrow(() -> new RuntimeException("Parent context unexpectedly empty.")) + .solveMethod(name, argumentsTypes, staticOnly); + if (parentSolution.isSolved()) { + candidateMethods.add(parentSolution.getCorrespondingDeclaration()); + } + } + + // if is interface and candidate method list is empty, we should check the Object Methods + if (candidateMethods.isEmpty() && typeDeclaration.isInterface()) { + SymbolReference res = MethodResolutionLogic.solveMethodInType(new ReflectionClassDeclaration(Object.class, typeSolver), name, argumentsTypes, false); + if (res.isSolved()) { + candidateMethods.add(res.getCorrespondingDeclaration()); + } + } + + return MethodResolutionLogic.findMostApplicable(candidateMethods, name, argumentsTypes, typeSolver); + } + + public SymbolReference solveConstructor(List argumentsTypes) { + if (typeDeclaration instanceof ResolvedClassDeclaration) { + return ConstructorResolutionLogic.findMostApplicable(typeDeclaration.getConstructors(), argumentsTypes, typeSolver); + } + return SymbolReference.unsolved(ResolvedConstructorDeclaration.class); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/LambdaExprContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/LambdaExprContext.java new file mode 100644 index 0000000000000000000000000000000000000000..639efa8ce607693d04d198ca5f08b55b6abc3bdb --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/LambdaExprContext.java @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.body.Parameter; +import com.github.javaparser.ast.body.VariableDeclarator; +import com.github.javaparser.ast.expr.CastExpr; +import com.github.javaparser.ast.expr.Expression; +import com.github.javaparser.ast.expr.LambdaExpr; +import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.ast.stmt.ReturnStmt; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.resolution.types.ResolvedLambdaConstraintType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFactory; +import com.github.javaparser.symbolsolver.logic.FunctionalInterfaceLogic; +import com.github.javaparser.symbolsolver.logic.InferenceContext; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.resolution.Value; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.symbolsolver.reflectionmodel.MyObjectProvider; +import com.github.javaparser.symbolsolver.resolution.SymbolDeclarator; + +import java.util.*; + +import static com.github.javaparser.symbolsolver.javaparser.Navigator.demandParentNode; + +/** + * @author Federico Tomassetti + */ +public class LambdaExprContext extends AbstractJavaParserContext { + + public LambdaExprContext(LambdaExpr wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + @Override + public Optional solveSymbolAsValue(String name) { + for (Parameter parameter : wrappedNode.getParameters()) { + SymbolDeclarator sb = JavaParserFactory.getSymbolDeclarator(parameter, typeSolver); + int index = 0; + for (ResolvedValueDeclaration decl : sb.getSymbolDeclarations()) { + if (decl.getName().equals(name)) { + Node parentNode = demandParentNode(wrappedNode); + if (parentNode instanceof MethodCallExpr) { + MethodCallExpr methodCallExpr = (MethodCallExpr) parentNode; + MethodUsage methodUsage = JavaParserFacade.get(typeSolver).solveMethodAsUsage(methodCallExpr); + int i = pos(methodCallExpr, wrappedNode); + ResolvedType lambdaType = methodUsage.getParamTypes().get(i); + + // Get the functional method in order for us to resolve it's type arguments properly + Optional functionalMethodOpt = FunctionalInterfaceLogic.getFunctionalMethod(lambdaType); + if (functionalMethodOpt.isPresent()){ + MethodUsage functionalMethod = functionalMethodOpt.get(); + InferenceContext inferenceContext = new InferenceContext(MyObjectProvider.INSTANCE); + + // Resolve each type variable of the lambda, and use this later to infer the type of each + // implicit parameter + lambdaType.asReferenceType().getTypeDeclaration().ifPresent(typeDeclaration -> { + inferenceContext.addPair( + lambdaType, + new ReferenceTypeImpl(typeDeclaration, typeSolver) + ); + }); + + // Find the position of this lambda argument + boolean found = false; + int lambdaParamIndex; + for (lambdaParamIndex = 0; lambdaParamIndex < wrappedNode.getParameters().size(); lambdaParamIndex++){ + if (wrappedNode.getParameter(lambdaParamIndex).getName().getIdentifier().equals(name)){ + found = true; + break; + } + } + if (!found) { return Optional.empty(); } + + // Now resolve the argument type using the inference context + ResolvedType argType = inferenceContext.resolve(inferenceContext.addSingle(functionalMethod.getParamType(lambdaParamIndex))); + + ResolvedLambdaConstraintType conType; + if (argType.isWildcard()){ + conType = ResolvedLambdaConstraintType.bound(argType.asWildcard().getBoundedType()); + } else { + conType = ResolvedLambdaConstraintType.bound(argType); + } + Value value = new Value(conType, name); + return Optional.of(value); + } else{ + return Optional.empty(); + } + } else if (parentNode instanceof VariableDeclarator) { + VariableDeclarator variableDeclarator = (VariableDeclarator) parentNode; + ResolvedType t = JavaParserFacade.get(typeSolver).convertToUsageVariableType(variableDeclarator); + Optional functionalMethod = FunctionalInterfaceLogic.getFunctionalMethod(t); + if (functionalMethod.isPresent()) { + ResolvedType lambdaType = functionalMethod.get().getParamType(index); + + // Replace parameter from declarator + Map inferredTypes = new HashMap<>(); + if (lambdaType.isReferenceType()) { + for (com.github.javaparser.utils.Pair entry : lambdaType.asReferenceType().getTypeParametersMap()) { + if (entry.b.isTypeVariable() && entry.b.asTypeParameter().declaredOnType()) { + ResolvedType ot = t.asReferenceType().typeParametersMap().getValue(entry.a); + lambdaType = lambdaType.replaceTypeVariables(entry.a, ot, inferredTypes); + } + } + } else if (lambdaType.isTypeVariable() && lambdaType.asTypeParameter().declaredOnType()) { + lambdaType = t.asReferenceType().typeParametersMap().getValue(lambdaType.asTypeParameter()); + } + + Value value = new Value(lambdaType, name); + return Optional.of(value); + } else { + throw new UnsupportedOperationException(); + } + } else if (parentNode instanceof ReturnStmt) { + ReturnStmt returnStmt = (ReturnStmt) parentNode; + Optional optDeclaration = returnStmt.findAncestor(MethodDeclaration.class); + if (optDeclaration.isPresent()) { + ResolvedType t = JavaParserFacade.get(typeSolver).convertToUsage(optDeclaration.get().asMethodDeclaration().getType()); + Optional functionalMethod = FunctionalInterfaceLogic.getFunctionalMethod(t); + + if (functionalMethod.isPresent()) { + ResolvedType lambdaType = functionalMethod.get().getParamType(index); + + // Replace parameter from declarator + Map inferredTypes = new HashMap<>(); + if (lambdaType.isReferenceType()) { + for (com.github.javaparser.utils.Pair entry : lambdaType.asReferenceType().getTypeParametersMap()) { + if (entry.b.isTypeVariable() && entry.b.asTypeParameter().declaredOnType()) { + ResolvedType ot = t.asReferenceType().typeParametersMap().getValue(entry.a); + lambdaType = lambdaType.replaceTypeVariables(entry.a, ot, inferredTypes); + } + } + } else if (lambdaType.isTypeVariable() && lambdaType.asTypeParameter().declaredOnType()) { + lambdaType = t.asReferenceType().typeParametersMap().getValue(lambdaType.asTypeParameter()); + } + + Value value = new Value(lambdaType, name); + return Optional.of(value); + } else { + throw new UnsupportedOperationException(); + } + } + } else if (parentNode instanceof CastExpr) { + CastExpr castExpr = (CastExpr) parentNode; + ResolvedType t = JavaParserFacade.get(typeSolver).convertToUsage(castExpr.getType()); + Optional functionalMethod = FunctionalInterfaceLogic.getFunctionalMethod(t); + + if (functionalMethod.isPresent()) { + ResolvedType lambdaType = functionalMethod.get().getParamType(index); + + // Replace parameter from declarator + Map inferredTypes = new HashMap<>(); + if (lambdaType.isReferenceType()) { + for (com.github.javaparser.utils.Pair entry : lambdaType.asReferenceType().getTypeParametersMap()) { + if (entry.b.isTypeVariable() && entry.b.asTypeParameter().declaredOnType()) { + ResolvedType ot = t.asReferenceType().typeParametersMap().getValue(entry.a); + lambdaType = lambdaType.replaceTypeVariables(entry.a, ot, inferredTypes); + } + } + } else if (lambdaType.isTypeVariable() && lambdaType.asTypeParameter().declaredOnType()) { + lambdaType = t.asReferenceType().typeParametersMap().getValue(lambdaType.asTypeParameter()); + } + + Value value = new Value(lambdaType, name); + return Optional.of(value); + } else { + throw new UnsupportedOperationException(); + } + } else { + throw new UnsupportedOperationException(); + } + } + index++; + } + } + + // if nothing is found we should ask the parent context + return solveSymbolAsValueInParentContext(name); + } + + @Override + public SymbolReference solveSymbol(String name) { + for (Parameter parameter : wrappedNode.getParameters()) { + SymbolDeclarator sb = JavaParserFactory.getSymbolDeclarator(parameter, typeSolver); + SymbolReference symbolReference = solveWith(sb, name); + if (symbolReference.isSolved()) { + return symbolReference; + } + } + + // if nothing is found we should ask the parent context + return solveSymbolInParentContext(name); + } + + @Override + public SymbolReference solveMethod(String name, List argumentsTypes, boolean staticOnly) { + // TODO: Document why staticOnly is forced to be false. + return solveMethodInParentContext(name, argumentsTypes, false); + } + + @Override + public List parametersExposedToChild(Node child) { + // TODO/FIXME: Presumably the parameters must be exposed to all children and their descendants, not just the direct child? + if (child == wrappedNode.getBody()) { + return wrappedNode.getParameters(); + } + return Collections.emptyList(); + } + + /// + /// Protected methods + /// + + protected final Optional solveWithAsValue(SymbolDeclarator symbolDeclarator, String name) { + for (ResolvedValueDeclaration decl : symbolDeclarator.getSymbolDeclarations()) { + if (decl.getName().equals(name)) { + + throw new UnsupportedOperationException(); + } + } + return Optional.empty(); + } + + /// + /// Private methods + /// + + private int pos(MethodCallExpr callExpr, Expression param) { + int i = 0; + for (Expression p : callExpr.getArguments()) { + if (p == param) { + return i; + } + i++; + } + throw new IllegalArgumentException(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/MethodCallExprContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/MethodCallExprContext.java new file mode 100644 index 0000000000000000000000000000000000000000..7304a16afdbcbc388e2588160bfd62f4134f49f8 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/MethodCallExprContext.java @@ -0,0 +1,493 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.expr.Expression; +import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.ast.expr.NameExpr; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.*; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.resolution.Value; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.symbolsolver.reflectionmodel.MyObjectProvider; +import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionClassDeclaration; +import com.github.javaparser.symbolsolver.resolution.MethodResolutionLogic; +import com.github.javaparser.utils.Pair; + +import java.util.*; + +public class MethodCallExprContext extends AbstractJavaParserContext { + + /// + /// Constructors + /// + + public MethodCallExprContext(MethodCallExpr wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + /// + /// Public methods + /// + + @Override + public Optional solveGenericType(String name) { + Optional nodeScope = wrappedNode.getScope(); + if (!nodeScope.isPresent()) { + return Optional.empty(); + } + + // Method calls can have generic types defined, for example: {@code expr.method(x, y, z);} or {@code super.check2(val1, val2).} + ResolvedType typeOfScope = JavaParserFacade.get(typeSolver).getType(nodeScope.get()); + Optional resolvedType = typeOfScope.asReferenceType().getGenericParameterByName(name); + + // TODO/FIXME: Consider if we should check if the result is present, else delegate "up" the context chain (e.g. {@code solveGenericTypeInParent()}) + return resolvedType; + } + + @Override + public String toString() { + return "MethodCallExprContext{wrapped=" + wrappedNode + "}"; + } + + @Override + public Optional solveMethodAsUsage(String name, List argumentsTypes) { + ResolvedType typeOfScope; + if (wrappedNode.hasScope()) { + Expression scope = wrappedNode.getScope().get(); + // Consider static method calls + if (scope instanceof NameExpr) { + String className = ((NameExpr) scope).getName().getId(); + SymbolReference ref = solveType(className); + if (ref.isSolved()) { + SymbolReference m = MethodResolutionLogic.solveMethodInType(ref.getCorrespondingDeclaration(), name, argumentsTypes); + if (m.isSolved()) { + MethodUsage methodUsage = new MethodUsage(m.getCorrespondingDeclaration()); + methodUsage = resolveMethodTypeParametersFromExplicitList(typeSolver, methodUsage); + methodUsage = resolveMethodTypeParameters(methodUsage, argumentsTypes); + return Optional.of(methodUsage); + } else { + throw new UnsolvedSymbolException(ref.getCorrespondingDeclaration().toString(), + "Method '" + name + "' with parameterTypes " + argumentsTypes); + } + } + } + + // Scope is present -- search/solve within that type + typeOfScope = JavaParserFacade.get(typeSolver).getType(scope); + } else { + // Scope not present -- search/solve within itself. + typeOfScope = JavaParserFacade.get(typeSolver).getTypeOfThisIn(wrappedNode); + } + + // we can replace the parameter types from the scope into the typeParametersValues + Map inferredTypes = new HashMap<>(); + for (int i = 0; i < argumentsTypes.size(); i++) { + // by replacing types I can also find new equivalences + // for example if I replace T=U with String because I know that T=String I can derive that also U equal String + ResolvedType originalArgumentType = argumentsTypes.get(i); + ResolvedType updatedArgumentType = usingParameterTypesFromScope(typeOfScope, originalArgumentType, inferredTypes); + argumentsTypes.set(i, updatedArgumentType); + } + for (int i = 0; i < argumentsTypes.size(); i++) { + ResolvedType updatedArgumentType = applyInferredTypes(argumentsTypes.get(i), inferredTypes); + argumentsTypes.set(i, updatedArgumentType); + } + + return solveMethodAsUsage(typeOfScope, name, argumentsTypes, this); + } + + private MethodUsage resolveMethodTypeParametersFromExplicitList(TypeSolver typeSolver, MethodUsage methodUsage) { + if (wrappedNode.getTypeArguments().isPresent()) { + final List typeArguments = new ArrayList<>(); + for (com.github.javaparser.ast.type.Type ty : wrappedNode.getTypeArguments().get()) { + typeArguments.add(JavaParserFacade.get(typeSolver).convertToUsage(ty)); + } + + List tyParamDecls = methodUsage.getDeclaration().getTypeParameters(); + if (tyParamDecls.size() == typeArguments.size()) { + for (int i = 0; i < tyParamDecls.size(); i++) { + methodUsage = methodUsage.replaceTypeParameter(tyParamDecls.get(i), typeArguments.get(i)); + } + } + } + + return methodUsage; + } + + @Override + public Optional solveSymbolAsValue(String name) { + return solveSymbolAsValueInParentContext(name); + } + + @Override + public SymbolReference solveMethod(String name, List argumentsTypes, boolean staticOnly) { + Collection rrtds = findTypeDeclarations(wrappedNode.getScope()); + + if (rrtds.isEmpty()) { + // if the bounds of a type parameter are empty, then the bound is implicitly "extends Object" + // we don't make this _ex_plicit in the data representation because that would affect codegen + // and make everything generate like instead of + // https://github.com/javaparser/javaparser/issues/2044 + rrtds = Collections.singleton(typeSolver.getSolvedJavaLangObject()); + } + + for (ResolvedReferenceTypeDeclaration rrtd : rrtds) { + SymbolReference res = MethodResolutionLogic.solveMethodInType(rrtd, name, argumentsTypes, false); + if (res.isSolved()) { + return res; + } + } + + return SymbolReference.unsolved(ResolvedMethodDeclaration.class); + } + + /// + /// Private methods + /// + + private Optional solveMethodAsUsage(ResolvedReferenceType refType, String name, + List argumentsTypes, + Context invokationContext) { + if(!refType.getTypeDeclaration().isPresent()) { + return Optional.empty(); + } + + Optional ref = ContextHelper.solveMethodAsUsage(refType.getTypeDeclaration().get(), name, argumentsTypes, invokationContext, refType.typeParametersValues()); + if (ref.isPresent()) { + MethodUsage methodUsage = ref.get(); + + methodUsage = resolveMethodTypeParametersFromExplicitList(typeSolver, methodUsage); + + // At this stage I should derive from the context and the value some information on the type parameters + // for example, when calling: + // myStream.collect(Collectors.toList()) + // I should be able to figure out that considering the type of the stream (e.g., Stream) + // and considering that Stream has this method: + // + // R collect(Collector collector) + // + // and collector has this method: + // + // static Collector> toList() + // + // In this case collect.R has to be equal to List + // And toList.T has to be equal to ? super Stream.T + // Therefore R has to be equal to List. + // In our example Stream.T equal to String, so the R (and the result of the call to collect) is + // List + + Map derivedValues = new HashMap<>(); + for (int i = 0; i < methodUsage.getParamTypes().size(); i++) { + ResolvedParameterDeclaration parameter = methodUsage.getDeclaration().getParam(i); + ResolvedType parameterType = parameter.getType(); + // Don't continue if a vararg parameter is reached and there are no arguments left + if (parameter.isVariadic() && argumentsTypes.size() < methodUsage.getNoParams()) { + break; + } + if (!argumentsTypes.get(i).isArray() && parameter.isVariadic()) { + parameterType = parameterType.asArrayType().getComponentType(); + } + inferTypes(argumentsTypes.get(i), parameterType, derivedValues); + } + + for (Map.Entry entry : derivedValues.entrySet()){ + methodUsage = methodUsage.replaceTypeParameter(entry.getKey(), entry.getValue()); + } + + ResolvedType returnType = refType.useThisTypeParametersOnTheGivenType(methodUsage.returnType()); + // we don't want to replace the return type in case of UNBOUNDED type () + if (returnType != methodUsage.returnType() && !(returnType == ResolvedWildcard.UNBOUNDED)) { + methodUsage = methodUsage.replaceReturnType(returnType); + } + for (int i = 0; i < methodUsage.getParamTypes().size(); i++) { + ResolvedType replaced = refType.useThisTypeParametersOnTheGivenType(methodUsage.getParamTypes().get(i)); + methodUsage = methodUsage.replaceParamType(i, replaced); + } + return Optional.of(methodUsage); + } else { + return ref; + } + } + + private void inferTypes(ResolvedType source, ResolvedType target, Map mappings) { + if (source.equals(target)) { + return; + } + if (source.isReferenceType() && target.isReferenceType()) { + ResolvedReferenceType sourceRefType = source.asReferenceType(); + ResolvedReferenceType targetRefType = target.asReferenceType(); + if (sourceRefType.getQualifiedName().equals(targetRefType.getQualifiedName())) { + if (!sourceRefType.isRawType() && !targetRefType.isRawType()) { + for (int i = 0; i < sourceRefType.typeParametersValues().size(); i++) { + inferTypes(sourceRefType.typeParametersValues().get(i), targetRefType.typeParametersValues().get(i), mappings); + } + } + } + return; + } + if (source.isReferenceType() && target.isWildcard()) { + if (target.asWildcard().isBounded()) { + inferTypes(source, target.asWildcard().getBoundedType(), mappings); + return; + } + return; + } + if (source.isWildcard() && target.isWildcard()) { + if (source.asWildcard().isBounded() && target.asWildcard().isBounded()){ + inferTypes(source.asWildcard().getBoundedType(), target.asWildcard().getBoundedType(), mappings); + } + return; + } + if (source.isReferenceType() && target.isTypeVariable()) { + mappings.put(target.asTypeParameter(), source); + return; + } + if (source.isWildcard() && target.isTypeVariable()) { + mappings.put(target.asTypeParameter(), source); + return; + } + if (source.isArray() && target.isArray()) { + ResolvedType sourceComponentType = source.asArrayType().getComponentType(); + ResolvedType targetComponentType = target.asArrayType().getComponentType(); + inferTypes(sourceComponentType, targetComponentType, mappings); + return; + } + if (source.isArray() && target.isWildcard()){ + if(target.asWildcard().isBounded()){ + inferTypes(source, target.asWildcard().getBoundedType(), mappings); + return; + } + return; + } + if (source.isArray() && target.isTypeVariable()) { + mappings.put(target.asTypeParameter(), source); + return; + } + + if (source.isWildcard() && target.isReferenceType()){ + if (source.asWildcard().isBounded()){ + inferTypes(source.asWildcard().getBoundedType(), target, mappings); + } + return; + } + if (source.isConstraint() && target.isReferenceType()){ + inferTypes(source.asConstraintType().getBound(), target, mappings); + return; + } + + if (source.isConstraint() && target.isTypeVariable()){ + inferTypes(source.asConstraintType().getBound(), target, mappings); + return; + } + if (source.isTypeVariable() && target.isTypeVariable()) { + mappings.put(target.asTypeParameter(), source); + return; + } + if (source.isTypeVariable()) { + inferTypes(target, source, mappings); + return; + } + if (source.isPrimitive() || target.isPrimitive()) { + return; + } + if (source.isNull()) { + return; + } + throw new RuntimeException(source.describe() + " " + target.describe()); + } + + private MethodUsage resolveMethodTypeParameters(MethodUsage methodUsage, List actualParamTypes) { + Map matchedTypeParameters = new HashMap<>(); + + if (methodUsage.getDeclaration().hasVariadicParameter()) { + if (actualParamTypes.size() == methodUsage.getDeclaration().getNumberOfParams()) { + // the varargs parameter is an Array, so extract the inner type + ResolvedType expectedType = + methodUsage.getDeclaration().getLastParam().getType().asArrayType().getComponentType(); + // the varargs corresponding type can be either T or Array + ResolvedType actualType = + actualParamTypes.get(actualParamTypes.size() - 1).isArray() ? + actualParamTypes.get(actualParamTypes.size() - 1).asArrayType().getComponentType() : + actualParamTypes.get(actualParamTypes.size() - 1); + if (!expectedType.isAssignableBy(actualType)) { + for (ResolvedTypeParameterDeclaration tp : methodUsage.getDeclaration().getTypeParameters()) { + expectedType = MethodResolutionLogic.replaceTypeParam(expectedType, tp, typeSolver); + } + } + if (!expectedType.isAssignableBy(actualType)) { + // ok, then it needs to be wrapped + throw new UnsupportedOperationException( + String.format("Unable to resolve the type typeParametersValues in a MethodUsage. Expected type: %s, Actual type: %s. Method Declaration: %s. MethodUsage: %s", + expectedType, + actualType, + methodUsage.getDeclaration(), + methodUsage)); + } + // match only the varargs type + matchTypeParameters(expectedType, actualType, matchedTypeParameters); + } else { + return methodUsage; + } + } + + int until = methodUsage.getDeclaration().hasVariadicParameter() ? + actualParamTypes.size() - 1 : + actualParamTypes.size(); + + for (int i = 0; i < until; i++) { + ResolvedType expectedType = methodUsage.getParamType(i); + ResolvedType actualType = actualParamTypes.get(i); + matchTypeParameters(expectedType, actualType, matchedTypeParameters); + } + for (ResolvedTypeParameterDeclaration tp : matchedTypeParameters.keySet()) { + methodUsage = methodUsage.replaceTypeParameter(tp, matchedTypeParameters.get(tp)); + } + return methodUsage; + } + + private void matchTypeParameters(ResolvedType expectedType, ResolvedType actualType, Map matchedTypeParameters) { + if (expectedType.isTypeVariable()) { + ResolvedType type = actualType; + // in case of primitive type, the expected type must be compared with the boxed type of the actual type + if (type.isPrimitive()) { + type = MyObjectProvider.INSTANCE.byName(type.asPrimitive().getBoxTypeQName()); + } + /* + * "a value of the null type (the null reference is the only such value) may be assigned to any reference type, resulting in a null reference of that type" + * https://docs.oracle.com/javase/specs/jls/se15/html/jls-5.html#jls-5.2 + */ + if (type.isNull()) { + type = MyObjectProvider.INSTANCE.object(); + } + if (!type.isTypeVariable() && !type.isReferenceType() && !type.isArray()) { + throw new UnsupportedOperationException(type.getClass().getCanonicalName()); + } + matchedTypeParameters.put(expectedType.asTypeParameter(), type); + } else if (expectedType.isArray()) { + // Issue 2258 : NullType must not fail this search + if (!(actualType.isArray() || actualType.isNull())) { + throw new UnsupportedOperationException(actualType.getClass().getCanonicalName()); + } + matchTypeParameters( + expectedType.asArrayType().getComponentType(), + actualType.isNull() ? actualType : actualType.asArrayType().getComponentType(), + matchedTypeParameters); + } else if (expectedType.isReferenceType()) { + // avoid cases where the actual type has no type parameters but the expected one has. Such as: "classX extends classY" + if (actualType.isReferenceType() && actualType.asReferenceType().typeParametersValues().size() > 0) { + int i = 0; + for (ResolvedType tp : expectedType.asReferenceType().typeParametersValues()) { + matchTypeParameters(tp, actualType.asReferenceType().typeParametersValues().get(i), matchedTypeParameters); + i++; + } + } + } else if (expectedType.isPrimitive()) { + // nothing to do + } else if (expectedType.isWildcard()) { + // nothing to do + } else { + throw new UnsupportedOperationException(expectedType.getClass().getCanonicalName()); + } + } + + private Optional solveMethodAsUsage(ResolvedTypeVariable tp, String name, List argumentsTypes, Context invokationContext) { + List bounds = tp.asTypeParameter().getBounds(); + + if (bounds.isEmpty()) { + // if the bounds of a type parameter are empty, then the bound is implicitly "extends Object" + // we don't make this _ex_plicit in the data representation because that would affect codegen + // and make everything generate like instead of + // https://github.com/javaparser/javaparser/issues/2044 + bounds = Collections.singletonList( + ResolvedTypeParameterDeclaration.Bound.extendsBound( + JavaParserFacade.get(typeSolver).classToResolvedType(Object.class))); + } + + for (ResolvedTypeParameterDeclaration.Bound bound : bounds) { + Optional methodUsage = solveMethodAsUsage(bound.getType(), name, argumentsTypes, invokationContext); + if (methodUsage.isPresent()) { + return methodUsage; + } + } + + return Optional.empty(); + } + + private Optional solveMethodAsUsage(ResolvedType type, String name, List argumentsTypes, Context invokationContext) { + if (type instanceof ResolvedReferenceType) { + return solveMethodAsUsage((ResolvedReferenceType) type, name, argumentsTypes, invokationContext); + } else if (type instanceof ResolvedTypeVariable) { + return solveMethodAsUsage((ResolvedTypeVariable) type, name, argumentsTypes, invokationContext); + } else if (type instanceof ResolvedWildcard) { + ResolvedWildcard wildcardUsage = (ResolvedWildcard) type; + if (wildcardUsage.isSuper()) { + return solveMethodAsUsage(wildcardUsage.getBoundedType(), name, argumentsTypes, invokationContext); + } else if (wildcardUsage.isExtends()) { + return solveMethodAsUsage(wildcardUsage.getBoundedType(), name, argumentsTypes, invokationContext); + } else { + return solveMethodAsUsage(new ReferenceTypeImpl(new ReflectionClassDeclaration(Object.class, typeSolver), typeSolver), name, argumentsTypes, invokationContext); + } + } else if (type instanceof ResolvedLambdaConstraintType){ + ResolvedLambdaConstraintType constraintType = (ResolvedLambdaConstraintType) type; + return solveMethodAsUsage(constraintType.getBound(), name, argumentsTypes, invokationContext); + } else if (type instanceof ResolvedArrayType) { + // An array inherits methods from Object not from it's component type + return solveMethodAsUsage(new ReferenceTypeImpl(new ReflectionClassDeclaration(Object.class, typeSolver), typeSolver), name, argumentsTypes, invokationContext); + } else if (type instanceof ResolvedUnionType) { + Optional commonAncestor = type.asUnionType().getCommonAncestor(); + if (commonAncestor.isPresent()) { + return solveMethodAsUsage(commonAncestor.get(), name, argumentsTypes, invokationContext); + } else { + throw new UnsupportedOperationException("no common ancestor available for " + type.describe()); + } + } else { + throw new UnsupportedOperationException("type usage: " + type.getClass().getCanonicalName()); + } + } + + private ResolvedType usingParameterTypesFromScope(ResolvedType scope, ResolvedType type, Map inferredTypes) { + if (type.isReferenceType()) { + for (Pair entry : type.asReferenceType().getTypeParametersMap()) { + if (entry.a.declaredOnType() && scope.isReferenceType() && scope.asReferenceType().getGenericParameterByName(entry.a.getName()).isPresent()) { + type = type.replaceTypeVariables(entry.a, scope.asReferenceType().getGenericParameterByName(entry.a.getName()).get(), inferredTypes); + } + } + return type; + } else { + return type; + } + } + + private ResolvedType applyInferredTypes(ResolvedType type, Map inferredTypes) { + for (ResolvedTypeParameterDeclaration tp : inferredTypes.keySet()) { + type = type.replaceTypeVariables(tp, inferredTypes.get(tp), inferredTypes); + } + return type; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/MethodContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/MethodContext.java new file mode 100644 index 0000000000000000000000000000000000000000..a23f131af79ad0e4adc6d185f4efde42453940a0 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/MethodContext.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.body.Parameter; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.Collections; +import java.util.List; + +/** + * @author Federico Tomassetti + */ +public class MethodContext extends AbstractMethodLikeDeclarationContext { + + /// + /// Constructors + /// + + public MethodContext(MethodDeclaration wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + + @Override + public List parametersExposedToChild(Node child) { + // TODO/FIXME: Presumably the parameters must be exposed to all children and their descendants, not just the direct child? + if (wrappedNode.getBody().isPresent() && child == wrappedNode.getBody().get()) { + return wrappedNode.getParameters(); + } + return Collections.emptyList(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/MethodReferenceExprContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/MethodReferenceExprContext.java new file mode 100644 index 0000000000000000000000000000000000000000..11e160310d31afec0c53602ec68ea9aedfb1400b --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/MethodReferenceExprContext.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.body.VariableDeclarator; +import com.github.javaparser.ast.expr.Expression; +import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.ast.expr.MethodReferenceExpr; +import com.github.javaparser.ast.stmt.ReturnStmt; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedLambdaConstraintType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.logic.FunctionalInterfaceLogic; +import com.github.javaparser.symbolsolver.logic.InferenceContext; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.symbolsolver.reflectionmodel.MyObjectProvider; +import com.github.javaparser.symbolsolver.resolution.MethodResolutionLogic; + +import java.util.*; + +import static com.github.javaparser.symbolsolver.javaparser.Navigator.demandParentNode; + +public class MethodReferenceExprContext extends AbstractJavaParserContext { + + /// + /// Constructors + /// + + public MethodReferenceExprContext(MethodReferenceExpr wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + /// + /// Public methods + /// + + @Override + public SymbolReference solveMethod(String name, List argumentsTypes, boolean staticOnly) { + if ("new".equals(name)) { + throw new UnsupportedOperationException("Constructor calls not yet resolvable"); + } + + argumentsTypes.addAll(inferArgumentTypes()); + + Collection rrtds = findTypeDeclarations(Optional.of(wrappedNode.getScope())); + + if (rrtds.isEmpty()) { + // if the bounds of a type parameter are empty, then the bound is implicitly "extends Object" + // we don't make this _ex_plicit in the data representation because that would affect codegen + // and make everything generate like instead of + // https://github.com/javaparser/javaparser/issues/2044 + rrtds = Collections.singleton(typeSolver.getSolvedJavaLangObject()); + } + + for (ResolvedReferenceTypeDeclaration rrtd : rrtds) { + SymbolReference firstResAttempt = MethodResolutionLogic.solveMethodInType(rrtd, name, argumentsTypes, false); + if (firstResAttempt.isSolved()) { + return firstResAttempt; + } else { + // If has not already been solved above then will be solved here if single argument type same as + // (or subclass of) rrtd, as call is actually performed on the argument itself with zero params + SymbolReference secondResAttempt = MethodResolutionLogic.solveMethodInType(rrtd, name, Collections.emptyList(), false); + if (secondResAttempt.isSolved()) { + return secondResAttempt; + } + } + } + + return SymbolReference.unsolved(ResolvedMethodDeclaration.class); + } + + /// + /// Private methods + /// + + private List inferArgumentTypes() { + if (demandParentNode(wrappedNode) instanceof MethodCallExpr) { + MethodCallExpr methodCallExpr = (MethodCallExpr) demandParentNode(wrappedNode); + MethodUsage methodUsage = JavaParserFacade.get(typeSolver).solveMethodAsUsage(methodCallExpr); + int pos = pos(methodCallExpr, wrappedNode); + ResolvedType lambdaType = methodUsage.getParamTypes().get(pos); + + // Get the functional method in order for us to resolve it's type arguments properly + Optional functionalMethodOpt = FunctionalInterfaceLogic.getFunctionalMethod(lambdaType); + if (functionalMethodOpt.isPresent()) { + MethodUsage functionalMethod = functionalMethodOpt.get(); + + List resolvedTypes = new ArrayList<>(); + + for (ResolvedType type : functionalMethod.getParamTypes()) { + InferenceContext inferenceContext = new InferenceContext(MyObjectProvider.INSTANCE); + + // Resolve each type variable of the lambda, and use this later to infer the type of each + // implicit parameter + inferenceContext.addPair(new ReferenceTypeImpl(functionalMethod.declaringType(), typeSolver), lambdaType); + + // Now resolve the argument type using the inference context + ResolvedType argType = inferenceContext.resolve(inferenceContext.addSingle(type)); + + ResolvedLambdaConstraintType conType; + if (argType.isWildcard()){ + conType = ResolvedLambdaConstraintType.bound(argType.asWildcard().getBoundedType()); + } else { + conType = ResolvedLambdaConstraintType.bound(argType); + } + + resolvedTypes.add(conType); + } + + return resolvedTypes; + } else { + throw new UnsupportedOperationException(); + } + } else if (demandParentNode(wrappedNode) instanceof VariableDeclarator) { + VariableDeclarator variableDeclarator = (VariableDeclarator) demandParentNode(wrappedNode); + ResolvedType t = JavaParserFacade.get(typeSolver).convertToUsageVariableType(variableDeclarator); + Optional functionalMethod = FunctionalInterfaceLogic.getFunctionalMethod(t); + if (functionalMethod.isPresent()) { + List resolvedTypes = new ArrayList<>(); + for (ResolvedType lambdaType : functionalMethod.get().getParamTypes()) { + // Replace parameter from declarator + Map inferredTypes = new HashMap<>(); + if (lambdaType.isReferenceType()) { + for (com.github.javaparser.utils.Pair entry : lambdaType.asReferenceType().getTypeParametersMap()) { + if (entry.b.isTypeVariable() && entry.b.asTypeParameter().declaredOnType()) { + ResolvedType ot = t.asReferenceType().typeParametersMap().getValue(entry.a); + lambdaType = lambdaType.replaceTypeVariables(entry.a, ot, inferredTypes); + } + } + } else if (lambdaType.isTypeVariable() && lambdaType.asTypeParameter().declaredOnType()) { + lambdaType = t.asReferenceType().typeParametersMap().getValue(lambdaType.asTypeParameter()); + } + resolvedTypes.add(lambdaType); + } + + return resolvedTypes; + } else { + throw new UnsupportedOperationException(); + } + } else if (demandParentNode(wrappedNode) instanceof ReturnStmt) { + ReturnStmt returnStmt = (ReturnStmt) demandParentNode(wrappedNode); + Optional optDeclaration = returnStmt.findAncestor(MethodDeclaration.class); + if (optDeclaration.isPresent()) { + ResolvedType t = JavaParserFacade.get(typeSolver).convertToUsage(optDeclaration.get().asMethodDeclaration().getType()); + Optional functionalMethod = FunctionalInterfaceLogic.getFunctionalMethod(t); + if (functionalMethod.isPresent()) { + List resolvedTypes = new ArrayList<>(); + for (ResolvedType lambdaType : functionalMethod.get().getParamTypes()) { + // Replace parameter from declarator + Map inferredTypes = new HashMap<>(); + if (lambdaType.isReferenceType()) { + for (com.github.javaparser.utils.Pair entry : lambdaType.asReferenceType().getTypeParametersMap()) { + if (entry.b.isTypeVariable() && entry.b.asTypeParameter().declaredOnType()) { + ResolvedType ot = t.asReferenceType().typeParametersMap().getValue(entry.a); + lambdaType = lambdaType.replaceTypeVariables(entry.a, ot, inferredTypes); + } + } + } else if (lambdaType.isTypeVariable() && lambdaType.asTypeParameter().declaredOnType()) { + lambdaType = t.asReferenceType().typeParametersMap().getValue(lambdaType.asTypeParameter()); + } + resolvedTypes.add(lambdaType); + } + + return resolvedTypes; + } else { + throw new UnsupportedOperationException(); + } + } + throw new UnsupportedOperationException(); + } else { + throw new UnsupportedOperationException(); + } + } + + private int pos(MethodCallExpr callExpr, Expression param) { + int i = 0; + for (Expression p : callExpr.getArguments()) { + if (p == param) { + return i; + } + i++; + } + throw new IllegalArgumentException(); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ObjectCreationContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ObjectCreationContext.java new file mode 100644 index 0000000000000000000000000000000000000000..ccf844d4dd1b863b9c85b867bffeabfa16d847f9 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/ObjectCreationContext.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.expr.Expression; +import com.github.javaparser.ast.expr.ObjectCreationExpr; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFactory; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.List; + +import static com.github.javaparser.symbolsolver.javaparser.Navigator.demandParentNode; + +/** + * @author Federico Tomassetti + */ +public class ObjectCreationContext extends AbstractJavaParserContext { + + public ObjectCreationContext(ObjectCreationExpr wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + @Override + public SymbolReference solveType(String name) { + if (wrappedNode.hasScope()) { + Expression scope = wrappedNode.getScope().get(); + ResolvedType scopeType = JavaParserFacade.get(typeSolver).getType(scope); + // Be careful, the scope can be an object creation expression like new inner().new Other() + if (scopeType.isReferenceType() && scopeType.asReferenceType().getTypeDeclaration().isPresent()) { + ResolvedReferenceTypeDeclaration scopeTypeDeclaration = scopeType.asReferenceType().getTypeDeclaration().get(); + for (ResolvedTypeDeclaration it : scopeTypeDeclaration.internalTypes()) { + if (it.getName().equals(name)) { + return SymbolReference.solved(it); + } + } + } + } + // find first parent node that is not an object creation expression to avoid stack overflow errors, see #1711 + Node parentNode = demandParentNode(wrappedNode); + while (parentNode instanceof ObjectCreationExpr) { + parentNode = demandParentNode(parentNode); + } + return JavaParserFactory.getContext(parentNode, typeSolver).solveType(name); + } + + @Override + public SymbolReference solveSymbol(String name) { + return JavaParserFactory.getContext(demandParentNode(wrappedNode), typeSolver).solveSymbol(name); + } + + @Override + public SymbolReference solveMethod(String name, List argumentsTypes, boolean staticOnly) { + return JavaParserFactory.getContext(demandParentNode(wrappedNode), typeSolver).solveMethod(name, argumentsTypes, false); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/StatementContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/StatementContext.java new file mode 100644 index 0000000000000000000000000000000000000000..a7ed3d2f2f4f0471f1fc8333d63e379a0388859c --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/StatementContext.java @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.ConstructorDeclaration; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.expr.LambdaExpr; +import com.github.javaparser.ast.expr.PatternExpr; +import com.github.javaparser.ast.nodeTypes.NodeWithStatements; +import com.github.javaparser.ast.stmt.Statement; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFactory; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserSymbolDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.resolution.Value; +import com.github.javaparser.symbolsolver.resolution.SymbolDeclarator; + +import java.util.Collections; +import java.util.List; +import java.util.ListIterator; +import java.util.Optional; + +/** + * @author Federico Tomassetti + */ +public class StatementContext extends AbstractJavaParserContext { + + public StatementContext(N wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + public static SymbolReference solveInBlock(String name, TypeSolver typeSolver, Statement stmt) { + Optional optionalParentNode = stmt.getParentNode(); + if(!optionalParentNode.isPresent()) { + return SymbolReference.unsolved(ResolvedValueDeclaration.class); + } + + Node parentOfWrappedNode = optionalParentNode.get(); + if (!(parentOfWrappedNode instanceof NodeWithStatements)) { + throw new IllegalArgumentException(); + } + + NodeWithStatements blockStmt = (NodeWithStatements) parentOfWrappedNode; + int position = -1; + for (int i = 0; i < blockStmt.getStatements().size(); i++) { + if (blockStmt.getStatements().get(i).equals(stmt)) { + position = i; + } + } + if (position == -1) { + throw new RuntimeException(); + } + for (int i = position - 1; i >= 0; i--) { + SymbolDeclarator symbolDeclarator = JavaParserFactory.getSymbolDeclarator(blockStmt.getStatements().get(i), typeSolver); + SymbolReference symbolReference = solveWith(symbolDeclarator, name); + if (symbolReference.isSolved()) { + return symbolReference; + } + } + + // if nothing is found we should ask the parent context + return JavaParserFactory.getContext(parentOfWrappedNode, typeSolver).solveSymbol(name); + } + + public static Optional solveInBlockAsValue(String name, TypeSolver typeSolver, Statement stmt) { + Optional optionalParentNode = stmt.getParentNode(); + if(!optionalParentNode.isPresent()) { + return Optional.empty(); + } + + Node parentOfWrappedNode = optionalParentNode.get(); + if (!(parentOfWrappedNode instanceof NodeWithStatements)) { + throw new IllegalArgumentException(); + } + + NodeWithStatements blockStmt = (NodeWithStatements) parentOfWrappedNode; + int position = -1; + for (int i = 0; i < blockStmt.getStatements().size(); i++) { + if (blockStmt.getStatements().get(i).equals(stmt)) { + position = i; + } + } + if (position == -1) { + throw new RuntimeException(); + } + for (int i = position - 1; i >= 0; i--) { + SymbolDeclarator symbolDeclarator = JavaParserFactory.getSymbolDeclarator(blockStmt.getStatements().get(i), typeSolver); + SymbolReference symbolReference = solveWith(symbolDeclarator, name); + if (symbolReference.isSolved()) { + return Optional.of(Value.from(symbolReference.getCorrespondingDeclaration())); + } + } + + // if nothing is found we should ask the parent context + return JavaParserFactory.getContext(parentOfWrappedNode, typeSolver).solveSymbolAsValue(name); + } + + @Override + public Optional solveSymbolAsValue(String name) { + + // if we're in a multiple Variable declaration line (for ex: double a=0, b=a;) + SymbolDeclarator symbolDeclarator = JavaParserFactory.getSymbolDeclarator(wrappedNode, typeSolver); + Optional symbolReference = solveWithAsValue(symbolDeclarator, name); + if (symbolReference.isPresent()) { + return symbolReference; + } + + // If there is no parent + if(!getParent().isPresent()) { + return Optional.empty(); + } + Context parentContext = getParent().get(); + + Optional optionalParentNode = wrappedNode.getParentNode(); + if(!optionalParentNode.isPresent()) { + return Optional.empty(); + } + + Node parentOfWrappedNode = optionalParentNode.get(); + + // we should look in all the statements preceding, treating them as SymbolDeclarators + if (parentOfWrappedNode instanceof MethodDeclaration) { + return parentContext.solveSymbolAsValue(name); + }else if (parentOfWrappedNode instanceof LambdaExpr) { + return parentContext.solveSymbolAsValue(name); + } else if (!(parentOfWrappedNode instanceof NodeWithStatements)) { + return parentContext.solveSymbolAsValue(name); + } + + NodeWithStatements nodeWithStmt = (NodeWithStatements) parentOfWrappedNode; + int position = -1; + + // Get the position of the wrapped node. + for (int i = 0; i < nodeWithStmt.getStatements().size(); i++) { + if (nodeWithStmt.getStatements().get(i).equals(wrappedNode)) { + position = i; + } + } + if (position == -1) { + throw new RuntimeException(); + } + + // Working backwards from the node, try to solve the symbol. This limits the scope to declarations that appear prior to usage. + for (int statementIndex = position - 1; statementIndex >= 0; statementIndex--) { + Statement statement = nodeWithStmt.getStatements().get(statementIndex); + symbolDeclarator = JavaParserFactory.getSymbolDeclarator(statement, typeSolver); + symbolReference = solveWithAsValue(symbolDeclarator, name); + if (symbolReference.isPresent()) { + return symbolReference; + } + } + + // If nothing is found we should ask the parent context. + return solveSymbolAsValueInParentContext(name); + } + + @Override + protected Optional solveWithAsValue(SymbolDeclarator symbolDeclarator, String name) { +// symbolDeclarator.getSymbolDeclarations().get(0). +// ResolvedValueDeclaration resolvedValueDeclaration = symbolDeclarator.getSymbolDeclarations().get(0); +// boolean isVariable = resolvedValueDeclaration.isVariable(); + // TODO: Try to get the context of the declarator / initialisations -- then check if the declarations themselves match (or vice versa) + return super.solveWithAsValue(symbolDeclarator, name); + } + + @Override + public SymbolReference solveSymbol(String name) { + return solveSymbol(name, true); + } + + /** + * Used where a symbol is being used (e.g. solving {@code x} when used as an argument {@code doubleThis(x)}, or calculation {@code return x * 2;}). + * @param name the variable / reference / identifier used. + * @param iterateAdjacentStmts flag to iterate adjacent statements, should be set to {@code true} except when calling itself in order to prevent revisiting already visited symbols. + * @return // FIXME: Better documentation on how this is different to solveSymbolAsValue() + */ + private SymbolReference solveSymbol(String name, boolean iterateAdjacentStmts) { + + /* + * If we're in a variable declaration line. + * Example: {@code double a=0, b=a;} + * Example: {@code a instanceof String s;} + */ + SymbolDeclarator symbolDeclarator = JavaParserFactory.getSymbolDeclarator(wrappedNode, typeSolver); + SymbolReference symbolReference = solveWith(symbolDeclarator, name); + if (symbolReference.isSolved()) { + return symbolReference; + } + + /* + * If we're in a statement that contains a pattern expression. + * Example: {@code double x = a instanceof String s;} + */ + List patternExprs = patternExprsExposedFromChildren(); + for (int i = 0; i < patternExprs.size(); i++) { + PatternExpr patternExpr = patternExprs.get(i); + if(patternExpr.getNameAsString().equals(name)) { + return SymbolReference.solved(JavaParserSymbolDeclaration.patternVar(patternExpr, typeSolver)); + } + } + + Optional optionalParentNode = wrappedNode.getParentNode(); + if(!optionalParentNode.isPresent()) { + return SymbolReference.unsolved(ResolvedValueDeclaration.class); + } + + Node parentOfWrappedNode = optionalParentNode.get(); + + // we should look in all the statements preceding, treating them as SymbolDeclarators + if (parentOfWrappedNode instanceof MethodDeclaration) { + return solveSymbolInParentContext(name); + } else if (parentOfWrappedNode instanceof ConstructorDeclaration) { + return solveSymbolInParentContext(name); + } else if (parentOfWrappedNode instanceof LambdaExpr) { + return solveSymbolInParentContext(name); + } else if (parentOfWrappedNode instanceof NodeWithStatements) { + // If we choose to not solve adjacent statements abort the solution process here. + // In the calling context (the context that calls this) we will attempt to + // resolve all prior adjacent statements, and then the common parent as the fallback. + // Then the common parent will check all of its prior adjacent statements, etc. + + // Further below is a more detailed explanation for why we may want to disable this visitation of adjacent statements + // to prevent revisiting the same contexts over and over again. + if (!iterateAdjacentStmts) { + return SymbolReference.unsolved(ResolvedValueDeclaration.class); + } + + NodeWithStatements nodeWithStmt = (NodeWithStatements) parentOfWrappedNode; + + // Assuming the wrapped node exists within the parent's collection of statements... + int position = nodeWithStmt.getStatements().indexOf(wrappedNode); + if (position == -1) { + throw new IllegalStateException("This node is not a statement within the current NodeWithStatements"); + } + + // Start at the current node and work backwards... + ListIterator statementListIterator = nodeWithStmt.getStatements().listIterator(position); + while(statementListIterator.hasPrevious()) { + Context prevContext = JavaParserFactory.getContext(statementListIterator.previous(), typeSolver); + if (prevContext instanceof StatementContext) { + // We have an explicit check for "StatementContext" to prevent a factorial increase of visited statements. + // + // For example consider the following: + // String a = "a"; + // String b = "b"; + // String c = get(); + // + // If we simply call "prevContext.solveSymbol(name)" we will call the current method with the adjacent statement "prevContext". + // Then "prevContext" will look at its previous adjacent statement. And so on and so forth. + // When there are no more previous statements in this chain of method calls, we come back to here... + // Then we look at the next "prevContext" which causes the entire process to start again. + // This is how we get a factorial increase in calls to "solveSymbol". + // + // So what we do instead with this check is we pass in a flag to say "Do not look at previous adjacent statements". + // Since each visited "prevContext" does not look at its adjacent statements we only visit each statement once in this while loop. + symbolReference = ((StatementContext)prevContext).solveSymbol(name, false); + } else { + symbolReference = prevContext.solveSymbol(name); + } + if (symbolReference.isSolved()) { + return symbolReference; + } + } + } + + // If nothing is found, attempt to solve within the parent context + return solveSymbolInParentContext(name); + } + + @Override + public SymbolReference solveMethod(String name, List argumentsTypes, boolean staticOnly) { + // TODO: Document why staticOnly is forced to be false. + return solveMethodInParentContext(name, argumentsTypes, false); + } + + + @Override + public List patternExprsExposedFromChildren() { + // Statements never make pattern expressions available. + return Collections.emptyList(); + + } + + @Override + public List negatedPatternExprsExposedFromChildren() { + // Statements never make pattern expressions available. + return Collections.emptyList(); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/SwitchEntryContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/SwitchEntryContext.java new file mode 100644 index 0000000000000000000000000000000000000000..0c43fb4701bb1f898b9313272a89e75f9061b8eb --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/SwitchEntryContext.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.stmt.Statement; +import com.github.javaparser.ast.stmt.SwitchEntry; +import com.github.javaparser.ast.stmt.SwitchStmt; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +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.symbolsolver.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.symbolsolver.resolution.SymbolDeclarator; + +import java.util.List; + +import static com.github.javaparser.symbolsolver.javaparser.Navigator.demandParentNode; + +/** + * @author Federico Tomassetti + */ +public class SwitchEntryContext extends AbstractJavaParserContext { + + public SwitchEntryContext(SwitchEntry wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + @Override + public SymbolReference solveSymbol(String name) { + SwitchStmt switchStmt = (SwitchStmt) demandParentNode(wrappedNode); + ResolvedType type = JavaParserFacade.get(typeSolver).getType(switchStmt.getSelector()); + if (type.isReferenceType() && type.asReferenceType().getTypeDeclaration().isPresent()) { + ResolvedReferenceTypeDeclaration typeDeclaration = type.asReferenceType().getTypeDeclaration().get(); + if (typeDeclaration.isEnum()) { + if (type instanceof ReferenceTypeImpl) { + ReferenceTypeImpl referenceType = (ReferenceTypeImpl) type; + if(referenceType.getTypeDeclaration().isPresent()) { + ResolvedReferenceTypeDeclaration typeUsageTypeDeclaration = referenceType.getTypeDeclaration().get(); + if (typeUsageTypeDeclaration.asEnum().hasEnumConstant(name)) { + return SymbolReference.solved(typeUsageTypeDeclaration.asEnum().getEnumConstant(name)); + } + if (typeUsageTypeDeclaration.hasField(name)) { + return SymbolReference.solved(typeUsageTypeDeclaration.getField(name)); + } + } else { + // Consider IllegalStateException or similar? + } + } else { + throw new UnsupportedOperationException(); + } + } + } + + // look for declaration in this and previous switch entry statements + for (SwitchEntry seStmt : switchStmt.getEntries()) { + for (Statement stmt : seStmt.getStatements()) { + SymbolDeclarator symbolDeclarator = JavaParserFactory.getSymbolDeclarator(stmt, typeSolver); + SymbolReference symbolReference = solveWith(symbolDeclarator, name); + if (symbolReference.isSolved()) { + return symbolReference; + } + } + // once we reach this switch entry statement, stop: we do not want to look in later switch entry statements + if (seStmt == wrappedNode) { + break; + } + } + + return solveSymbolInParentContext(name); + } + + @Override + public SymbolReference solveMethod(String name, List argumentsTypes, boolean staticOnly) { + // TODO: Document why staticOnly is forced to be false. + return solveMethodInParentContext(name, argumentsTypes, false); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/TryWithResourceContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/TryWithResourceContext.java new file mode 100644 index 0000000000000000000000000000000000000000..0b34562f94cbafa6a7e2cc0d0cf7bf39f49ac9d2 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/TryWithResourceContext.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.body.VariableDeclarator; +import com.github.javaparser.ast.expr.Expression; +import com.github.javaparser.ast.expr.VariableDeclarationExpr; +import com.github.javaparser.ast.stmt.BlockStmt; +import com.github.javaparser.ast.stmt.TryStmt; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserSymbolDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.resolution.Value; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import static com.github.javaparser.symbolsolver.javaparser.Navigator.demandParentNode; + +public class TryWithResourceContext extends AbstractJavaParserContext { + + public TryWithResourceContext(TryStmt wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + @Override + public Optional solveSymbolAsValue(String name) { + for (Expression expr : wrappedNode.getResources()) { + if (expr instanceof VariableDeclarationExpr) { + for (VariableDeclarator v : ((VariableDeclarationExpr)expr).getVariables()) { + if (v.getName().getIdentifier().equals(name)) { + ResolvedValueDeclaration decl = JavaParserSymbolDeclaration.localVar(v, typeSolver); + return Optional.of(Value.from(decl)); + } + } + } + } + + if (demandParentNode(wrappedNode) instanceof BlockStmt) { + return StatementContext.solveInBlockAsValue(name, typeSolver, wrappedNode); + } else { + return solveSymbolAsValueInParentContext(name); + } + } + + @Override + public SymbolReference solveSymbol(String name) { + for (Expression expr : wrappedNode.getResources()) { + if (expr instanceof VariableDeclarationExpr) { + for (VariableDeclarator v : ((VariableDeclarationExpr)expr).getVariables()) { + if (v.getName().getIdentifier().equals(name)) { + return SymbolReference.solved(JavaParserSymbolDeclaration.localVar(v, typeSolver)); + } + } + } + } + + if (demandParentNode(wrappedNode) instanceof BlockStmt) { + return StatementContext.solveInBlock(name, typeSolver, wrappedNode); + } else { + return solveSymbolInParentContext(name); + } + } + + @Override + public SymbolReference solveMethod(String name, List argumentsTypes, boolean staticOnly) { + // TODO: Document why staticOnly is forced to be false. + return solveMethodInParentContext(name, argumentsTypes, false); + } + + @Override + public List localVariablesExposedToChild(Node child) { + NodeList resources = wrappedNode.getResources(); + for (int i=0;i e instanceof VariableDeclarationExpr ? ((VariableDeclarationExpr) e).getVariables() + : Collections.emptyList()) + .flatMap(List::stream) + .collect(Collectors.toList()); + } + } + if (child == wrappedNode.getTryBlock()) { + List res = new LinkedList<>(); + for (Expression expr : resources) { + if (expr instanceof VariableDeclarationExpr) { + res.addAll(((VariableDeclarationExpr)expr).getVariables()); + } + } + return res; + } + return Collections.emptyList(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/UnaryExprContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/UnaryExprContext.java new file mode 100644 index 0000000000000000000000000000000000000000..bab93c9be6e1aca852b6742ed758ac48b37bcc0e --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/UnaryExprContext.java @@ -0,0 +1,55 @@ +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.expr.PatternExpr; +import com.github.javaparser.ast.expr.UnaryExpr; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFactory; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.ArrayList; +import java.util.List; + +public class UnaryExprContext extends AbstractJavaParserContext { + + public UnaryExprContext(UnaryExpr wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + @Override + public List patternExprsExposedFromChildren() { + List results = new ArrayList<>(); + + // Propagate any pattern expressions "up" + if(wrappedNode.getOperator() == UnaryExpr.Operator.LOGICAL_COMPLEMENT) { + Context innerContext = JavaParserFactory.getContext(wrappedNode.getExpression(), typeSolver); + + // Avoid infinite loop + if(!this.equals(innerContext)) { + // Note that `UnaryExpr.Operator.LOGICAL_COMPLEMENT` is `!` + // Previously negated pattern expressions are now now available (double negatives) -- e.g. if(!!("a" instanceof String s)) {} + results.addAll(innerContext.negatedPatternExprsExposedFromChildren()); + } + } + + return results; + } + + @Override + public List negatedPatternExprsExposedFromChildren() { + List results = new ArrayList<>(); + + // Propagate any pattern expressions "up" + if(wrappedNode.getOperator() == UnaryExpr.Operator.LOGICAL_COMPLEMENT) { + Context innerContext = JavaParserFactory.getContext(wrappedNode.getExpression(), typeSolver); + + if(!this.equals(innerContext)) { + // Note that `UnaryExpr.Operator.LOGICAL_COMPLEMENT` is `!` + // Previously available pattern expressions are now negated (double negatives) -- e.g. if(!("a" instanceof String s)) {} + results.addAll(innerContext.patternExprsExposedFromChildren()); + } + } + + return results; + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/VariableDeclarationExprContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/VariableDeclarationExprContext.java new file mode 100644 index 0000000000000000000000000000000000000000..ba178d109ed69bb04327f733d1bd19ecae46db55 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/VariableDeclarationExprContext.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.VariableDeclarator; +import com.github.javaparser.ast.expr.PatternExpr; +import com.github.javaparser.ast.expr.VariableDeclarationExpr; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserSymbolDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.Collections; +import java.util.List; + +/** + * @author Federico Tomassetti + */ +public class VariableDeclarationExprContext extends AbstractJavaParserContext { + + public VariableDeclarationExprContext(VariableDeclarationExpr wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + public SymbolReference solveSymbol(String name) { + List patternExprs = patternExprsExposedFromChildren(); + for (int i = 0; i < patternExprs.size(); i++) { + PatternExpr patternExpr = patternExprs.get(i); + if(patternExpr.getNameAsString().equals(name)) { + return SymbolReference.solved(JavaParserSymbolDeclaration.patternVar(patternExpr, typeSolver)); + } + } + + // Default to solving in parent context if unable to solve directly here. + return solveSymbolInParentContext(name); + } + + @Override + public List localVariablesExposedToChild(Node child) { + for (int i = 0; i < wrappedNode.getVariables().size(); i++) { + if (child == wrappedNode.getVariable(i)) { + return wrappedNode.getVariables().subList(0, i); + } + } + // TODO: Consider pattern exprs + return Collections.emptyList(); + } + + + + @Override + public List patternExprsExposedFromChildren() { + // Variable declarations never make pattern expressions available. + return Collections.emptyList(); + } + + @Override + public List negatedPatternExprsExposedFromChildren() { + // Variable declarations never make pattern expressions available. + return Collections.emptyList(); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/VariableDeclaratorContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/VariableDeclaratorContext.java new file mode 100644 index 0000000000000000000000000000000000000000..b1aa0a57baa8aaba70ff50d83eb0bc1f3a136a99 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/VariableDeclaratorContext.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.contexts; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.VariableDeclarator; +import com.github.javaparser.ast.expr.PatternExpr; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.Collections; +import java.util.List; + +/** + * @author Federico Tomassetti + */ +public class VariableDeclaratorContext extends AbstractJavaParserContext { + + public VariableDeclaratorContext(VariableDeclarator wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + @Override + public List localVariablesExposedToChild(Node child) { + if (wrappedNode.getInitializer().isPresent() && wrappedNode.getInitializer().get() == child) { + return Collections.singletonList(wrappedNode); + } + + return Collections.emptyList(); + } + + @Override + public List patternExprsExposedFromChildren() { + // Variable declarators never make pattern expressions available. + return Collections.emptyList(); + } + + @Override + public List negatedPatternExprsExposedFromChildren() { + // Variable declarators never make pattern expressions available. + return Collections.emptyList(); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/AstResolutionUtils.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/AstResolutionUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..098149abc38b0608b37a14dcc1248158e825a096 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/AstResolutionUtils.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarations; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.expr.AnnotationExpr; +import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations; +import com.github.javaparser.ast.nodeTypes.NodeWithMembers; +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.ResolvedConstructorDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeDeclaration; +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.google.common.collect.ImmutableList; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +class AstResolutionUtils { + + static String containerName(Node container) { + String packageName = getPackageName(container); + String className = getClassName("", container); + return packageName + + ((!packageName.isEmpty() && !className.isEmpty()) ? "." : "") + + className; + } + + /* + * Returns the package name from a node (that can be null) or an empty string + */ + static String getPackageName(Node container) { + String packageName = ""; + if (container == null) return packageName; + Optional cu = container.findCompilationUnit(); + if (cu.isPresent()) { + packageName = cu.get().getPackageDeclaration().map(pd -> pd.getNameAsString()).orElse(""); + } + return packageName; + } + + static String getClassName(String base, Node container) { + if (container instanceof com.github.javaparser.ast.body.ClassOrInterfaceDeclaration) { + String b = getClassName(base, container.getParentNode().orElse(null)); + String cn = ((com.github.javaparser.ast.body.ClassOrInterfaceDeclaration) container).getName().getId(); + if (b.isEmpty()) { + return cn; + } else { + return b + "." + cn; + } + } else if (container instanceof com.github.javaparser.ast.body.EnumDeclaration) { + String b = getClassName(base, container.getParentNode().orElse(null)); + String cn = ((com.github.javaparser.ast.body.EnumDeclaration) container).getName().getId(); + if (b.isEmpty()) { + return cn; + } else { + return b + "." + cn; + } + } else if (container instanceof com.github.javaparser.ast.body.AnnotationDeclaration) { + String b = getClassName(base, container.getParentNode().orElse(null)); + String cn = ((com.github.javaparser.ast.body.AnnotationDeclaration) container).getName().getId(); + if (b.isEmpty()) { + return cn; + } else { + return b + "." + cn; + } + } else if (container != null) { + return getClassName(base, container.getParentNode().orElse(null)); + } + return base; + } + + static boolean hasDirectlyAnnotation(NodeWithAnnotations nodeWithAnnotations, TypeSolver typeSolver, + String canonicalName) { + for (AnnotationExpr annotationExpr : nodeWithAnnotations.getAnnotations()) { + SymbolReference ref = JavaParserFactory.getContext(annotationExpr, typeSolver) + .solveType(annotationExpr.getNameAsString()); + if (ref.isSolved()) { + if (ref.getCorrespondingDeclaration().getQualifiedName().equals(canonicalName)) { + return true; + } + } else { + throw new UnsolvedSymbolException(annotationExpr.getName().getId()); + } + } + return false; + } + + static List getConstructors( + NodeWithMembers wrappedNode, + TypeSolver typeSolver, + N container) { + List declared = wrappedNode.getConstructors().stream() + .map(c -> new JavaParserConstructorDeclaration(container, c, typeSolver)) + .collect(Collectors.toList()); + if (declared.isEmpty()) { + // If there are no constructors insert the default constructor + return ImmutableList.of(new DefaultConstructorDeclaration(container)); + } else { + return declared; + } + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/DefaultConstructorDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/DefaultConstructorDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..d90452b9d320584c4a9efbae9c715431a20f9f20 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/DefaultConstructorDeclaration.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarations; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.ast.body.ConstructorDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedConstructorDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedParameterDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * This represents the default constructor added by the compiler for objects not declaring one. + * It takes no parameters. See JLS 8.8.9 for details. + * + * @author Federico Tomassetti + */ +public class DefaultConstructorDeclaration implements ResolvedConstructorDeclaration { + + private N declaringType; + + DefaultConstructorDeclaration(N declaringType) { + this.declaringType = declaringType; + } + + @Override + public N declaringType() { + return declaringType; + } + + @Override + public int getNumberOfParams() { + return 0; + } + + @Override + public ResolvedParameterDeclaration getParam(int i) { + throw new UnsupportedOperationException("The default constructor has no parameters"); + } + + @Override + public String getName() { + return declaringType.getName(); + } + + @Override + public AccessSpecifier accessSpecifier() { + return AccessSpecifier.PUBLIC; + } + + @Override + public List getTypeParameters() { + return Collections.emptyList(); + } + + @Override + public int getNumberOfSpecifiedExceptions() { + return 0; + } + + @Override + public ResolvedType getSpecifiedException(int index) { + throw new UnsupportedOperationException("The default constructor does not throw exceptions"); + } + + @Override + public Optional toAst() { + return Optional.empty(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserAnnotationDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserAnnotationDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..d5159e2ac2127624cfecab3ae426d24587d1bf15 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserAnnotationDeclaration.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarations; + +import com.github.javaparser.ast.body.AnnotationDeclaration; +import com.github.javaparser.ast.body.AnnotationMemberDeclaration; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.logic.AbstractTypeDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; + +import java.lang.annotation.Inherited; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +public class JavaParserAnnotationDeclaration extends AbstractTypeDeclaration implements ResolvedAnnotationDeclaration { + + private com.github.javaparser.ast.body.AnnotationDeclaration wrappedNode; + private TypeSolver typeSolver; + private JavaParserTypeAdapter javaParserTypeAdapter; + + public JavaParserAnnotationDeclaration(AnnotationDeclaration wrappedNode, TypeSolver typeSolver) { + this.wrappedNode = wrappedNode; + this.typeSolver = typeSolver; + this.javaParserTypeAdapter = new JavaParserTypeAdapter<>(wrappedNode, typeSolver); + } + + @Override + public List getAncestors(boolean acceptIncompleteList) { + List ancestors = new ArrayList<>(); + ancestors.add(new ReferenceTypeImpl(typeSolver.solveType("java.lang.annotation.Annotation"), typeSolver)); + return ancestors; + } + + @Override + public Set internalTypes() { + return javaParserTypeAdapter.internalTypes(); + } + + @Override + public List getAllFields() { + return wrappedNode.getFields().stream() + .flatMap(field -> field.getVariables().stream()) + .map(var -> new JavaParserFieldDeclaration(var, typeSolver)) + .collect(Collectors.toList()); + } + + @Override + public Set getDeclaredMethods() { + // TODO #1838 + throw new UnsupportedOperationException(); + } + + @Override + public boolean isAssignableBy(ResolvedType type) { + // TODO #1836 + throw new UnsupportedOperationException(); + } + + @Override + public boolean isAssignableBy(ResolvedReferenceTypeDeclaration other) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasDirectlyAnnotation(String canonicalName) { + return AstResolutionUtils.hasDirectlyAnnotation(wrappedNode, typeSolver, canonicalName); + } + + @Override + public String getPackageName() { + return AstResolutionUtils.getPackageName(wrappedNode); + } + + @Override + public String getClassName() { + return AstResolutionUtils.getClassName("", wrappedNode); + } + + @Override + public String getQualifiedName() { + String containerName = AstResolutionUtils.containerName(wrappedNode.getParentNode().orElse(null)); + if (containerName.isEmpty()) { + return wrappedNode.getName().getId(); + } else { + return containerName + "." + wrappedNode.getName(); + } + } + + @Override + public String getName() { + return wrappedNode.getName().getId(); + } + + /** + * Annotation declarations cannot have type parameters and hence this method always returns an empty list. + * + * @return An empty list. + */ + @Override + public List getTypeParameters() { + // Annotation declarations cannot have type parameters - i.e. we can always return an empty list. + return Collections.emptyList(); + } + + @Override + public Optional containerType() { + // TODO #1841 + throw new UnsupportedOperationException("containerType is not supported for " + this.getClass().getCanonicalName()); + } + + @Override + public List getAnnotationMembers() { + return wrappedNode.getMembers().stream() + .filter(m -> m instanceof AnnotationMemberDeclaration) + .map(m -> new JavaParserAnnotationMemberDeclaration((AnnotationMemberDeclaration)m, typeSolver)) + .collect(Collectors.toList()); + } + + @Override + public List getConstructors() { + return Collections.emptyList(); + } + + @Override + public Optional toAst() { + return Optional.of(wrappedNode); + } + + @Override + public boolean isInheritable() { + return wrappedNode.getAnnotationByClass(Inherited.class).isPresent(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserAnnotationMemberDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserAnnotationMemberDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..67451e1140895cf1d3e283a008b4ec5c69bf3bcf --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserAnnotationMemberDeclaration.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarations; + +import com.github.javaparser.ast.body.AnnotationMemberDeclaration; +import com.github.javaparser.ast.expr.Expression; +import com.github.javaparser.resolution.declarations.ResolvedAnnotationMemberDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFactory; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +/** + * @author Federico Tomassetti + */ +public class JavaParserAnnotationMemberDeclaration implements ResolvedAnnotationMemberDeclaration { + + private com.github.javaparser.ast.body.AnnotationMemberDeclaration wrappedNode; + private TypeSolver typeSolver; + + public AnnotationMemberDeclaration getWrappedNode() { + return wrappedNode; + } + + public JavaParserAnnotationMemberDeclaration(AnnotationMemberDeclaration wrappedNode, TypeSolver typeSolver) { + this.wrappedNode = wrappedNode; + this.typeSolver = typeSolver; + } + + @Override + public Expression getDefaultValue() { + return wrappedNode.getDefaultValue().orElse(null); + } + + @Override + public ResolvedType getType() { + return JavaParserFacade.get(typeSolver).convert(wrappedNode.getType(), getContext()); + } + + @Override + public String getName() { + return wrappedNode.getNameAsString(); + } + + private Context getContext() { + return JavaParserFactory.getContext(wrappedNode, typeSolver); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserAnonymousClassDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserAnonymousClassDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..0ec0bcdba2a6e28f3dd05e62d8b868d083f8f58a --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserAnonymousClassDeclaration.java @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarations; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.FieldDeclaration; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.body.TypeDeclaration; +import com.github.javaparser.ast.expr.ObjectCreationExpr; +import com.github.javaparser.ast.type.ClassOrInterfaceType; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.core.resolution.MethodUsageResolutionCapability; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFactory; +import com.github.javaparser.symbolsolver.javaparsermodel.contexts.ObjectCreationContext; +import com.github.javaparser.symbolsolver.logic.AbstractClassDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * An anonymous class declaration representation. + */ +public class JavaParserAnonymousClassDeclaration extends AbstractClassDeclaration + implements MethodUsageResolutionCapability { + + private final TypeSolver typeSolver; + private final ObjectCreationExpr wrappedNode; + private final ResolvedTypeDeclaration superTypeDeclaration; + private final String name = "Anonymous-" + UUID.randomUUID(); + + public JavaParserAnonymousClassDeclaration(ObjectCreationExpr wrappedNode, + TypeSolver typeSolver) { + this.typeSolver = typeSolver; + this.wrappedNode = wrappedNode; + + ClassOrInterfaceType superType = wrappedNode.getType(); + String superTypeName = superType.getName().getId(); + if (superType.getScope().isPresent()) { + superTypeName = superType.getScope().get().asString() + "." + superTypeName; + } + + Context context = new ObjectCreationContext(wrappedNode, typeSolver); + superTypeDeclaration = context.solveType(superTypeName).getCorrespondingDeclaration(); + } + + public ResolvedTypeDeclaration getSuperTypeDeclaration() { + return superTypeDeclaration; + } + + public List findMembersOfKind(final Class memberClass) { + if (wrappedNode.getAnonymousClassBody().isPresent()) { + return wrappedNode + .getAnonymousClassBody() + .get() + .stream() + .filter(node -> memberClass.isAssignableFrom(node.getClass())) + .map(memberClass::cast) + .collect(Collectors.toList()); + } else { + return Collections.emptyList(); + } + } + + public Context getContext() { + return JavaParserFactory.getContext(wrappedNode, typeSolver); + } + + @Override + public SymbolReference solveMethod(String name, List argumentsTypes, + boolean staticOnly) { + return getContext().solveMethod(name, argumentsTypes, staticOnly); + } + + @Override + public Optional solveMethodAsUsage(String name, List argumentTypes, + Context invocationContext, List typeParameters) { + return getContext().solveMethodAsUsage(name, argumentTypes); + } + + @Override + protected ResolvedReferenceType object() { + return new ReferenceTypeImpl(typeSolver.getSolvedJavaLangObject(), typeSolver); + } + + @Override + public Optional getSuperClass() { + ResolvedReferenceTypeDeclaration superRRTD = superTypeDeclaration.asReferenceType(); + if (superRRTD == null) { + return Optional.empty(); + } + return Optional.of(new ReferenceTypeImpl(superRRTD, typeSolver)); + } + + @Override + public List getInterfaces() { + return superTypeDeclaration.asReferenceType() + .getAncestors() + .stream() + .filter(type -> type.getTypeDeclaration().isPresent()) + .filter(type -> type.getTypeDeclaration().get().isInterface()) + .collect(Collectors.toList()); + } + + @Override + public List getConstructors() { + if (superTypeDeclaration.isInterface()) { + return Collections.singletonList(new DefaultConstructorDeclaration<>(this)); + } + return superTypeDeclaration.asReferenceType().getConstructors(); + } + + @Override + public AccessSpecifier accessSpecifier() { + return AccessSpecifier.PRIVATE; + } + + @Override + public List getAncestors(boolean acceptIncompleteList) { + ImmutableList.Builder builder = ImmutableList.builder(); + + // Only add the super type if it is present (e.g. java.lang.Object has no super class) + getSuperClass().ifPresent(builder::add); + + // All all ancestors of the super type..? + builder.addAll(superTypeDeclaration.asReferenceType().getAncestors(acceptIncompleteList)); + + return builder.build(); + } + + @Override + public List getAllFields() { + + List myFields = findMembersOfKind(FieldDeclaration.class) + .stream() + .flatMap(field -> field.getVariables() + .stream() + .map(variable -> new JavaParserFieldDeclaration(variable, typeSolver)) + ) + .collect(Collectors.toList()); + + + // TODO: Figure out if it is appropriate to remove the orElseThrow() -- if so, how... + List superClassFields = getSuperClass() + .orElseThrow(() -> new RuntimeException("super class unexpectedly empty")) + .getTypeDeclaration() + .orElseThrow(() -> new RuntimeException("TypeDeclaration unexpectedly empty.")) + .getAllFields(); + + // TODO: Figure out if it is appropriate to remove the orElseThrow() -- if so, how... + List interfaceFields = + getInterfaces().stream() + .flatMap(interfaceReferenceType -> interfaceReferenceType + .getTypeDeclaration().orElseThrow(() -> new RuntimeException("TypeDeclaration unexpectedly empty.")) + .getAllFields() + .stream() + ) + .collect(Collectors.toList()); + + return ImmutableList + .builder() + .addAll(myFields) + .addAll(superClassFields) + .addAll(interfaceFields) + .build(); + } + + @Override + public Set getDeclaredMethods() { + return + findMembersOfKind(MethodDeclaration.class) + .stream() + .map(method -> new JavaParserMethodDeclaration(method, typeSolver)) + .collect(Collectors.toSet()); + } + + @Override + public boolean isAssignableBy(ResolvedType type) { + return false; + } + + @Override + public boolean isAssignableBy(ResolvedReferenceTypeDeclaration other) { + return false; + } + + @Override + public boolean hasDirectlyAnnotation(String qualifiedName) { + return false; + } + + @Override + public String getPackageName() { + return AstResolutionUtils.getPackageName(wrappedNode); + } + + @Override + public String getClassName() { + return AstResolutionUtils.getClassName("", wrappedNode); + } + + @Override + public String getQualifiedName() { + String containerName = AstResolutionUtils.containerName(wrappedNode.getParentNode().orElse(null)); + if (containerName.isEmpty()) { + return getName(); + } else { + return containerName + "." + getName(); + } + } + + @Override + public Set internalTypes() { + return + findMembersOfKind(TypeDeclaration.class) + .stream() + .map(typeMember -> JavaParserFacade.get(typeSolver).getTypeDeclaration(typeMember)) + .collect(Collectors.toSet()); + } + + @Override + public String getName() { + return name; + } + + @Override + public List getTypeParameters() { + return Lists.newArrayList(); + } + + @Override + public Optional containerType() { + throw new UnsupportedOperationException("containerType is not supported for " + this.getClass().getCanonicalName()); + } + + @Override + public Optional toAst() { + return Optional.of(wrappedNode); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserClassDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserClassDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..381ba3e15ba45bc678e5f7dfb11715c51dd67542 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserClassDeclaration.java @@ -0,0 +1,482 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarations; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.BodyDeclaration; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.FieldDeclaration; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.type.ClassOrInterfaceType; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.core.resolution.MethodUsageResolutionCapability; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFactory; +import com.github.javaparser.symbolsolver.logic.AbstractClassDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.LazyType; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.symbolsolver.resolution.SymbolSolver; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +public class JavaParserClassDeclaration extends AbstractClassDeclaration implements MethodUsageResolutionCapability { + + /// + /// Fields + /// + + private TypeSolver typeSolver; + private ClassOrInterfaceDeclaration wrappedNode; + private JavaParserTypeAdapter javaParserTypeAdapter; + + /// + /// Constructors + /// + + public JavaParserClassDeclaration(ClassOrInterfaceDeclaration wrappedNode, + TypeSolver typeSolver) { + if (wrappedNode.isInterface()) { + throw new IllegalArgumentException("Interface given"); + } + this.wrappedNode = wrappedNode; + this.typeSolver = typeSolver; + this.javaParserTypeAdapter = new JavaParserTypeAdapter<>(wrappedNode, typeSolver); + } + + /// + /// Public methods: from Object + /// + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + JavaParserClassDeclaration that = (JavaParserClassDeclaration) o; + + return wrappedNode.equals(that.wrappedNode); + } + + @Override + public int hashCode() { + return wrappedNode.hashCode(); + } + + @Override + public String toString() { + return "JavaParserClassDeclaration{" + + "wrappedNode=" + wrappedNode + + '}'; + } + + /// + /// Public methods: fields + /// + + @Override + public List getAllFields() { + List fields = javaParserTypeAdapter.getFieldsForDeclaredVariables(); + + getAncestors(true).stream().filter(ancestor -> ancestor.getTypeDeclaration().isPresent()) + .forEach(ancestor -> ancestor.getTypeDeclaration().get().getAllFields() + .forEach(f -> { + fields.add(new ResolvedFieldDeclaration() { + + @Override + public AccessSpecifier accessSpecifier() { + return f.accessSpecifier(); + } + + @Override + public String getName() { + return f.getName(); + } + + @Override + public ResolvedType getType() { + return ancestor.useThisTypeParametersOnTheGivenType(f.getType()); + } + + @Override + public boolean isStatic() { + return f.isStatic(); + } + + @Override + public boolean isVolatile() { + return f.isVolatile(); + } + + @Override + public ResolvedTypeDeclaration declaringType() { + return f.declaringType(); + } + + @Override + public Optional toAst() { + return f.toAst(); + } + }); + })); + + return fields; + } + + /// + /// Public methods + /// + + public SymbolReference solveMethod(String name, List parameterTypes) { + Context ctx = getContext(); + return ctx.solveMethod(name, parameterTypes, false); + } + + @Override + public Optional solveMethodAsUsage(String name, List argumentTypes, + Context invocationContext, List typeParameters) { + return getContext().solveMethodAsUsage(name, argumentTypes); + } + + /** + * This method is deprecated because the context is an implementation detail that should not be exposed. + * Ideally this method should become private. For this reason all further usages of this method are discouraged. + */ + @Deprecated + public Context getContext() { + return JavaParserFactory.getContext(wrappedNode, typeSolver); + } + + public ResolvedType getUsage(Node node) { + throw new UnsupportedOperationException(); + } + + @Override + public String getName() { + return wrappedNode.getName().getId(); + } + + @Override + public Optional getSuperClass() { + if(isJavaLangObject()) { + // If this is java.lang.Object, it has no super class. + return Optional.empty(); + } else if (wrappedNode.getExtendedTypes().isEmpty()) { + // All objects implicitly extend java.lang.Object -- inject it here (only when this isn't java.lang.Object) + return Optional.of(object()); + } else { + // Otherwise, return the first ancestor (n.b.: we know it's not empty due to check above). + return Optional.of(toReferenceType(wrappedNode.getExtendedTypes().getFirst().get())); + } + } + + @Override + public List getInterfaces() { + List interfaces = new ArrayList<>(); + // TODO FIXME: Remove null check -- should be an empty list... + if (wrappedNode.getImplementedTypes() != null) { + for (ClassOrInterfaceType t : wrappedNode.getImplementedTypes()) { + interfaces.add(toReferenceType(t)); + } + } + return interfaces; + } + + @Override + public List getConstructors() { + return AstResolutionUtils.getConstructors(this.wrappedNode, typeSolver, this); + } + + @Override + public boolean hasDirectlyAnnotation(String canonicalName) { + return AstResolutionUtils.hasDirectlyAnnotation(wrappedNode, typeSolver, canonicalName); + } + + @Override + public boolean isInterface() { + return wrappedNode.isInterface(); + } + + @Override + public String getPackageName() { + return javaParserTypeAdapter.getPackageName(); + } + + @Override + public String getClassName() { + return javaParserTypeAdapter.getClassName(); + } + + @Override + public String getQualifiedName() { + return javaParserTypeAdapter.getQualifiedName(); + } + + @Override + public boolean isAssignableBy(ResolvedReferenceTypeDeclaration other) { + return javaParserTypeAdapter.isAssignableBy(other); + } + + @Override + public boolean isAssignableBy(ResolvedType type) { + return javaParserTypeAdapter.isAssignableBy(type); + } + + @Override + public boolean canBeAssignedTo(ResolvedReferenceTypeDeclaration other) { + // TODO consider generic types + if (this.getQualifiedName().equals(other.getQualifiedName())) { + return true; + } + + Optional optionalSuperClass = getSuperClass(); + if (optionalSuperClass.isPresent()) { + Optional optionalSuperclassTypeDeclaration = optionalSuperClass.get().getTypeDeclaration(); + if (optionalSuperclassTypeDeclaration.isPresent()) { + ResolvedReferenceTypeDeclaration superclassTypeDeclaration = optionalSuperclassTypeDeclaration.get(); + if (superclassTypeDeclaration != this && superclassTypeDeclaration.isClass()) { + if (superclassTypeDeclaration.asClass().canBeAssignedTo(other)) { + return true; + } + } + } + } + + // TODO FIXME: Remove null check -- should be an empty list... + if (this.wrappedNode.getImplementedTypes() != null) { + for (ClassOrInterfaceType type : wrappedNode.getImplementedTypes()) { + ResolvedReferenceTypeDeclaration ancestor = (ResolvedReferenceTypeDeclaration) new SymbolSolver(typeSolver).solveType(type); + if (ancestor.canBeAssignedTo(other)) { + return true; + } + } + } + + return false; + } + + @Override + public boolean isTypeParameter() { + return false; + } + + /** + * Resolution should move out of declarations, so that they are pure declarations and the resolution should + * work for JavaParser, Reflection and Javassist classes in the same way and not be specific to the three + * implementations. + */ + @Deprecated + public SymbolReference solveType(String name) { + if (this.wrappedNode.getName().getId().equals(name)) { + return SymbolReference.solved(this); + } + SymbolReference ref = javaParserTypeAdapter.solveType(name); + if (ref.isSolved()) { + return ref; + } + + String prefix = wrappedNode.getName() + "."; + if (name.startsWith(prefix) && name.length() > prefix.length()) { + return new JavaParserClassDeclaration(this.wrappedNode, typeSolver).solveType(name.substring(prefix.length())); + } + + return getContext().getParent() + .orElseThrow(() -> new RuntimeException("Parent context unexpectedly empty.")) + .solveType(name); + } + + @Override + public SymbolReference solveMethod(String name, List argumentsTypes, + boolean staticOnly) { + return getContext().solveMethod(name, argumentsTypes, staticOnly); + } + + @Override + public List getAncestors(boolean acceptIncompleteList) { + List ancestors = new ArrayList<>(); + + // We want to avoid infinite recursion in case of Object having Object as ancestor + if (this.isJavaLangObject()) { + return ancestors; + } + + Optional qualifiedName = wrappedNode.getFullyQualifiedName(); + if (!qualifiedName.isPresent()) { + return ancestors; + } + + try { + // If a superclass is found, add it as an ancestor + Optional superClass = getSuperClass(); + if (superClass.isPresent()) { + if (isAncestor(superClass.get(), qualifiedName.get())) { + ancestors.add(superClass.get()); + } + } + } catch (UnsolvedSymbolException e) { + // in case we could not resolve the super class, we may still be able to resolve (some of) the + // implemented interfaces and so we continue gracefully with an (incomplete) list of ancestors + + if (!acceptIncompleteList) { + // Only throw if an incomplete ancestor list is unacceptable. + throw e; + } + } + + for (ClassOrInterfaceType implemented : wrappedNode.getImplementedTypes()) { + try { + // If an implemented interface is found, add it as an ancestor + ResolvedReferenceType rrt = toReferenceType(implemented); + if (isAncestor(rrt, qualifiedName.get())) { + ancestors.add(rrt); + } + } catch (UnsolvedSymbolException e) { + // in case we could not resolve some implemented interface, we may still be able to resolve the + // extended class or (some of) the other implemented interfaces and so we continue gracefully + // with an (incomplete) list of ancestors + + if (!acceptIncompleteList) { + // Only throw if an incomplete ancestor list is unacceptable. + throw e; + } + } + } + + return ancestors; + } + + private boolean isAncestor(ResolvedReferenceType candidateAncestor, String ownQualifiedName) { + Optional resolvedReferenceTypeDeclaration = candidateAncestor.getTypeDeclaration(); + if (resolvedReferenceTypeDeclaration.isPresent()) { + ResolvedTypeDeclaration rtd = resolvedReferenceTypeDeclaration.get().asType(); + // do not consider an inner or nested class as an ancestor + return !rtd.getQualifiedName().contains(ownQualifiedName); + } + return false; + } + + @Override + public Set getDeclaredMethods() { + Set methods = new HashSet<>(); + for (BodyDeclaration member : wrappedNode.getMembers()) { + if (member instanceof MethodDeclaration) { + methods.add(new JavaParserMethodDeclaration((MethodDeclaration) member, typeSolver)); + } + } + return methods; + } + + @Override + public List getTypeParameters() { + return this.wrappedNode.getTypeParameters().stream().map( + (tp) -> new JavaParserTypeParameter(tp, typeSolver) + ).collect(Collectors.toList()); + } + + /** + * Returns the JavaParser node associated with this JavaParserClassDeclaration. + * + * @return A visitable JavaParser node wrapped by this object. + */ + public ClassOrInterfaceDeclaration getWrappedNode() { + return wrappedNode; + } + + @Override + public AccessSpecifier accessSpecifier() { + return wrappedNode.getAccessSpecifier(); + } + + @Override + public Optional toAst() { + return Optional.of(wrappedNode); + } + + /// + /// Protected methods + /// + + @Override + protected ResolvedReferenceType object() { + ResolvedReferenceTypeDeclaration solvedJavaLangObject = typeSolver.getSolvedJavaLangObject(); + return new ReferenceTypeImpl(solvedJavaLangObject, typeSolver); + } + + @Override + public Set internalTypes() { + return javaParserTypeAdapter.internalTypes(); + } + + @Override + public Optional containerType() { + return javaParserTypeAdapter.containerType(); + } + + /// + /// Private methods + /// + + private ResolvedReferenceType toReferenceType(ClassOrInterfaceType classOrInterfaceType) { + String className = classOrInterfaceType.getName().getId(); + if (classOrInterfaceType.getScope().isPresent()) { + // look for the qualified name (for example class of type Rectangle2D.Double) + className = classOrInterfaceType.getScope().get().toString() + "." + className; + } + SymbolReference ref = solveType(className); + + // If unable to solve by the class name alone, attempt to qualify it. + if (!ref.isSolved()) { + Optional localScope = classOrInterfaceType.getScope(); + if (localScope.isPresent()) { + String localName = localScope.get().getName().getId() + "." + classOrInterfaceType.getName().getId(); + ref = solveType(localName); + } + } + + // If still unable to resolve, throw an exception. + if (!ref.isSolved()) { + throw new UnsolvedSymbolException(classOrInterfaceType.getName().getId()); + } + + if (!classOrInterfaceType.getTypeArguments().isPresent()) { + return new ReferenceTypeImpl(ref.getCorrespondingDeclaration().asReferenceType(), typeSolver); + } + + List superClassTypeParameters = classOrInterfaceType.getTypeArguments().get() + .stream() + .map(ta -> new LazyType(v -> JavaParserFacade.get(typeSolver).convert(ta, ta))) + .collect(Collectors.toList()); + + return new ReferenceTypeImpl(ref.getCorrespondingDeclaration().asReferenceType(), superClassTypeParameters, typeSolver); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserConstructorDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserConstructorDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..2ae4b76266c9273c05998f5452bd4e30634d82f8 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserConstructorDeclaration.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarations; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.ast.body.ConstructorDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedConstructorDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedParameterDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +public class JavaParserConstructorDeclaration implements ResolvedConstructorDeclaration { + + private N declaringType; + private com.github.javaparser.ast.body.ConstructorDeclaration wrappedNode; + private TypeSolver typeSolver; + + JavaParserConstructorDeclaration(N declaringType, com.github.javaparser.ast.body.ConstructorDeclaration wrappedNode, + TypeSolver typeSolver) { + this.declaringType = declaringType; + this.wrappedNode = wrappedNode; + this.typeSolver = typeSolver; + } + + @Override + public N declaringType() { + return declaringType; + } + + @Override + public int getNumberOfParams() { + return this.wrappedNode.getParameters().size(); + } + + @Override + public ResolvedParameterDeclaration getParam(int i) { + if (i < 0 || i >= getNumberOfParams()) { + throw new IllegalArgumentException(String.format("No param with index %d. Number of params: %d", i, getNumberOfParams())); + } + return new JavaParserParameterDeclaration(wrappedNode.getParameters().get(i), typeSolver); + } + + @Override + public String getName() { + return this.declaringType.getName(); + } + + /** + * Returns the JavaParser node associated with this JavaParserConstructorDeclaration. + * + * @return A visitable JavaParser node wrapped by this object. + */ + public com.github.javaparser.ast.body.ConstructorDeclaration getWrappedNode() { + return wrappedNode; + } + + @Override + public AccessSpecifier accessSpecifier() { + return wrappedNode.getAccessSpecifier(); + } + + @Override + public List getTypeParameters() { + return this.wrappedNode.getTypeParameters().stream().map((astTp) -> new JavaParserTypeParameter(astTp, typeSolver)).collect(Collectors.toList()); + } + + @Override + public int getNumberOfSpecifiedExceptions() { + return wrappedNode.getThrownExceptions().size(); + } + + @Override + public ResolvedType getSpecifiedException(int index) { + if (index < 0 || index >= getNumberOfSpecifiedExceptions()) { + throw new IllegalArgumentException(String.format("No exception with index %d. Number of exceptions: %d", + index, getNumberOfSpecifiedExceptions())); + } + return JavaParserFacade.get(typeSolver) + .convert(wrappedNode.getThrownExceptions().get(index), wrappedNode); + } + + @Override + public Optional toAst() { + return Optional.of(wrappedNode); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserEnumConstantDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserEnumConstantDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..25ca5ca751a310331c3bc3530077a517985f1fae --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserEnumConstantDeclaration.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarations; + +import com.github.javaparser.ast.body.EnumDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedEnumConstantDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; + +import static com.github.javaparser.symbolsolver.javaparser.Navigator.demandParentNode; + +/** + * @author Federico Tomassetti + */ +public class JavaParserEnumConstantDeclaration implements ResolvedEnumConstantDeclaration { + + private TypeSolver typeSolver; + private com.github.javaparser.ast.body.EnumConstantDeclaration wrappedNode; + + public JavaParserEnumConstantDeclaration(com.github.javaparser.ast.body.EnumConstantDeclaration wrappedNode, TypeSolver typeSolver) { + this.wrappedNode = wrappedNode; + this.typeSolver = typeSolver; + } + + @Override + public ResolvedType getType() { + return new ReferenceTypeImpl(new JavaParserEnumDeclaration((EnumDeclaration) demandParentNode(wrappedNode), typeSolver), typeSolver); + } + + @Override + public String getName() { + return wrappedNode.getName().getId(); + } + + /** + * Returns the JavaParser node associated with this JavaParserEnumConstantDeclaration. + * + * @return A visitable JavaParser node wrapped by this object. + */ + public com.github.javaparser.ast.body.EnumConstantDeclaration getWrappedNode() { + return wrappedNode; + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserEnumDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserEnumDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..f75281ab45c61ffea6cf1b53681637ced8d18919 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserEnumDeclaration.java @@ -0,0 +1,568 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarations; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.BodyDeclaration; +import com.github.javaparser.ast.body.EnumDeclaration; +import com.github.javaparser.ast.body.FieldDeclaration; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.type.ClassOrInterfaceType; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedArrayType; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.resolution.types.parametrization.ResolvedTypeParametersMap; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.core.resolution.MethodUsageResolutionCapability; +import com.github.javaparser.symbolsolver.core.resolution.TypeVariableResolutionCapability; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFactory; +import com.github.javaparser.symbolsolver.logic.AbstractTypeDeclaration; +import com.github.javaparser.symbolsolver.logic.MethodResolutionCapability; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.LazyType; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionFactory; + +import java.io.Serializable; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +public class JavaParserEnumDeclaration extends AbstractTypeDeclaration + implements ResolvedEnumDeclaration, MethodResolutionCapability, MethodUsageResolutionCapability, + AssociableToAST { + + private static String JAVA_LANG_ENUM = java.lang.Enum.class.getCanonicalName(); + private static String JAVA_LANG_COMPARABLE = java.lang.Comparable.class.getCanonicalName(); + private static String JAVA_IO_SERIALIZABLE = Serializable.class.getCanonicalName(); + + private TypeSolver typeSolver; + private EnumDeclaration wrappedNode; + private JavaParserTypeAdapter javaParserTypeAdapter; + + public JavaParserEnumDeclaration(com.github.javaparser.ast.body.EnumDeclaration wrappedNode, TypeSolver typeSolver) { + this.wrappedNode = wrappedNode; + this.typeSolver = typeSolver; + this.javaParserTypeAdapter = new JavaParserTypeAdapter<>(wrappedNode, typeSolver); + } + + @Override + public String toString() { + return "JavaParserEnumDeclaration{" + + "wrappedNode=" + wrappedNode + + '}'; + } + + @Override + public Set getDeclaredMethods() { + Set methods = new HashSet<>(); + for (BodyDeclaration member : wrappedNode.getMembers()) { + if (member instanceof com.github.javaparser.ast.body.MethodDeclaration) { + methods.add(new JavaParserMethodDeclaration((com.github.javaparser.ast.body.MethodDeclaration) member, typeSolver)); + } + } + return methods; + } + + public Context getContext() { + return JavaParserFactory.getContext(wrappedNode, typeSolver); + } + + @Override + public String getName() { + return wrappedNode.getName().getId(); + } + + @Override + public boolean isField() { + return false; + } + + @Override + public boolean isParameter() { + return false; + } + + @Override + public boolean isType() { + return true; + } + + @Override + public boolean hasDirectlyAnnotation(String canonicalName) { + return AstResolutionUtils.hasDirectlyAnnotation(wrappedNode, typeSolver, canonicalName); + } + + @Override + public boolean canBeAssignedTo(ResolvedReferenceTypeDeclaration other) { + String otherName = other.getQualifiedName(); + // Enums cannot be extended + if (otherName.equals(this.getQualifiedName())) { + return true; + } + if (otherName.equals(JAVA_LANG_ENUM)) { + return true; + } + // Enum implements Comparable and Serializable + if (otherName.equals(JAVA_LANG_COMPARABLE)) { + return true; + } + if (otherName.equals(JAVA_IO_SERIALIZABLE)) { + return true; + } + if (other.isJavaLangObject()) { + return true; + } + return false; + } + + @Override + public boolean isClass() { + return false; + } + + @Override + public boolean isInterface() { + return false; + } + + @Override + public String getPackageName() { + return javaParserTypeAdapter.getPackageName(); + } + + @Override + public String getClassName() { + return javaParserTypeAdapter.getClassName(); + } + + @Override + public String getQualifiedName() { + return javaParserTypeAdapter.getQualifiedName(); + } + + @Override + public boolean isAssignableBy(ResolvedReferenceTypeDeclaration other) { + return javaParserTypeAdapter.isAssignableBy(other); + } + + @Override + public boolean isAssignableBy(ResolvedType type) { + return javaParserTypeAdapter.isAssignableBy(type); + } + + @Override + public boolean isTypeParameter() { + return false; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + JavaParserEnumDeclaration that = (JavaParserEnumDeclaration) o; + + if (!wrappedNode.equals(that.wrappedNode)) return false; + + return true; + } + + @Override + public int hashCode() { + return wrappedNode.hashCode(); + } + + @Override + public Optional solveMethodAsUsage(String name, List argumentTypes, + Context invokationContext, List typeParameters) { + return getContext().solveMethodAsUsage(name, argumentTypes); + } + + @Override + public SymbolReference solveMethod(String name, List argumentsTypes, + boolean staticOnly) { + if (name.equals("values") && argumentsTypes.isEmpty()) { + return SymbolReference.solved(new JavaParserEnumDeclaration.ValuesMethod(this, typeSolver)); + } + if (name.equals("valueOf") && argumentsTypes.size() == 1) { + ResolvedType argument = argumentsTypes.get(0); + if (argument.isReferenceType() && "java.lang.String".equals(argument.asReferenceType().getQualifiedName())) { + return SymbolReference.solved(new JavaParserEnumDeclaration.ValueOfMethod(this, typeSolver)); + } + } + return getContext().solveMethod(name, argumentsTypes, staticOnly); + } + + @Override + public List getAllFields() { + List fields = javaParserTypeAdapter.getFieldsForDeclaredVariables(); + + this.getAncestors().forEach(a -> fields.addAll(a.getAllFieldsVisibleToInheritors())); + + this.wrappedNode.getMembers().stream().filter(m -> m instanceof FieldDeclaration).forEach(m -> { + FieldDeclaration fd = (FieldDeclaration)m; + fd.getVariables().forEach(v -> fields.add(new JavaParserFieldDeclaration(v, typeSolver))); + }); + + return fields; + } + + @Override + public List getAncestors(boolean acceptIncompleteList) { + List ancestors = new ArrayList<>(); + + ResolvedReferenceType enumClass = ReflectionFactory.typeUsageFor(Enum.class, typeSolver).asReferenceType(); + if(enumClass.getTypeDeclaration().isPresent()) { + ResolvedTypeParameterDeclaration eTypeParameter = enumClass.getTypeDeclaration().get() + .getTypeParameters() + .get(0); + enumClass = enumClass.deriveTypeParameters(new ResolvedTypeParametersMap.Builder() + .setValue(eTypeParameter, new ReferenceTypeImpl(this, typeSolver)) + .build()); + ancestors.add(enumClass); + } else { + // Consider IllegalStateException or similar? + } + + // TODO FIXME: Remove null check -- should be an empty list... + if (wrappedNode.getImplementedTypes() != null) { + for (ClassOrInterfaceType implementedType : wrappedNode.getImplementedTypes()) { + try { + ancestors.add(toReferenceType(implementedType)); + } catch (UnsolvedSymbolException e) { + if (!acceptIncompleteList) { + throw e; + } + } + } + } + + return ancestors; + } + + private ResolvedReferenceType toReferenceType(ClassOrInterfaceType classOrInterfaceType) { + String className = classOrInterfaceType.getName().getId(); + if (classOrInterfaceType.getScope().isPresent()) { + // look for the qualified name (for example class of type Rectangle2D.Double) + className = classOrInterfaceType.getScope().get().toString() + "." + className; + } + SymbolReference ref = solveType(className); + if (!ref.isSolved()) { + throw new UnsolvedSymbolException(classOrInterfaceType.getName().getId()); + } + if (!classOrInterfaceType.getTypeArguments().isPresent()) { + return new ReferenceTypeImpl(ref.getCorrespondingDeclaration().asReferenceType(), typeSolver); + } + List superClassTypeParameters = classOrInterfaceType.getTypeArguments().get() + .stream().map(ta -> new LazyType(v -> JavaParserFacade.get(typeSolver).convert(ta, ta))) + .collect(Collectors.toList()); + return new ReferenceTypeImpl(ref.getCorrespondingDeclaration().asReferenceType(), superClassTypeParameters, typeSolver); + } + + /** + * This method is deprecated because it receives the TypesSolver as a parameter. + * Eventually we would like to remove all usages of TypeSolver as a parameter. + * + * Also, resolution should move out of declarations, so that they are pure declarations and the resolution should + * work for JavaParser, Reflection and Javassist classes in the same way and not be specific to the three + * implementations. + */ + @Deprecated + public SymbolReference solveType(String name) { + if (this.wrappedNode.getName().getId().equals(name)) { + return SymbolReference.solved(this); + } + SymbolReference ref = javaParserTypeAdapter.solveType(name); + if (ref.isSolved()) { + return ref; + } + + return getContext().getParent() + .orElseThrow(() -> new RuntimeException("Parent context unexpectedly empty.")) + .solveType(name); + } + + @Override + public List getTypeParameters() { + return Collections.emptyList(); + } + + /** + * Returns the JavaParser node associated with this JavaParserEnumDeclaration. + * + * @return A visitable JavaParser node wrapped by this object. + */ + public com.github.javaparser.ast.body.EnumDeclaration getWrappedNode() { + return wrappedNode; + } + + @Override + public List getEnumConstants() { + return wrappedNode.getEntries().stream() + .map(entry -> new JavaParserEnumConstantDeclaration(entry, typeSolver)) + .collect(Collectors.toList()); + } + + + /** + * Needed by ContextHelper + * + * An implicitly declared method {@code public static E[] values()}, which returns an array containing the + * enum constants of {@code E}, in the same order as they appear in the body of the declaration of E. + * + * @see https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.9.2 + * @see https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.9.3 + * @see https://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-8.9.3 + */ + public static class ValuesMethod implements ResolvedMethodDeclaration, TypeVariableResolutionCapability { + + private JavaParserEnumDeclaration enumDeclaration; + private TypeSolver typeSolver; + + public ValuesMethod(JavaParserEnumDeclaration enumDeclaration, TypeSolver typeSolver) { + this.enumDeclaration = enumDeclaration; + this.typeSolver = typeSolver; + } + + @Override + public ResolvedReferenceTypeDeclaration declaringType() { + return enumDeclaration; + } + + @Override + public ResolvedType getReturnType() { + return new ResolvedArrayType(new ReferenceTypeImpl(enumDeclaration, typeSolver)); + } + + @Override + public int getNumberOfParams() { + return 0; + } + + @Override + public ResolvedParameterDeclaration getParam(int i) { + throw new UnsupportedOperationException(); + } + + public MethodUsage getUsage(Node node) { + throw new UnsupportedOperationException(); + } + + public MethodUsage resolveTypeVariables(Context context, List parameterTypes) { + return new MethodUsage(this); + } + + @Override + public boolean isAbstract() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isDefaultMethod() { + return false; + } + + @Override + public boolean isStatic() { + return false; + } + + @Override + public String getName() { + return "values"; + } + + @Override + public List getTypeParameters() { + return Collections.emptyList(); + } + + @Override + public AccessSpecifier accessSpecifier() { + return enumDeclaration.getWrappedNode().getAccessSpecifier(); + } + + @Override + public int getNumberOfSpecifiedExceptions() { + return 0; + } + + @Override + public ResolvedType getSpecifiedException(int index) { + throw new UnsupportedOperationException("The values method of an enum does not throw any exception"); + } + + @Override + public Optional toAst() { + return Optional.empty(); + } + } + + + /** + * Needed by ContextHelper + * An implicitly declared method {@code public static E valueOf(String name)}, which returns the + * enum constant of {@code E} with the specified name. + * + * @see https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.9.2 + * @see https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.9.3 + * @see https://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-8.9.3 + */ + public static class ValueOfMethod implements ResolvedMethodDeclaration, TypeVariableResolutionCapability { + + private JavaParserEnumDeclaration enumDeclaration; + private TypeSolver typeSolver; + + public ValueOfMethod(JavaParserEnumDeclaration enumDeclaration, TypeSolver typeSolver) { + this.enumDeclaration = enumDeclaration; + this.typeSolver = typeSolver; + } + + @Override + public ResolvedReferenceTypeDeclaration declaringType() { + return enumDeclaration; + } + + @Override + public ResolvedType getReturnType() { + return new ReferenceTypeImpl(enumDeclaration, typeSolver); + } + + @Override + public int getNumberOfParams() { + return 1; + } + + @Override + public ResolvedParameterDeclaration getParam(int i) { + if (i == 0) { + return new ResolvedParameterDeclaration() { + @Override + public String getName() { + return "name"; + } + + @Override + public ResolvedType getType() { + return new ReferenceTypeImpl(typeSolver.solveType("java.lang.String"), typeSolver); + } + + @Override + public boolean isVariadic() { + return false; + } + }; + } + + throw new IllegalArgumentException("Invalid parameter index!"); + } + + public MethodUsage getUsage(Node node) { + throw new UnsupportedOperationException(); + } + + @Override + public MethodUsage resolveTypeVariables(Context context, List parameterTypes) { + return new MethodUsage(this); + } + + @Override + public boolean isAbstract() { + return false; + } + + @Override + public boolean isDefaultMethod() { + return false; + } + + @Override + public boolean isStatic() { + return true; + } + + @Override + public String getName() { + return "valueOf"; + } + + @Override + public List getTypeParameters() { + return Collections.emptyList(); + } + + @Override + public AccessSpecifier accessSpecifier() { + return AccessSpecifier.PUBLIC; + } + + @Override + public int getNumberOfSpecifiedExceptions() { + return 0; + } + + @Override + public ResolvedType getSpecifiedException(int index) { + throw new UnsupportedOperationException("The valueOf method of an enum does not throw any exception"); + } + + @Override + public Optional toAst() { + return Optional.empty(); + } + } + + @Override + public AccessSpecifier accessSpecifier() { + return wrappedNode.getAccessSpecifier(); + } + + @Override + public Set internalTypes() { + return javaParserTypeAdapter.internalTypes(); + } + + @Override + public Optional containerType() { + return javaParserTypeAdapter.containerType(); + } + + @Override + public List getConstructors() { + return AstResolutionUtils.getConstructors(this.wrappedNode, typeSolver, this); + } + + @Override + public Optional toAst() { + return Optional.of(wrappedNode); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserFieldDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserFieldDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..88cd8a114ef34c0451cd555c2de166a3682427d9 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserFieldDeclaration.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarations; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.ast.Modifier; +import com.github.javaparser.ast.body.FieldDeclaration; +import com.github.javaparser.ast.body.TypeDeclaration; +import com.github.javaparser.ast.body.VariableDeclarator; +import com.github.javaparser.resolution.declarations.AssociableToAST; +import com.github.javaparser.resolution.declarations.ResolvedFieldDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.Optional; + +import static com.github.javaparser.symbolsolver.javaparser.Navigator.demandParentNode; + +/** + * @author Federico Tomassetti + */ +public class JavaParserFieldDeclaration implements ResolvedFieldDeclaration, AssociableToAST { + + private VariableDeclarator variableDeclarator; + private com.github.javaparser.ast.body.FieldDeclaration wrappedNode; + private TypeSolver typeSolver; + + public JavaParserFieldDeclaration(VariableDeclarator variableDeclarator, TypeSolver typeSolver) { + if (typeSolver == null) { + throw new IllegalArgumentException("typeSolver should not be null"); + } + this.variableDeclarator = variableDeclarator; + this.typeSolver = typeSolver; + if (!(demandParentNode(variableDeclarator) instanceof com.github.javaparser.ast.body.FieldDeclaration)) { + throw new IllegalStateException(demandParentNode(variableDeclarator).getClass().getCanonicalName()); + } + this.wrappedNode = (com.github.javaparser.ast.body.FieldDeclaration) demandParentNode(variableDeclarator); + } + + @Override + public ResolvedType getType() { + return JavaParserFacade.get(typeSolver).convert(variableDeclarator.getType(), wrappedNode); + } + + @Override + public String getName() { + return variableDeclarator.getName().getId(); + } + + @Override + public boolean isStatic() { + return wrappedNode.hasModifier(Modifier.Keyword.STATIC); + } + + @Override + public boolean isVolatile() { + return wrappedNode.hasModifier(Modifier.Keyword.VOLATILE); + } + + @Override + public boolean isField() { + return true; + } + + /** + * Returns the JavaParser node associated with this JavaParserFieldDeclaration. + * + * @return A visitable JavaParser node wrapped by this object. + */ + public com.github.javaparser.ast.body.FieldDeclaration getWrappedNode() { + return wrappedNode; + } + + public VariableDeclarator getVariableDeclarator() { + return variableDeclarator; + } + + @Override + public String toString() { + return "JavaParserFieldDeclaration{" + getName() + "}"; + } + + @Override + public AccessSpecifier accessSpecifier() { + return wrappedNode.getAccessSpecifier(); + } + + @Override + public ResolvedTypeDeclaration declaringType() { + Optional typeDeclaration = wrappedNode.findAncestor(TypeDeclaration.class); + if (typeDeclaration.isPresent()) { + return JavaParserFacade.get(typeSolver).getTypeDeclaration(typeDeclaration.get()); + } + throw new IllegalStateException(); + } + + @Override + public Optional toAst() { + return Optional.ofNullable(wrappedNode); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserInterfaceDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserInterfaceDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..0d8e5532230553d9568347e93d600e2fe1b43e4b --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserInterfaceDeclaration.java @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarations; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.BodyDeclaration; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.FieldDeclaration; +import com.github.javaparser.ast.type.ClassOrInterfaceType; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.core.resolution.MethodUsageResolutionCapability; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFactory; +import com.github.javaparser.symbolsolver.logic.AbstractTypeDeclaration; +import com.github.javaparser.symbolsolver.logic.MethodResolutionCapability; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.LazyType; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.symbolsolver.resolution.SymbolSolver; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +public class JavaParserInterfaceDeclaration extends AbstractTypeDeclaration + implements ResolvedInterfaceDeclaration, MethodResolutionCapability, MethodUsageResolutionCapability { + + private TypeSolver typeSolver; + private ClassOrInterfaceDeclaration wrappedNode; + private JavaParserTypeAdapter javaParserTypeAdapter; + + public JavaParserInterfaceDeclaration(ClassOrInterfaceDeclaration wrappedNode, TypeSolver typeSolver) { + if (!wrappedNode.isInterface()) { + throw new IllegalArgumentException(); + } + this.wrappedNode = wrappedNode; + this.typeSolver = typeSolver; + this.javaParserTypeAdapter = new JavaParserTypeAdapter<>(wrappedNode, typeSolver); + } + + @Override + public Set getDeclaredMethods() { + Set methods = new HashSet<>(); + for (BodyDeclaration member : wrappedNode.getMembers()) { + if (member instanceof com.github.javaparser.ast.body.MethodDeclaration) { + methods.add(new JavaParserMethodDeclaration((com.github.javaparser.ast.body.MethodDeclaration) member, typeSolver)); + } + } + return methods; + } + + public Context getContext() { + return JavaParserFactory.getContext(wrappedNode, typeSolver); + } + + public ResolvedType getUsage(Node node) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + JavaParserInterfaceDeclaration that = (JavaParserInterfaceDeclaration) o; + + if (!wrappedNode.equals(that.wrappedNode)) return false; + + return true; + } + + @Override + public int hashCode() { + return wrappedNode.hashCode(); + } + + @Override + public String getName() { + return wrappedNode.getName().getId(); + } + + @Override + public ResolvedInterfaceDeclaration asInterface() { + return this; + } + + @Override + public boolean hasDirectlyAnnotation(String canonicalName) { + return AstResolutionUtils.hasDirectlyAnnotation(wrappedNode, typeSolver, canonicalName); + } + + @Override + public boolean isInterface() { + return true; + } + + @Override + public List getInterfacesExtended() { + List interfaces = new ArrayList<>(); + for (ClassOrInterfaceType t : wrappedNode.getExtendedTypes()) { + interfaces.add(new ReferenceTypeImpl( + solveType(t.getName().getId()).getCorrespondingDeclaration().asInterface(), typeSolver)); + } + return interfaces; + } + + @Override + public String getPackageName() { + return javaParserTypeAdapter.getPackageName(); + } + + @Override + public String getClassName() { + return javaParserTypeAdapter.getClassName(); + } + + @Override + public String getQualifiedName() { + return javaParserTypeAdapter.getQualifiedName(); + } + + @Override + public boolean isAssignableBy(ResolvedReferenceTypeDeclaration other) { + return javaParserTypeAdapter.isAssignableBy(other); + } + + @Override + public boolean isAssignableBy(ResolvedType type) { + return javaParserTypeAdapter.isAssignableBy(type); + } + + @Override + public boolean canBeAssignedTo(ResolvedReferenceTypeDeclaration other) { + // TODO consider generic types + if (this.getQualifiedName().equals(other.getQualifiedName())) { + return true; + } + if (this.wrappedNode.getExtendedTypes() != null) { + for (ClassOrInterfaceType type : wrappedNode.getExtendedTypes()) { + ResolvedReferenceTypeDeclaration ancestor = (ResolvedReferenceTypeDeclaration) new SymbolSolver(typeSolver).solveType(type); + if (ancestor.canBeAssignedTo(other)) { + return true; + } + } + } + + // TODO FIXME: Remove null check -- should be an empty list... + if (this.wrappedNode.getImplementedTypes() != null) { + for (ClassOrInterfaceType type : wrappedNode.getImplementedTypes()) { + ResolvedReferenceTypeDeclaration ancestor = (ResolvedReferenceTypeDeclaration) new SymbolSolver(typeSolver).solveType(type); + if (ancestor.canBeAssignedTo(other)) { + return true; + } + } + } + + return false; + } + + @Override + public boolean isTypeParameter() { + return false; + } + + @Override + public List getAllFields() { + List fields = javaParserTypeAdapter.getFieldsForDeclaredVariables(); + + getAncestors() + .stream() + .filter(ancestor -> ancestor.getTypeDeclaration().isPresent()) + .forEach(ancestor -> ancestor.getTypeDeclaration().get() + .getAllFields() + .forEach(f -> { + fields.add(new ResolvedFieldDeclaration() { + + @Override + public AccessSpecifier accessSpecifier() { + return f.accessSpecifier(); + } + + @Override + public String getName() { + return f.getName(); + } + + @Override + public ResolvedType getType() { + return ancestor.useThisTypeParametersOnTheGivenType(f.getType()); + } + + @Override + public boolean isStatic() { + return f.isStatic(); + } + + @Override + public boolean isVolatile() { + return f.isVolatile(); + } + + @Override + public ResolvedTypeDeclaration declaringType() { + return f.declaringType(); + } + + @Override + public Optional toAst() { + return f.toAst(); + } + }); + }) + ); + + return fields; + } + + + @Override + public String toString() { + return "JavaParserInterfaceDeclaration{" + + "wrappedNode=" + wrappedNode + + '}'; + } + + /** + * This method is deprecated because it receives the TypesSolver as a parameter. + * Eventually we would like to remove all usages of TypeSolver as a parameter. + * + * Also, resolution should move out of declarations, so that they are pure declarations and the resolution should + * work for JavaParser, Reflection and Javassist classes in the same way and not be specific to the three + * implementations. + */ + @Deprecated + public SymbolReference solveType(String name) { + if (this.wrappedNode.getName().getId().equals(name)) { + return SymbolReference.solved(this); + } + SymbolReference ref = javaParserTypeAdapter.solveType(name); + if (ref.isSolved()) { + return ref; + } + + String prefix = wrappedNode.getName() + "."; + if (name.startsWith(prefix) && name.length() > prefix.length()) { + return new JavaParserInterfaceDeclaration(this.wrappedNode, typeSolver).solveType(name.substring(prefix.length())); + } + + return getContext().getParent() + .orElseThrow(() -> new RuntimeException("Parent context unexpectedly empty.")) + .solveType(name); + } + + @Override + public SymbolReference solveMethod(String name, List argumentsTypes, + boolean staticOnly) { + return getContext().solveMethod(name, argumentsTypes, staticOnly); + } + + @Override + public Optional solveMethodAsUsage(String name, List argumentTypes, + Context invocationContext, List typeParameters) { + return getContext().solveMethodAsUsage(name, argumentTypes); + } + + @Override + public List getAncestors(boolean acceptIncompleteList) { + List ancestors = new ArrayList<>(); + if (wrappedNode.getExtendedTypes() != null) { + for (ClassOrInterfaceType extended : wrappedNode.getExtendedTypes()) { + try { + ancestors.add(toReferenceType(extended)); + } catch (UnsolvedSymbolException e) { + if (!acceptIncompleteList) { + // we only throw an exception if we require a complete list; otherwise, we attempt to continue gracefully + throw e; + } + } + } + } + + // TODO FIXME: Remove null check -- should be an empty list... + if (wrappedNode.getImplementedTypes() != null) { + for (ClassOrInterfaceType implemented : wrappedNode.getImplementedTypes()) { + try { + ancestors.add(toReferenceType(implemented)); + } catch (UnsolvedSymbolException e) { + if (!acceptIncompleteList) { + // we only throw an exception if we require a complete list; otherwise, we attempt to continue gracefully + throw e; + } + } + } + } + return ancestors; + } + + @Override + public List getTypeParameters() { + if (this.wrappedNode.getTypeParameters() == null) { + return Collections.emptyList(); + } else { + return this.wrappedNode.getTypeParameters().stream().map( + (tp) -> new JavaParserTypeParameter(tp, typeSolver) + ).collect(Collectors.toList()); + } + } + + /** + * Returns the JavaParser node associated with this JavaParserInterfaceDeclaration. + * + * @return A visitable JavaParser node wrapped by this object. + */ + public ClassOrInterfaceDeclaration getWrappedNode() { + return wrappedNode; + } + + @Override + public AccessSpecifier accessSpecifier() { + return wrappedNode.getAccessSpecifier(); + } + + @Override + public Set internalTypes() { + return javaParserTypeAdapter.internalTypes(); + } + + @Override + public Optional containerType() { + return javaParserTypeAdapter.containerType(); + } + + @Override + public List getConstructors() { + return Collections.emptyList(); + } + + @Override + public Optional toAst() { + return Optional.of(wrappedNode); + } + + /// + /// Private methods + /// + + private ResolvedReferenceType toReferenceType(ClassOrInterfaceType classOrInterfaceType) { + SymbolReference ref = null; + String typeName = classOrInterfaceType.getName().getId(); + if (classOrInterfaceType.getScope().isPresent()) { + typeName = classOrInterfaceType.getScope().get().asString() + "." + typeName; + } + + if (typeName.indexOf('.') > -1) { + ref = typeSolver.tryToSolveType(typeName); + } + if (ref == null || !ref.isSolved()) { + ref = solveType(typeName); + } + if (!ref.isSolved()) { + throw new UnsolvedSymbolException(classOrInterfaceType.getName().getId()); + } + if (!classOrInterfaceType.getTypeArguments().isPresent()) { + return new ReferenceTypeImpl(ref.getCorrespondingDeclaration().asReferenceType(), typeSolver); + } + List superClassTypeParameters = classOrInterfaceType.getTypeArguments().get() + .stream().map(ta -> new LazyType(v -> JavaParserFacade.get(typeSolver).convert(ta, ta))) + .collect(Collectors.toList()); + return new ReferenceTypeImpl(ref.getCorrespondingDeclaration().asReferenceType(), superClassTypeParameters, typeSolver); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserMethodDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserMethodDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..ad1161308b5e54c2bcbe3fd9a8213a0741fa7f3f --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserMethodDeclaration.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarations; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.expr.ObjectCreationExpr; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedParameterDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.core.resolution.TypeVariableResolutionCapability; +import com.github.javaparser.symbolsolver.declarations.common.MethodDeclarationCommonLogic; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFactory; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import static com.github.javaparser.symbolsolver.javaparser.Navigator.demandParentNode; + +/** + * @author Federico Tomassetti + */ +public class JavaParserMethodDeclaration implements ResolvedMethodDeclaration, TypeVariableResolutionCapability { + + private com.github.javaparser.ast.body.MethodDeclaration wrappedNode; + private TypeSolver typeSolver; + + public JavaParserMethodDeclaration(com.github.javaparser.ast.body.MethodDeclaration wrappedNode, TypeSolver typeSolver) { + this.wrappedNode = wrappedNode; + this.typeSolver = typeSolver; + } + + @Override + public String toString() { + return "JavaParserMethodDeclaration{" + + "wrappedNode=" + wrappedNode + + ", typeSolver=" + typeSolver + + '}'; + } + + @Override + public ResolvedReferenceTypeDeclaration declaringType() { + if (demandParentNode(wrappedNode) instanceof ObjectCreationExpr) { + ObjectCreationExpr parentNode = (ObjectCreationExpr) demandParentNode(wrappedNode); + return new JavaParserAnonymousClassDeclaration(parentNode, typeSolver); + } + return JavaParserFactory.toTypeDeclaration(demandParentNode(wrappedNode), typeSolver); + } + + @Override + public ResolvedType getReturnType() { + return JavaParserFacade.get(typeSolver).convert(wrappedNode.getType(), getContext()); + } + + @Override + public int getNumberOfParams() { + return wrappedNode.getParameters().size(); + } + + @Override + public ResolvedParameterDeclaration getParam(int i) { + if (i < 0 || i >= getNumberOfParams()) { + throw new IllegalArgumentException(String.format("No param with index %d. Number of params: %d", i, getNumberOfParams())); + } + return new JavaParserParameterDeclaration(wrappedNode.getParameters().get(i), typeSolver); + } + + public MethodUsage getUsage(Node node) { + throw new UnsupportedOperationException(); + } + + public MethodUsage resolveTypeVariables(Context context, List parameterTypes) { + return new MethodDeclarationCommonLogic(this, typeSolver).resolveTypeVariables(context, parameterTypes); + } + + private Context getContext() { + return JavaParserFactory.getContext(wrappedNode, typeSolver); + } + + @Override + public boolean isAbstract() { + return !wrappedNode.getBody().isPresent(); + } + + @Override + public String getName() { + return wrappedNode.getName().getId(); + } + + @Override + public List getTypeParameters() { + return this.wrappedNode.getTypeParameters().stream().map((astTp) -> new JavaParserTypeParameter(astTp, typeSolver)).collect(Collectors.toList()); + } + + @Override + public boolean isDefaultMethod() { + return wrappedNode.isDefault(); + } + + @Override + public boolean isStatic() { + return wrappedNode.isStatic(); + } + + /** + * Returns the JavaParser node associated with this JavaParserMethodDeclaration. + * + * @return A visitable JavaParser node wrapped by this object. + */ + public com.github.javaparser.ast.body.MethodDeclaration getWrappedNode() { + return wrappedNode; + } + + @Override + public AccessSpecifier accessSpecifier() { + return wrappedNode.getAccessSpecifier(); + } + + @Override + public int getNumberOfSpecifiedExceptions() { + return wrappedNode.getThrownExceptions().size(); + } + + @Override + public ResolvedType getSpecifiedException(int index) { + if (index < 0 || index >= getNumberOfSpecifiedExceptions()) { + throw new IllegalArgumentException(String.format("No exception with index %d. Number of exceptions: %d", + index, getNumberOfSpecifiedExceptions())); + } + return JavaParserFacade.get(typeSolver).convert(wrappedNode.getThrownExceptions() + .get(index), wrappedNode); + } + + @Override + public Optional toAst() { + return Optional.of(wrappedNode); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserParameterDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserParameterDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..fceabe0ec925c8fb3a95273c3318e458a2fc5905 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserParameterDeclaration.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarations; + +import com.github.javaparser.ast.body.Parameter; +import com.github.javaparser.ast.type.UnknownType; +import com.github.javaparser.resolution.declarations.AssociableToAST; +import com.github.javaparser.resolution.declarations.ResolvedParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedArrayType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFactory; +import com.github.javaparser.symbolsolver.javaparsermodel.contexts.LambdaExprContext; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.resolution.Value; + +import java.util.Optional; + +/** + * @author Federico Tomassetti + */ +public class JavaParserParameterDeclaration implements ResolvedParameterDeclaration, AssociableToAST { + + private final Parameter wrappedNode; + private final TypeSolver typeSolver; + + public JavaParserParameterDeclaration(Parameter wrappedNode, TypeSolver typeSolver) { + this.wrappedNode = wrappedNode; + this.typeSolver = typeSolver; + } + + @Override + public String getName() { + return wrappedNode.getName().getId(); + } + + @Override + public boolean isVariadic() { + return wrappedNode.isVarArgs(); + } + + @Override + public ResolvedType getType() { + if (wrappedNode.getType() instanceof UnknownType && JavaParserFactory.getContext(wrappedNode, typeSolver) instanceof LambdaExprContext) { + Optional value = JavaParserFactory.getContext(wrappedNode, typeSolver).solveSymbolAsValue(wrappedNode.getNameAsString()); + if (value.isPresent()) { + return value.get().getType(); + } + } + ResolvedType res = JavaParserFacade.get(typeSolver).convert(wrappedNode.getType(), wrappedNode); + if (isVariadic()) { + res = new ResolvedArrayType(res); + } + return res; + } + + /** + * Returns the JavaParser node associated with this JavaParserParameterDeclaration. + * + * @return A visitable JavaParser node wrapped by this object. + */ + public Parameter getWrappedNode() { + return wrappedNode; + } + + @Override + public Optional toAst() { + return Optional.of(wrappedNode); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserPatternDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserPatternDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..47f6c77a69107d531df067e26b38b2827013e648 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserPatternDeclaration.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarations; + +import com.github.javaparser.ast.expr.PatternExpr; +import com.github.javaparser.resolution.declarations.AssociableToAST; +import com.github.javaparser.resolution.declarations.ResolvedPatternDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.Optional; + +/** + * WARNING: Implemented fairly blindly. Unsure if required or even appropriate. Use with extreme caution. + * + * @author Roger Howell + */ +public class JavaParserPatternDeclaration implements ResolvedPatternDeclaration, AssociableToAST { + + private final PatternExpr wrappedNode; + private final TypeSolver typeSolver; + + public JavaParserPatternDeclaration(PatternExpr wrappedNode, TypeSolver typeSolver) { + this.wrappedNode = wrappedNode; + this.typeSolver = typeSolver; + } + + @Override + public String getName() { + return wrappedNode.getName().getId(); + } + + @Override + public ResolvedType getType() { + return JavaParserFacade.get(typeSolver).convert(wrappedNode.getType(), wrappedNode); + } + + /** + * Returns the JavaParser node associated with this JavaParserPatternDeclaration. + * + * @return A visitable JavaParser node wrapped by this object. + */ + public PatternExpr getWrappedNode() { + return wrappedNode; + } + + @Override + public Optional toAst() { + return Optional.of(wrappedNode); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserSymbolDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserSymbolDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..330d7b7f170ec5a37ddbedd58b56d8eb66551a9a --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserSymbolDeclaration.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarations; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.Parameter; +import com.github.javaparser.ast.body.VariableDeclarator; +import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.ast.expr.PatternExpr; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import static com.github.javaparser.symbolsolver.javaparser.Navigator.demandParentNode; + +/** + * This should not be used to represent fields of parameters. + * + * Eventually this should be renamed in JavaParserVariableDeclaration. + * + * @author Federico Tomassetti + */ +public final class JavaParserSymbolDeclaration { + + public static JavaParserFieldDeclaration field(VariableDeclarator wrappedNode, TypeSolver typeSolver) { + return new JavaParserFieldDeclaration(wrappedNode, typeSolver); + } + + public static JavaParserParameterDeclaration parameter(Parameter parameter, TypeSolver typeSolver) { + return new JavaParserParameterDeclaration(parameter, typeSolver); + } + + public static JavaParserVariableDeclaration localVar(VariableDeclarator variableDeclarator, TypeSolver typeSolver) { + return new JavaParserVariableDeclaration(variableDeclarator, typeSolver); + } + + public static JavaParserPatternDeclaration patternVar(PatternExpr patternExpr, TypeSolver typeSolver) { + return new JavaParserPatternDeclaration(patternExpr, typeSolver); + } + + public static int getParamPos(Parameter parameter) { + int pos = 0; + for (Node node : demandParentNode(parameter).getChildNodes()) { + if (node == parameter) { + return pos; + } else if (node instanceof Parameter) { + pos++; + } + } + return pos; + } + + public static int getParamPos(Node node) { + if (demandParentNode(node) instanceof MethodCallExpr) { + MethodCallExpr call = (MethodCallExpr) demandParentNode(node); + for (int i = 0; i < call.getArguments().size(); i++) { + if (call.getArguments().get(i) == node) return i; + } + throw new IllegalStateException(); + } + throw new IllegalArgumentException(); + } + + private JavaParserSymbolDeclaration() { + // This private constructor is used to hide the public one + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserTypeAdapter.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserTypeAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..2b57f5306c9e7a247cb15b09d05b77f534a68f78 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserTypeAdapter.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarations; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.body.*; +import com.github.javaparser.ast.nodeTypes.NodeWithMembers; +import com.github.javaparser.ast.nodeTypes.NodeWithSimpleName; +import com.github.javaparser.ast.nodeTypes.NodeWithTypeParameters; +import com.github.javaparser.ast.type.TypeParameter; +import com.github.javaparser.resolution.declarations.ResolvedFieldDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeDeclaration; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +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.symbolsolver.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.symbolsolver.resolution.SymbolSolver; + +import java.util.*; + +/** + * @author Federico Tomassetti + */ +public class JavaParserTypeAdapter & NodeWithMembers> { + + private T wrappedNode; + private TypeSolver typeSolver; + + public JavaParserTypeAdapter(T wrappedNode, TypeSolver typeSolver) { + this.wrappedNode = wrappedNode; + this.typeSolver = typeSolver; + } + + public String getPackageName() { + return AstResolutionUtils.getPackageName(wrappedNode); + } + + public String getClassName() { + return AstResolutionUtils.getClassName("", wrappedNode); + } + + public String getQualifiedName() { + String containerName = AstResolutionUtils.containerName(wrappedNode.getParentNode().orElse(null)); + if (containerName.isEmpty()) { + return wrappedNode.getName().getId(); + } else { + return containerName + "." + wrappedNode.getName().getId(); + } + } + + public boolean isAssignableBy(ResolvedReferenceTypeDeclaration other) { + List ancestorsOfOther = other.getAllAncestors(); + ancestorsOfOther.add(new ReferenceTypeImpl(other, typeSolver)); + for (ResolvedReferenceType ancestorOfOther : ancestorsOfOther) { + if (ancestorOfOther.getQualifiedName().equals(this.getQualifiedName())) { + return true; + } + } + return false; + } + + public boolean isAssignableBy(ResolvedType type) { + if (type.isNull()) { + return true; + } + if (type.isReferenceType()) { + ResolvedReferenceTypeDeclaration other = typeSolver.solveType(type.describe()); + return isAssignableBy(other); + } else { + throw new UnsupportedOperationException(); + } + } + + /** + * This method is deprecated because it receives the TypesSolver as a parameter. + * Eventually we would like to remove all usages of TypeSolver as a parameter. + * + * Also, resolution should move out of declarations, so that they are pure declarations and the resolution should + * work for JavaParser, Reflection and Javassist classes in the same way and not be specific to the three + * implementations. + */ + @Deprecated + public SymbolReference solveType(String name) { + if(wrappedNode instanceof NodeWithTypeParameters) { + NodeList typeParameters = ((NodeWithTypeParameters) wrappedNode).getTypeParameters(); + for (com.github.javaparser.ast.type.TypeParameter typeParameter : typeParameters) { + if (typeParameter.getName().getId().equals(name)) { + return SymbolReference.solved(new JavaParserTypeVariableDeclaration(typeParameter, typeSolver)); + } + } + } + + // Member classes & interfaces + for (BodyDeclaration member : this.wrappedNode.getMembers()) { + if (member instanceof com.github.javaparser.ast.body.TypeDeclaration) { + com.github.javaparser.ast.body.TypeDeclaration internalType = (com.github.javaparser.ast.body.TypeDeclaration) member; + String prefix = internalType.getName() + "."; + if (internalType.getName().getId().equals(name)) { + if (internalType instanceof ClassOrInterfaceDeclaration) { + if (((ClassOrInterfaceDeclaration) internalType).isInterface()) { + return SymbolReference.solved(new JavaParserInterfaceDeclaration((com.github.javaparser.ast.body.ClassOrInterfaceDeclaration) internalType, typeSolver)); + } else { + return SymbolReference.solved(new JavaParserClassDeclaration((com.github.javaparser.ast.body.ClassOrInterfaceDeclaration) internalType, typeSolver)); + } + } else if (internalType instanceof EnumDeclaration) { + return SymbolReference.solved(new JavaParserEnumDeclaration((com.github.javaparser.ast.body.EnumDeclaration) internalType, typeSolver)); + } else if (internalType instanceof AnnotationDeclaration) { + return SymbolReference.solved(new JavaParserAnnotationDeclaration((com.github.javaparser.ast.body.AnnotationDeclaration) internalType, typeSolver)); + } else { + throw new UnsupportedOperationException(); + } + } else if (name.startsWith(prefix) && name.length() > prefix.length()) { + if (internalType instanceof ClassOrInterfaceDeclaration) { + if (((ClassOrInterfaceDeclaration) internalType).isInterface()) { + return new JavaParserInterfaceDeclaration((com.github.javaparser.ast.body.ClassOrInterfaceDeclaration) internalType, typeSolver).solveType(name.substring(prefix.length())); + } else { + return new JavaParserClassDeclaration((com.github.javaparser.ast.body.ClassOrInterfaceDeclaration) internalType, typeSolver).solveType(name.substring(prefix.length())); + } + } else if (internalType instanceof EnumDeclaration) { + return new SymbolSolver(typeSolver).solveTypeInType(new JavaParserEnumDeclaration((com.github.javaparser.ast.body.EnumDeclaration) internalType, typeSolver), name.substring(prefix.length())); + } else if (internalType instanceof AnnotationDeclaration) { + return SymbolReference.solved(new JavaParserAnnotationDeclaration((com.github.javaparser.ast.body.AnnotationDeclaration) internalType, typeSolver)); + } else { + throw new UnsupportedOperationException(); + } + } + } + } + return SymbolReference.unsolved(ResolvedTypeDeclaration.class); + } + + public Optional containerType() { + return wrappedNode + .getParentNode() + .map(node -> JavaParserFactory.toTypeDeclaration(node, typeSolver)); + } + + public List getFieldsForDeclaredVariables() { + List fields = new ArrayList<>(); + if (wrappedNode.getMembers() != null) { + for (BodyDeclaration member : this.wrappedNode.getMembers()) { + if (member instanceof com.github.javaparser.ast.body.FieldDeclaration) { + com.github.javaparser.ast.body.FieldDeclaration field = (com.github.javaparser.ast.body.FieldDeclaration) member; + for (VariableDeclarator vd : field.getVariables()) { + fields.add(new JavaParserFieldDeclaration(vd, typeSolver)); + } + } + } + } + return fields; + } + + public Set internalTypes() { + // Use a special Set implementation that avoids calculating the hashCode of the node, + // since this can be very time-consuming for big node trees, and we are sure there are + // no duplicates in the members list. + Set res = Collections.newSetFromMap(new IdentityHashMap<>()); + for (BodyDeclaration member : this.wrappedNode.getMembers()) { + if (member instanceof TypeDeclaration) { + res.add(JavaParserFacade.get(typeSolver).getTypeDeclaration((TypeDeclaration) member)); + } + } + return res; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserTypeParameter.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserTypeParameter.java new file mode 100644 index 0000000000000000000000000000000000000000..8927ea21802be77f4309e2a8bf333c00c8c0fd72 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserTypeParameter.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarations; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.type.ClassOrInterfaceType; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.logic.AbstractTypeDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; + +import java.util.*; +import java.util.stream.Collectors; + +import static com.github.javaparser.symbolsolver.javaparser.Navigator.demandParentNode; + +/** + * @author Federico Tomassetti + */ +public class JavaParserTypeParameter extends AbstractTypeDeclaration implements ResolvedTypeParameterDeclaration { + + private com.github.javaparser.ast.type.TypeParameter wrappedNode; + private TypeSolver typeSolver; + + public JavaParserTypeParameter(com.github.javaparser.ast.type.TypeParameter wrappedNode, TypeSolver typeSolver) { + this.wrappedNode = wrappedNode; + this.typeSolver = typeSolver; + } + + @Override + public Set getDeclaredMethods() { + return Collections.emptySet(); + } + + public SymbolReference solveMethod(String name, List parameterTypes) { + return getContext().solveMethod(name, parameterTypes, false); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof JavaParserTypeParameter)) return false; + + JavaParserTypeParameter that = (JavaParserTypeParameter) o; + + if (wrappedNode != null ? !wrappedNode.equals(that.wrappedNode) : that.wrappedNode != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = wrappedNode != null ? wrappedNode.hashCode() : 0; + result = 31 * result + (typeSolver != null ? typeSolver.hashCode() : 0); + return result; + } + + @Override + public String getName() { + return wrappedNode.getName().getId(); + } + + @Override + public boolean isAssignableBy(ResolvedReferenceTypeDeclaration other) { + return isAssignableBy(new ReferenceTypeImpl(other, typeSolver)); + } + + @Override + public String getContainerQualifiedName() { + ResolvedTypeParametrizable container = getContainer(); + if (container instanceof ResolvedReferenceTypeDeclaration) { + return ((ResolvedReferenceTypeDeclaration) container).getQualifiedName(); + } else if (container instanceof JavaParserConstructorDeclaration) { + return ((JavaParserConstructorDeclaration) container).getQualifiedSignature(); + } else { + return ((JavaParserMethodDeclaration) container).getQualifiedSignature(); + } + } + + @Override + public String getContainerId() { + ResolvedTypeParametrizable container = getContainer(); + if (container instanceof ResolvedReferenceTypeDeclaration) { + return ((ResolvedReferenceTypeDeclaration) container).getId(); + } else if (container instanceof JavaParserConstructorDeclaration) { + return ((JavaParserConstructorDeclaration) container).getQualifiedSignature(); + } else { + return ((JavaParserMethodDeclaration) container).getQualifiedSignature(); + } + } + + @Override + public ResolvedTypeParametrizable getContainer() { + Node parentNode = demandParentNode(wrappedNode); + if (parentNode instanceof com.github.javaparser.ast.body.ClassOrInterfaceDeclaration) { + com.github.javaparser.ast.body.ClassOrInterfaceDeclaration jpTypeDeclaration = (com.github.javaparser.ast.body.ClassOrInterfaceDeclaration) parentNode; + return JavaParserFacade.get(typeSolver).getTypeDeclaration(jpTypeDeclaration); + } else if (parentNode instanceof com.github.javaparser.ast.body.ConstructorDeclaration){ + com.github.javaparser.ast.body.ConstructorDeclaration jpConstructorDeclaration = (com.github.javaparser.ast.body.ConstructorDeclaration) parentNode; + Optional jpTypeDeclaration = jpConstructorDeclaration.findAncestor(com.github.javaparser.ast.body.ClassOrInterfaceDeclaration.class); + if (jpTypeDeclaration.isPresent()) { + ResolvedReferenceTypeDeclaration typeDeclaration = JavaParserFacade.get(typeSolver).getTypeDeclaration(jpTypeDeclaration.get()); + if (typeDeclaration.isClass()) { + return new JavaParserConstructorDeclaration(typeDeclaration.asClass(), jpConstructorDeclaration, typeSolver); + } + } + } else { + com.github.javaparser.ast.body.MethodDeclaration jpMethodDeclaration = (com.github.javaparser.ast.body.MethodDeclaration) parentNode; + return new JavaParserMethodDeclaration(jpMethodDeclaration, typeSolver); + } + throw new UnsupportedOperationException(); + } + + @Override + public String getQualifiedName() { + return String.format("%s.%s", getContainerQualifiedName(), getName()); + } + + @Override + public List getBounds() { + return wrappedNode.getTypeBound().stream().map((astB) -> toBound(astB, typeSolver)).collect(Collectors.toList()); + } + + private Bound toBound(ClassOrInterfaceType classOrInterfaceType, TypeSolver typeSolver) { + ResolvedType type = JavaParserFacade.get(typeSolver).convertToUsage(classOrInterfaceType, classOrInterfaceType); + return Bound.extendsBound(type); + } + + public Context getContext() { + throw new UnsupportedOperationException(); + } + + public ResolvedType getUsage(Node node) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isAssignableBy(ResolvedType type) { + throw new UnsupportedOperationException(); + } + + @Override + public ResolvedFieldDeclaration getField(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasField(String name) { + return false; + } + + @Override + public List getAllFields() { + return new ArrayList<>(); + } + + @Override + public List getAncestors(boolean acceptIncompleteList) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isTypeParameter() { + return true; + } + + @Override + public ResolvedTypeParameterDeclaration asTypeParameter() { + return this; + } + + @Override + public boolean hasDirectlyAnnotation(String canonicalName) { + throw new UnsupportedOperationException(); + } + + @Override + public List getTypeParameters() { + return Collections.emptyList(); + } + + /** + * Returns the JavaParser node associated with this JavaParserTypeParameter. + * + * @return A visitable JavaParser node wrapped by this object. + */ + public com.github.javaparser.ast.type.TypeParameter getWrappedNode() { + return wrappedNode; + } + + @Override + public String toString() { + return "JPTypeParameter(" + wrappedNode.getName() + ", bounds=" + wrappedNode.getTypeBound() + ")"; + } + + @Override + public Optional containerType() { + ResolvedTypeParametrizable container = getContainer(); + if (container instanceof ResolvedReferenceTypeDeclaration) { + return Optional.of((ResolvedReferenceTypeDeclaration) container); + } + return Optional.empty(); + } + + @Override + public List getConstructors() { + return Collections.emptyList(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserTypeVariableDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserTypeVariableDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..3a8cd8e236376651f39ca36410f8717f25c54fec --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserTypeVariableDeclaration.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarations; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.type.TypeParameter; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.logic.AbstractTypeDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; + +import java.util.*; + +/** + * @author Federico Tomassetti + */ +public class JavaParserTypeVariableDeclaration extends AbstractTypeDeclaration implements AssociableToAST { + + private TypeParameter wrappedNode; + private TypeSolver typeSolver; + + public JavaParserTypeVariableDeclaration(TypeParameter wrappedNode, TypeSolver typeSolver) { + this.wrappedNode = wrappedNode; + this.typeSolver = typeSolver; + } + + @Override + public boolean isAssignableBy(ResolvedReferenceTypeDeclaration other) { + return isAssignableBy(new ReferenceTypeImpl(other, typeSolver)); + } + + @Override + public String getPackageName() { + return AstResolutionUtils.getPackageName(wrappedNode); + } + + @Override + public String getClassName() { + return AstResolutionUtils.getClassName("", wrappedNode); + } + + @Override + public String getQualifiedName() { + return getName(); + } + + public Context getContext() { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return "JavaParserTypeVariableDeclaration{" + + wrappedNode.getName() + + '}'; + } + + public SymbolReference solveMethod(String name, List parameterTypes) { + throw new UnsupportedOperationException(); + } + + public ResolvedType getUsage(Node node) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isAssignableBy(ResolvedType type) { + if (type.isTypeVariable()) { + throw new UnsupportedOperationException("Is this type variable declaration assignable by " + type.describe()); + } else { + throw new UnsupportedOperationException("Is this type variable declaration assignable by " + type); + } + } + + @Override + public boolean isTypeParameter() { + return true; + } + + @Override + public ResolvedFieldDeclaration getField(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasField(String name) { + return false; + } + + @Override + public List getAllFields() { + return new ArrayList<>(); + } + + @Override + public List getAncestors(boolean acceptIncompleteList) { + throw new UnsupportedOperationException(); + } + + @Override + public Set getDeclaredMethods() { + return Collections.emptySet(); + } + + @Override + public String getName() { + return wrappedNode.getName().getId(); + } + + @Override + public boolean isType() { + return true; + } + + @Override + public boolean hasDirectlyAnnotation(String canonicalName) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isClass() { + return false; + } + + @Override + public boolean isInterface() { + return false; + } + + @Override + public List getTypeParameters() { + return Collections.emptyList(); + } + + @Override + public ResolvedTypeParameterDeclaration asTypeParameter() { + return new JavaParserTypeParameter(this.wrappedNode, typeSolver); + } + + /** + * Returns the JavaParser node associated with this JavaParserTypeVariableDeclaration. + * + * @return A visitable JavaParser node wrapped by this object. + */ + public TypeParameter getWrappedNode() { + return wrappedNode; + } + + @Override + public Optional containerType() { + return asTypeParameter().containerType(); + } + + @Override + public List getConstructors() { + return Collections.emptyList(); + } + + @Override + public Optional toAst() { + return Optional.of(wrappedNode); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserVariableDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserVariableDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..6be6794377f9d237ddb7c80e5b951b591c8e8681 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserVariableDeclaration.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarations; + +import com.github.javaparser.ast.body.VariableDeclarator; +import com.github.javaparser.ast.expr.VariableDeclarationExpr; +import com.github.javaparser.resolution.declarations.AssociableToAST; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.Optional; + +import static com.github.javaparser.symbolsolver.javaparser.Navigator.demandParentNode; + +/** + * @author Federico Tomassetti + */ +public class JavaParserVariableDeclaration implements ResolvedValueDeclaration, AssociableToAST { + + private VariableDeclarator variableDeclarator; + private VariableDeclarationExpr wrappedNode; + private TypeSolver typeSolver; + + public JavaParserVariableDeclaration(VariableDeclarator variableDeclarator, TypeSolver typeSolver) { + if (typeSolver == null) { + throw new IllegalArgumentException("typeSolver should not be null"); + } + this.variableDeclarator = variableDeclarator; + this.typeSolver = typeSolver; + if (!(demandParentNode(variableDeclarator) instanceof VariableDeclarationExpr)) { + throw new IllegalStateException(demandParentNode(variableDeclarator).getClass().getCanonicalName()); + } + this.wrappedNode = (VariableDeclarationExpr) demandParentNode(variableDeclarator); + } + + @Override + public ResolvedType getType() { + return JavaParserFacade.get(typeSolver).convert(variableDeclarator.getType(), wrappedNode); + } + + @Override + public String getName() { + return variableDeclarator.getName().getId(); + } + + @Override + public boolean isVariable() { + return true; + } + + /** + * Returns the JavaParser node associated with this JavaParserFieldDeclaration. + * + * @return A visitable JavaParser node wrapped by this object. + */ + public VariableDeclarationExpr getWrappedNode() { + return wrappedNode; + } + + public VariableDeclarator getVariableDeclarator() { + return variableDeclarator; + } + + @Override + public String toString() { + return "JavaParserVariableDeclaration{" + getName() + "}"; + } + + @Override + public Optional toAst() { + return Optional.of(wrappedNode); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarators/AbstractSymbolDeclarator.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarators/AbstractSymbolDeclarator.java new file mode 100644 index 0000000000000000000000000000000000000000..99bcb0450eb5b06f09fb466957b475bc22412c14 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarators/AbstractSymbolDeclarator.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarators; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.resolution.SymbolDeclarator; + +/** + * @author Federico Tomassetti + */ +public abstract class AbstractSymbolDeclarator implements SymbolDeclarator { + + protected N wrappedNode; + protected TypeSolver typeSolver; + + public AbstractSymbolDeclarator(N wrappedNode, TypeSolver typeSolver) { + this.wrappedNode = wrappedNode; + this.typeSolver = typeSolver; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarators/FieldSymbolDeclarator.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarators/FieldSymbolDeclarator.java new file mode 100644 index 0000000000000000000000000000000000000000..74a72012d065f8f1dbfe579ebe7a21794c491e7d --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarators/FieldSymbolDeclarator.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarators; + +import com.github.javaparser.ast.body.FieldDeclaration; +import com.github.javaparser.ast.body.VariableDeclarator; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserSymbolDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.LinkedList; +import java.util.List; + +/** + * @author Federico Tomassetti + */ +public class FieldSymbolDeclarator extends AbstractSymbolDeclarator { + + public FieldSymbolDeclarator(FieldDeclaration wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + @Override + public List getSymbolDeclarations() { + List symbols = new LinkedList<>(); + for (VariableDeclarator v : wrappedNode.getVariables()) { + symbols.add(JavaParserSymbolDeclaration.field(v, typeSolver)); + } + return symbols; + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarators/NoSymbolDeclarator.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarators/NoSymbolDeclarator.java new file mode 100644 index 0000000000000000000000000000000000000000..0ce3daa123f0c077168b84f7d8d9973e1b5c2e8f --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarators/NoSymbolDeclarator.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarators; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.Collections; +import java.util.List; + +/** + * @author Federico Tomassetti + */ +public class NoSymbolDeclarator extends AbstractSymbolDeclarator { + + public NoSymbolDeclarator(N wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + @Override + public List getSymbolDeclarations() { + return Collections.emptyList(); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarators/ParameterSymbolDeclarator.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarators/ParameterSymbolDeclarator.java new file mode 100644 index 0000000000000000000000000000000000000000..7ac9adae311a371c400b34c6265ef134b2f55ada --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarators/ParameterSymbolDeclarator.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarators; + +import com.github.javaparser.ast.body.Parameter; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserSymbolDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.LinkedList; +import java.util.List; + +/** + * @author Federico Tomassetti + */ +public class ParameterSymbolDeclarator extends AbstractSymbolDeclarator { + + public ParameterSymbolDeclarator(Parameter wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + @Override + public List getSymbolDeclarations() { + List symbols = new LinkedList<>(); + symbols.add(JavaParserSymbolDeclaration.parameter(wrappedNode, typeSolver)); + return symbols; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarators/PatternSymbolDeclarator.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarators/PatternSymbolDeclarator.java new file mode 100644 index 0000000000000000000000000000000000000000..e5b883713c6dba2ea57903d50d71b50c9dc9abe4 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarators/PatternSymbolDeclarator.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarators; + +import com.github.javaparser.ast.expr.PatternExpr; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserSymbolDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.LinkedList; +import java.util.List; + +/** + * @author Roger Howell + */ +public class PatternSymbolDeclarator extends AbstractSymbolDeclarator { + + public PatternSymbolDeclarator(PatternExpr wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + } + + @Override + public List getSymbolDeclarations() { + List symbols = new LinkedList<>(); + symbols.add(JavaParserSymbolDeclaration.patternVar(wrappedNode, typeSolver)); + return symbols; + } + + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarators/VariableSymbolDeclarator.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarators/VariableSymbolDeclarator.java new file mode 100644 index 0000000000000000000000000000000000000000..131538ae37ebb8354969c59cdcdd13d6647a72ca --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarators/VariableSymbolDeclarator.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javaparsermodel.declarators; + +import com.github.javaparser.ast.body.FieldDeclaration; +import com.github.javaparser.ast.expr.VariableDeclarationExpr; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserSymbolDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +public class VariableSymbolDeclarator extends AbstractSymbolDeclarator { + + public VariableSymbolDeclarator(VariableDeclarationExpr wrappedNode, TypeSolver typeSolver) { + super(wrappedNode, typeSolver); + wrappedNode.getParentNode().ifPresent(p -> { + if (p instanceof FieldDeclaration) { + throw new IllegalArgumentException(); + } + }); + } + + @Override + public List getSymbolDeclarations() { + List variables = wrappedNode.getVariables() + .stream() + .map(v -> JavaParserSymbolDeclaration.localVar(v, typeSolver)) + .collect(Collectors.toCollection(ArrayList::new)); + +// // FIXME: This returns ALL PatternExpr, regardless of whether it is in scope or not. +// List patterns = wrappedNode.getVariables() +// .stream() +// .filter(variableDeclarator -> variableDeclarator.getInitializer().isPresent()) +// .map(variableDeclarator -> variableDeclarator.getInitializer().get()) +// .map(expression -> expression.findAll(PatternExpr.class)) +// .flatMap(Collection::stream) +// .map(v -> JavaParserSymbolDeclaration.patternVar(v, typeSolver)) +// .collect(Collectors.toCollection(ArrayList::new)); + + List all = new ArrayList<>(variables); +// all.addAll(patterns); + + return all; + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/package-info.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..df936a1709dc4261ba050861a7cacf0ffda16fa9 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/package-info.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +/** + * Implementation of model based on JavaParser. + */ +package com.github.javaparser.symbolsolver.javaparsermodel; diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistAnnotationDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistAnnotationDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..c716d10a74443fb81f1bd7735ec3a4e20b9adf6a --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistAnnotationDeclaration.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javassistmodel; + +import com.github.javaparser.ast.body.AnnotationDeclaration; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.logic.AbstractTypeDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import javassist.CtClass; + +import java.lang.annotation.Inherited; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author Malte Skoruppa + */ +public class JavassistAnnotationDeclaration extends AbstractTypeDeclaration implements ResolvedAnnotationDeclaration { + + private CtClass ctClass; + private TypeSolver typeSolver; + private JavassistTypeDeclarationAdapter javassistTypeDeclarationAdapter; + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + + "ctClass=" + ctClass.getName() + + ", typeSolver=" + typeSolver + + '}'; + } + + public JavassistAnnotationDeclaration(CtClass ctClass, TypeSolver typeSolver) { + if (!ctClass.isAnnotation()) { + throw new IllegalArgumentException("Not an annotation: " + ctClass.getName()); + } + this.ctClass = ctClass; + this.typeSolver = typeSolver; + this.javassistTypeDeclarationAdapter = new JavassistTypeDeclarationAdapter(ctClass, typeSolver, this); + } + + @Override + public String getPackageName() { + return ctClass.getPackageName(); + } + + @Override + public String getClassName() { + String qualifiedName = getQualifiedName(); + if (qualifiedName.contains(".")) { + return qualifiedName.substring(qualifiedName.lastIndexOf(".") + 1, qualifiedName.length()); + } else { + return qualifiedName; + } + } + + @Override + public String getQualifiedName() { + return ctClass.getName().replace('$', '.'); + } + + @Override + public boolean isAssignableBy(ResolvedType type) { + // TODO #1836 + throw new UnsupportedOperationException(); + } + + @Override + public List getAllFields() { + return javassistTypeDeclarationAdapter.getDeclaredFields(); + } + + @Override + public boolean isAssignableBy(ResolvedReferenceTypeDeclaration other) { + throw new UnsupportedOperationException(); + } + + @Override + public List getAncestors(boolean acceptIncompleteList) { + return javassistTypeDeclarationAdapter.getAncestors(acceptIncompleteList); + } + + @Override + public Set internalTypes() { + return javassistTypeDeclarationAdapter.internalTypes(); + } + + @Override + public Set getDeclaredMethods() { + // TODO #1838 + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasDirectlyAnnotation(String canonicalName) { + return ctClass.hasAnnotation(canonicalName); + } + + @Override + public String getName() { + return getClassName(); + } + + /** + * Annotation declarations cannot have type parameters and hence this method always returns an empty list. + * + * @return An empty list. + */ + @Override + public List getTypeParameters() { + // Annotation declarations cannot have type parameters - i.e. we can always return an empty list. + return Collections.emptyList(); + } + + @Override + public Optional containerType() { + // TODO #1841 + throw new UnsupportedOperationException("containerType() is not supported for " + this.getClass().getCanonicalName()); + } + + @Override + public List getConstructors() { + return Collections.emptyList(); + } + + @Override + public List getAnnotationMembers() { + return Stream.of(ctClass.getDeclaredMethods()) + .map(m -> new JavassistAnnotationMemberDeclaration(m, typeSolver)) + .collect(Collectors.toList()); + } + + @Override + public Optional toAst() { + return Optional.empty(); + } + + @Override + public boolean isInheritable() { + try { + return ctClass.getAnnotation(Inherited.class) != null; + } catch (ClassNotFoundException e) { + return false; + } + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistAnnotationMemberDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistAnnotationMemberDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..76f7c8643d60b494b748c3f5c5ebd2b018256df5 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistAnnotationMemberDeclaration.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javassistmodel; + +import com.github.javaparser.ast.expr.*; +import com.github.javaparser.resolution.declarations.ResolvedAnnotationMemberDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import javassist.CtMethod; +import javassist.bytecode.AnnotationDefaultAttribute; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.SignatureAttribute; +import javassist.bytecode.annotation.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +/** + * @author Malte Skoruppa + */ +public class JavassistAnnotationMemberDeclaration implements ResolvedAnnotationMemberDeclaration { + + private static Map, Function> memberValueAsExressionConverter = new HashMap<>(); + static { + memberValueAsExressionConverter.put(BooleanMemberValue.class, (memberValue) -> new BooleanLiteralExpr(BooleanMemberValue.class.cast(memberValue).getValue())); + memberValueAsExressionConverter.put(CharMemberValue.class, (memberValue) -> new CharLiteralExpr(CharMemberValue.class.cast(memberValue).getValue())); + memberValueAsExressionConverter.put(DoubleMemberValue.class, (memberValue) -> new DoubleLiteralExpr(DoubleMemberValue.class.cast(memberValue).getValue())); + memberValueAsExressionConverter.put(IntegerMemberValue.class, (memberValue) -> new IntegerLiteralExpr(IntegerMemberValue.class.cast(memberValue).getValue())); + memberValueAsExressionConverter.put(LongMemberValue.class, (memberValue) -> new LongLiteralExpr(LongMemberValue.class.cast(memberValue).getValue())); + memberValueAsExressionConverter.put(StringMemberValue.class, (memberValue) -> new StringLiteralExpr(StringMemberValue.class.cast(memberValue).getValue())); + } + + private CtMethod annotationMember; + private TypeSolver typeSolver; + + public JavassistAnnotationMemberDeclaration(CtMethod annotationMember, TypeSolver typeSolver) { + this.annotationMember = annotationMember; + this.typeSolver = typeSolver; + } + + @Override + public Expression getDefaultValue() { + AnnotationDefaultAttribute defaultAttribute = (AnnotationDefaultAttribute) annotationMember.getMethodInfo().getAttribute(AnnotationDefaultAttribute.tag); + if (defaultAttribute == null) return null; + MemberValue memberValue = defaultAttribute.getDefaultValue(); + Function fn = memberValueAsExressionConverter.get(memberValue.getClass()); + if (fn == null) throw new UnsupportedOperationException(String.format("Obtaining the type of the annotation member %s is not supported yet.", annotationMember.getName())); + return fn.apply(memberValue); + } + + @Override + public ResolvedType getType() { + try { + String descriptor = annotationMember.getMethodInfo().getDescriptor(); + SignatureAttribute.MethodSignature signature = SignatureAttribute.toMethodSignature(descriptor); + SymbolReference returnType = typeSolver.tryToSolveType(signature.getReturnType().jvmTypeName()); + if (returnType.isSolved()) { + return new ReferenceTypeImpl(returnType.getCorrespondingDeclaration(), typeSolver); + } + } catch (BadBytecode e) { + // We don't expect this to happen, but we handle it anyway. + throw new IllegalStateException("An invalid descriptor was received from JavaAssist.", e); + } + throw new UnsupportedOperationException(String.format("Obtaining the type of the annotation member %s is not supported yet.", annotationMember.getLongName())); + } + + @Override + public String getName() { + return annotationMember.getName(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistClassDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistClassDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..443bb7369fff7fc2ec2b1d4cc5e98a2dd371b773 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistClassDeclaration.java @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javassistmodel; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.ast.Node; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.core.resolution.MethodUsageResolutionCapability; +import com.github.javaparser.symbolsolver.javaparsermodel.LambdaArgumentTypePlaceholder; +import com.github.javaparser.symbolsolver.logic.AbstractClassDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.symbolsolver.resolution.SymbolSolver; +import javassist.CtClass; +import javassist.CtField; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +/** + * @author Federico Tomassetti + */ +public class JavassistClassDeclaration extends AbstractClassDeclaration implements MethodUsageResolutionCapability { + + private CtClass ctClass; + private TypeSolver typeSolver; + private JavassistTypeDeclarationAdapter javassistTypeDeclarationAdapter; + + public JavassistClassDeclaration(CtClass ctClass, TypeSolver typeSolver) { + if (ctClass == null) { + throw new IllegalArgumentException(); + } + if (ctClass.isInterface() || ctClass.isAnnotation() || ctClass.isPrimitive() || ctClass.isEnum()) { + throw new IllegalArgumentException("Trying to instantiate a JavassistClassDeclaration with something which is not a class: " + ctClass.toString()); + } + this.ctClass = ctClass; + this.typeSolver = typeSolver; + this.javassistTypeDeclarationAdapter = new JavassistTypeDeclarationAdapter(ctClass, typeSolver, this); + } + + @Override + protected ResolvedReferenceType object() { + return new ReferenceTypeImpl(typeSolver.getSolvedJavaLangObject(), typeSolver); + } + + @Override + public boolean hasDirectlyAnnotation(String canonicalName) { + return ctClass.hasAnnotation(canonicalName); + } + + @Override + public Set getDeclaredMethods() { + return javassistTypeDeclarationAdapter.getDeclaredMethods(); + } + + @Override + public boolean isAssignableBy(ResolvedReferenceTypeDeclaration other) { + return isAssignableBy(new ReferenceTypeImpl(other, typeSolver)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + JavassistClassDeclaration that = (JavassistClassDeclaration) o; + + return ctClass.equals(that.ctClass); + } + + @Override + public int hashCode() { + return ctClass.hashCode(); + } + + @Override + public String getPackageName() { + return ctClass.getPackageName(); + } + + @Override + public String getClassName() { + String className = ctClass.getName().replace('$', '.'); + if (getPackageName() != null) { + return className.substring(getPackageName().length() + 1); + } + return className; + } + + @Override + public String getQualifiedName() { + return ctClass.getName().replace('$', '.'); + } + + @Deprecated + public Optional solveMethodAsUsage(String name, List argumentsTypes, + Context invokationContext, List typeParameterValues) { + return JavassistUtils.solveMethodAsUsage(name, argumentsTypes, typeSolver, invokationContext, typeParameterValues, this, ctClass); + } + + @Deprecated + public SymbolReference solveSymbol(String name, TypeSolver typeSolver) { + for (CtField field : ctClass.getDeclaredFields()) { + if (field.getName().equals(name)) { + return SymbolReference.solved(new JavassistFieldDeclaration(field, typeSolver)); + } + } + + final String superclassFQN = getSuperclassFQN(); + SymbolReference ref = solveSymbolForFQN(name, superclassFQN); + if (ref.isSolved()) { + return ref; + } + + String[] interfaceFQNs = getInterfaceFQNs(); + for (String interfaceFQN : interfaceFQNs) { + SymbolReference interfaceRef = solveSymbolForFQN(name, interfaceFQN); + if (interfaceRef.isSolved()) { + return interfaceRef; + } + } + + return SymbolReference.unsolved(ResolvedValueDeclaration.class); + } + + private SymbolReference solveSymbolForFQN(String symbolName, String fqn) { + if (fqn == null) { + return SymbolReference.unsolved(ResolvedValueDeclaration.class); + } + + ResolvedReferenceTypeDeclaration fqnTypeDeclaration = typeSolver.solveType(fqn); + return new SymbolSolver(typeSolver).solveSymbolInType(fqnTypeDeclaration, symbolName); + } + + private String[] getInterfaceFQNs() { + return ctClass.getClassFile().getInterfaces(); + } + + private String getSuperclassFQN() { + return ctClass.getClassFile().getSuperclass(); + } + + @Override + public List getAncestors(boolean acceptIncompleteList) { + return javassistTypeDeclarationAdapter.getAncestors(acceptIncompleteList); + } + + @Override + @Deprecated + public SymbolReference solveMethod(String name, List argumentsTypes, boolean staticOnly) { + return JavassistUtils.solveMethod(name, argumentsTypes, staticOnly, typeSolver, this, ctClass); + } + + public ResolvedType getUsage(Node node) { + return new ReferenceTypeImpl(this, typeSolver); + } + + @Override + public boolean isAssignableBy(ResolvedType type) { + if (type.isNull()) { + return true; + } + + if (type instanceof LambdaArgumentTypePlaceholder) { + return isFunctionalInterface(); + } + + // TODO look into generics + if (type.describe().equals(this.getQualifiedName())) { + return true; + } + + Optional superClassOpt = getSuperClass(); + if (superClassOpt.isPresent()) { + ResolvedReferenceType superClass = superClassOpt.get(); + if (superClass.isAssignableBy(type)) { + return true; + } + } + + for (ResolvedReferenceType interfaceType : getInterfaces()) { + if (interfaceType.isAssignableBy(type)) { + return true; + } + } + + return false; + } + + @Override + public boolean isTypeParameter() { + return false; + } + + @Override + public List getAllFields() { + return javassistTypeDeclarationAdapter.getDeclaredFields(); + } + + @Override + public String getName() { + String[] nameElements = ctClass.getSimpleName().replace('$', '.').split("\\."); + return nameElements[nameElements.length - 1]; + } + + @Override + public boolean isField() { + return false; + } + + @Override + public boolean isParameter() { + return false; + } + + @Override + public boolean isType() { + return true; + } + + @Override + public boolean isClass() { + return !ctClass.isInterface(); + } + + @Override + public Optional getSuperClass() { + return javassistTypeDeclarationAdapter.getSuperClass(); + } + + @Override + public List getInterfaces() { + return javassistTypeDeclarationAdapter.getInterfaces(); + } + + @Override + public boolean isInterface() { + return ctClass.isInterface(); + } + + @Override + public String toString() { + return "JavassistClassDeclaration {" + ctClass.getName() + '}'; + } + + @Override + public List getTypeParameters() { + return javassistTypeDeclarationAdapter.getTypeParameters(); + } + + @Override + public AccessSpecifier accessSpecifier() { + return JavassistFactory.modifiersToAccessLevel(ctClass.getModifiers()); + } + + @Override + public List getConstructors() { + return javassistTypeDeclarationAdapter.getConstructors(); + } + + @Override + public Optional containerType() { + return javassistTypeDeclarationAdapter.containerType(); + } + + @Override + public Set internalTypes() { + return javassistTypeDeclarationAdapter.internalTypes(); + } + + @Override + public ResolvedReferenceTypeDeclaration getInternalType(String name) { + /* + The name of the ReferenceTypeDeclaration could be composed of the internal class and the outer class, e.g. A$B. That's why we search the internal type in the ending part. + In case the name is composed of the internal type only, i.e. f.getName() returns B, it will also works. + */ + Optional type = + this.internalTypes().stream().filter(f -> f.getName().endsWith(name)).findFirst(); + return type.orElseThrow(() -> + new UnsolvedSymbolException("Internal type not found: " + name)); + } + + @Override + public boolean hasInternalType(String name) { + /* + The name of the ReferenceTypeDeclaration could be composed of the internal class and the outer class, e.g. A$B. That's why we search the internal type in the ending part. + In case the name is composed of the internal type only, i.e. f.getName() returns B, it will also works. + */ + return this.internalTypes().stream().anyMatch(f -> f.getName().endsWith(name)); + } + + @Override + public Optional toAst() { + return Optional.empty(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistConstructorDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistConstructorDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..280e28defecfe181610d3b0de80d6cbf33d3177e --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistConstructorDeclaration.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javassistmodel; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.ast.body.ConstructorDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedConstructorDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedParameterDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import javassist.CtConstructor; + +import java.util.List; +import java.util.Optional; + +/** + * @author Fred Lefévère-Laoide + */ +public class JavassistConstructorDeclaration implements ResolvedConstructorDeclaration { + private final CtConstructor ctConstructor; + private final TypeSolver typeSolver; + private final JavassistMethodLikeDeclarationAdapter methodLikeAdaper; + + public JavassistConstructorDeclaration(CtConstructor ctConstructor, TypeSolver typeSolver) { + this.ctConstructor = ctConstructor; + this.typeSolver = typeSolver; + this.methodLikeAdaper = new JavassistMethodLikeDeclarationAdapter(ctConstructor, typeSolver, this); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + + "ctConstructor=" + ctConstructor.getName() + + ", typeSolver=" + typeSolver + + '}'; + } + + @Override + public String getName() { + return ctConstructor.getName(); + } + + @Override + public boolean isField() { + return false; + } + + @Override + public boolean isParameter() { + return false; + } + + @Override + public boolean isType() { + return false; + } + + @Override + public ResolvedReferenceTypeDeclaration declaringType() { + return JavassistFactory.toTypeDeclaration(ctConstructor.getDeclaringClass(), typeSolver); + } + + @Override + public int getNumberOfParams() { + return methodLikeAdaper.getNumberOfParams(); + } + + @Override + public ResolvedParameterDeclaration getParam(int i) { + return methodLikeAdaper.getParam(i); + } + + @Override + public List getTypeParameters() { + return methodLikeAdaper.getTypeParameters(); + } + + @Override + public AccessSpecifier accessSpecifier() { + return JavassistFactory.modifiersToAccessLevel(ctConstructor.getModifiers()); + } + + @Override + public int getNumberOfSpecifiedExceptions() { + return methodLikeAdaper.getNumberOfSpecifiedExceptions(); + } + + @Override + public ResolvedType getSpecifiedException(int index) { + return methodLikeAdaper.getSpecifiedException(index); + } + + @Override + public Optional toAst() { + return Optional.empty(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistEnumConstantDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistEnumConstantDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..e9db25eb14252123a63c0a53896175eb747563ac --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistEnumConstantDeclaration.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javassistmodel; + +import com.github.javaparser.resolution.declarations.ResolvedEnumConstantDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import javassist.CtField; +import javassist.bytecode.AccessFlag; + +/** + * @author Federico Tomassetti + */ +public class JavassistEnumConstantDeclaration implements ResolvedEnumConstantDeclaration { + + private CtField ctField; + private TypeSolver typeSolver; + private ResolvedType type; + + public JavassistEnumConstantDeclaration(CtField ctField, TypeSolver typeSolver) { + if (ctField == null) { + throw new IllegalArgumentException(); + } + if ((ctField.getFieldInfo2().getAccessFlags() & AccessFlag.ENUM) == 0) { + throw new IllegalArgumentException( + "Trying to instantiate a JavassistEnumConstantDeclaration with something which is not an enum field: " + + ctField.toString()); + } + this.ctField = ctField; + this.typeSolver = typeSolver; + } + + + @Override + public String getName() { + return ctField.getName(); + } + + @Override + public ResolvedType getType() { + if (type == null) { + type = new ReferenceTypeImpl(new JavassistEnumDeclaration(ctField.getDeclaringClass(), typeSolver), + typeSolver); + } + return type; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + + "ctField=" + ctField.getName() + + ", typeSolver=" + typeSolver + + '}'; + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistEnumDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistEnumDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..131c3100cdf60d29fba9e628d25f7fc8b9820108 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistEnumDeclaration.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javassistmodel; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.core.resolution.MethodUsageResolutionCapability; +import com.github.javaparser.symbolsolver.logic.AbstractTypeDeclaration; +import com.github.javaparser.symbolsolver.logic.MethodResolutionCapability; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.resolution.SymbolSolver; +import javassist.CtClass; +import javassist.CtField; +import javassist.bytecode.AccessFlag; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +public class JavassistEnumDeclaration extends AbstractTypeDeclaration + implements ResolvedEnumDeclaration, MethodResolutionCapability, MethodUsageResolutionCapability { + + private CtClass ctClass; + private TypeSolver typeSolver; + private JavassistTypeDeclarationAdapter javassistTypeDeclarationAdapter; + + public JavassistEnumDeclaration(CtClass ctClass, TypeSolver typeSolver) { + if (ctClass == null) { + throw new IllegalArgumentException(); + } + if (!ctClass.isEnum()) { + throw new IllegalArgumentException("Trying to instantiate a JavassistEnumDeclaration with something which is not an enum: " + ctClass.toString()); + } + this.ctClass = ctClass; + this.typeSolver = typeSolver; + this.javassistTypeDeclarationAdapter = new JavassistTypeDeclarationAdapter(ctClass, typeSolver, this); + } + + @Override + public AccessSpecifier accessSpecifier() { + return JavassistFactory.modifiersToAccessLevel(ctClass.getModifiers()); + } + + @Override + public String getPackageName() { + return ctClass.getPackageName(); + } + + @Override + public String getClassName() { + String name = ctClass.getName().replace('$', '.'); + if (getPackageName() != null) { + return name.substring(getPackageName().length() + 1); + } + return name; + } + + @Override + public String getQualifiedName() { + return ctClass.getName().replace('$', '.'); + } + + @Override + public List getAncestors(boolean acceptIncompleteList) { + return javassistTypeDeclarationAdapter.getAncestors(acceptIncompleteList); + } + + @Override + public ResolvedFieldDeclaration getField(String name) { + Optional field = javassistTypeDeclarationAdapter.getDeclaredFields().stream().filter(f -> f.getName().equals(name)).findFirst(); + + return field.orElseThrow(() -> new RuntimeException("Field " + name + " does not exist in " + ctClass.getName() + ".")); + } + + @Override + public boolean hasField(String name) { + return javassistTypeDeclarationAdapter.getDeclaredFields().stream().anyMatch(f -> f.getName().equals(name)); + } + + @Override + public List getAllFields() { + return javassistTypeDeclarationAdapter.getDeclaredFields(); + } + + @Override + public Set getDeclaredMethods() { + return javassistTypeDeclarationAdapter.getDeclaredMethods(); + } + + @Override + public boolean isAssignableBy(ResolvedType type) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isAssignableBy(ResolvedReferenceTypeDeclaration other) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasDirectlyAnnotation(String canonicalName) { + return ctClass.hasAnnotation(canonicalName); + } + + @Override + public String getName() { + String[] nameElements = ctClass.getSimpleName().replace('$', '.').split("\\."); + return nameElements[nameElements.length - 1]; + } + + @Override + public List getTypeParameters() { + return javassistTypeDeclarationAdapter.getTypeParameters(); + } + + @Override + public Optional containerType() { + return javassistTypeDeclarationAdapter.containerType(); + } + + @Override + public SymbolReference solveMethod(String name, List argumentsTypes, boolean staticOnly) { + return JavassistUtils.solveMethod(name, argumentsTypes, staticOnly, typeSolver, this, ctClass); + } + + public Optional solveMethodAsUsage(String name, List argumentsTypes, + Context invokationContext, List typeParameterValues) { + return JavassistUtils.solveMethodAsUsage(name, argumentsTypes, typeSolver, invokationContext, typeParameterValues, this, ctClass); + } + + @Override + public Set internalTypes() { + return javassistTypeDeclarationAdapter.internalTypes(); + } + + @Override + public ResolvedReferenceTypeDeclaration getInternalType(String name) { + /* + The name of the ReferenceTypeDeclaration could be composed on the internal class and the outer class, e.g. A$B. That's why we search the internal type in the ending part. + In case the name is composed of the internal type only, i.e. f.getName() returns B, it will also works. + */ + Optional type = + this.internalTypes().stream().filter(f -> f.getName().endsWith(name)).findFirst(); + return type.orElseThrow(() -> + new UnsolvedSymbolException("Internal type not found: " + name)); + } + + @Override + public boolean hasInternalType(String name) { + /* + The name of the ReferenceTypeDeclaration could be composed on the internal class and the outer class, e.g. A$B. That's why we search the internal type in the ending part. + In case the name is composed of the internal type only, i.e. f.getName() returns B, it will also works. + */ + return this.internalTypes().stream().anyMatch(f -> f.getName().endsWith(name)); + } + + public SymbolReference solveSymbol(String name, TypeSolver typeSolver) { + for (CtField field : ctClass.getDeclaredFields()) { + if (field.getName().equals(name)) { + return SymbolReference.solved(new JavassistFieldDeclaration(field, typeSolver)); + } + } + + String[] interfaceFQNs = getInterfaceFQNs(); + for (String interfaceFQN : interfaceFQNs) { + SymbolReference interfaceRef = solveSymbolForFQN(name, interfaceFQN); + if (interfaceRef.isSolved()) { + return interfaceRef; + } + } + + return SymbolReference.unsolved(ResolvedValueDeclaration.class); + } + + private SymbolReference solveSymbolForFQN(String symbolName, String fqn) { + if (fqn == null) { + return SymbolReference.unsolved(ResolvedValueDeclaration.class); + } + + ResolvedReferenceTypeDeclaration fqnTypeDeclaration = typeSolver.solveType(fqn); + return new SymbolSolver(typeSolver).solveSymbolInType(fqnTypeDeclaration, symbolName); + } + + private String[] getInterfaceFQNs() { + return ctClass.getClassFile().getInterfaces(); + } + + @Override + public List getEnumConstants() { + return Arrays.stream(ctClass.getFields()) + .filter(f -> (f.getFieldInfo2().getAccessFlags() & AccessFlag.ENUM) != 0) + .map(f -> new JavassistEnumConstantDeclaration(f, typeSolver)) + .collect(Collectors.toList()); + } + + @Override + public List getConstructors() { + return javassistTypeDeclarationAdapter.getConstructors(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + + "ctClass=" + ctClass.getName() + + ", typeSolver=" + typeSolver + + '}'; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistFactory.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..2f5a918b454e9da97abecceeedcee9c5545e80c8 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistFactory.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javassistmodel; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.types.ResolvedArrayType; +import com.github.javaparser.resolution.types.ResolvedPrimitiveType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.resolution.types.ResolvedVoidType; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import javassist.CtClass; +import javassist.NotFoundException; + +import java.lang.reflect.Modifier; + +/** + * @author Federico Tomassetti + */ +public class JavassistFactory { + + public static ResolvedType typeUsageFor(CtClass ctClazz, TypeSolver typeSolver) { + try { + if (ctClazz.isArray()) { + return new ResolvedArrayType(typeUsageFor(ctClazz.getComponentType(), typeSolver)); + } else if (ctClazz.isPrimitive()) { + if (ctClazz.getName().equals("void")) { + return ResolvedVoidType.INSTANCE; + } else { + return ResolvedPrimitiveType.byName(ctClazz.getName()); + } + } else { + if (ctClazz.isInterface()) { + return new ReferenceTypeImpl(new JavassistInterfaceDeclaration(ctClazz, typeSolver), + typeSolver); + } else if (ctClazz.isEnum()) { + return new ReferenceTypeImpl(new JavassistEnumDeclaration(ctClazz, typeSolver), + typeSolver); + } else { + return new ReferenceTypeImpl(new JavassistClassDeclaration(ctClazz, typeSolver), + typeSolver); + } + } + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + } + + public static ResolvedReferenceTypeDeclaration toTypeDeclaration(CtClass ctClazz, TypeSolver typeSolver) { + if (ctClazz.isAnnotation()) { + return new JavassistAnnotationDeclaration(ctClazz, typeSolver); + } else if (ctClazz.isInterface()) { + return new JavassistInterfaceDeclaration(ctClazz, typeSolver); + } else if (ctClazz.isEnum()) { + return new JavassistEnumDeclaration(ctClazz, typeSolver); + } else if (ctClazz.isArray()) { + throw new IllegalArgumentException("This method should not be called passing an array"); + } else { + return new JavassistClassDeclaration(ctClazz, typeSolver); + } + } + + static AccessSpecifier modifiersToAccessLevel(final int modifiers) { + if (Modifier.isPublic(modifiers)) { + return AccessSpecifier.PUBLIC; + } else if (Modifier.isProtected(modifiers)) { + return AccessSpecifier.PROTECTED; + } else if (Modifier.isPrivate(modifiers)) { + return AccessSpecifier.PRIVATE; + } else { + return AccessSpecifier.PACKAGE_PRIVATE; + } + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistFieldDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistFieldDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..3822cce27309b893c6287adf9bfdabf694c71904 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistFieldDeclaration.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javassistmodel; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.resolution.declarations.ResolvedFieldDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParametrizable; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import javassist.CtField; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.SignatureAttribute; + +import java.lang.reflect.Modifier; + +/** + * @author Federico Tomassetti + */ +public class JavassistFieldDeclaration implements ResolvedFieldDeclaration { + private CtField ctField; + private TypeSolver typeSolver; + + public JavassistFieldDeclaration(CtField ctField, TypeSolver typeSolver) { + this.ctField = ctField; + this.typeSolver = typeSolver; + } + + @Override + public ResolvedType getType() { + try { + String signature = ctField.getGenericSignature(); + if (signature == null) { + signature = ctField.getSignature(); + } + SignatureAttribute.Type genericSignatureType = SignatureAttribute.toTypeSignature(signature); + return JavassistUtils.signatureTypeToType(genericSignatureType, typeSolver, (ResolvedTypeParametrizable) declaringType()); + } catch (BadBytecode e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean isStatic() { + return Modifier.isStatic(ctField.getModifiers()); + } + + @Override + public boolean isVolatile() { + return Modifier.isVolatile(ctField.getModifiers()); + } + + @Override + public String getName() { + return ctField.getName(); + } + + @Override + public boolean isField() { + return true; + } + + @Override + public boolean isParameter() { + return false; + } + + @Override + public boolean isType() { + return false; + } + + @Override + public AccessSpecifier accessSpecifier() { + return JavassistFactory.modifiersToAccessLevel(ctField.getModifiers()); + } + + @Override + public ResolvedTypeDeclaration declaringType() { + return JavassistFactory.toTypeDeclaration(ctField.getDeclaringClass(), typeSolver); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistInterfaceDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistInterfaceDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..100a59310b23ace688246cfe6248db47f0d8e9ab --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistInterfaceDeclaration.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javassistmodel; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.core.resolution.MethodUsageResolutionCapability; +import com.github.javaparser.symbolsolver.logic.AbstractTypeDeclaration; +import com.github.javaparser.symbolsolver.logic.MethodResolutionCapability; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.resolution.SymbolSolver; +import javassist.CtClass; +import javassist.CtField; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +public class JavassistInterfaceDeclaration extends AbstractTypeDeclaration + implements ResolvedInterfaceDeclaration, MethodResolutionCapability, MethodUsageResolutionCapability { + + private CtClass ctClass; + private TypeSolver typeSolver; + private JavassistTypeDeclarationAdapter javassistTypeDeclarationAdapter; + + @Override + public String toString() { + return "JavassistInterfaceDeclaration{" + + "ctClass=" + ctClass.getName() + + ", typeSolver=" + typeSolver + + '}'; + } + + public JavassistInterfaceDeclaration(CtClass ctClass, TypeSolver typeSolver) { + if (!ctClass.isInterface()) { + throw new IllegalArgumentException("Not an interface: " + ctClass.getName()); + } + this.ctClass = ctClass; + this.typeSolver = typeSolver; + this.javassistTypeDeclarationAdapter = new JavassistTypeDeclarationAdapter(ctClass, typeSolver, this); + } + + @Override + public List getInterfacesExtended() { + return javassistTypeDeclarationAdapter.getInterfaces(); + } + + @Override + public String getPackageName() { + return ctClass.getPackageName(); + } + + @Override + public String getClassName() { + String className = ctClass.getName().replace('$', '.'); + if (getPackageName() != null) { + return className.substring(getPackageName().length() + 1); + } + return className; + } + + @Override + public String getQualifiedName() { + return ctClass.getName().replace('$', '.'); + } + + @Deprecated + public Optional solveMethodAsUsage(String name, List argumentsTypes, + Context invokationContext, List typeParameterValues) { + return JavassistUtils.solveMethodAsUsage(name, argumentsTypes, typeSolver, invokationContext, typeParameterValues, this, ctClass); + } + + @Override + @Deprecated + public SymbolReference solveMethod(String name, List argumentsTypes, boolean staticOnly) { + return JavassistUtils.solveMethod(name, argumentsTypes, staticOnly, typeSolver, this, ctClass); + } + + @Override + public boolean isAssignableBy(ResolvedType type) { + return javassistTypeDeclarationAdapter.isAssignableBy(type); + } + + @Override + public List getAllFields() { + return javassistTypeDeclarationAdapter.getDeclaredFields(); + } + + @Override + public boolean isAssignableBy(ResolvedReferenceTypeDeclaration other) { + return javassistTypeDeclarationAdapter.isAssignableBy(other); + } + + @Override + public List getAncestors(boolean acceptIncompleteList) { + return javassistTypeDeclarationAdapter.getAncestors(acceptIncompleteList); + } + + @Override + public Set getDeclaredMethods() { + return Arrays.stream(ctClass.getDeclaredMethods()) + .map(m -> new JavassistMethodDeclaration(m, typeSolver)) + .collect(Collectors.toSet()); + } + + @Override + public boolean hasDirectlyAnnotation(String canonicalName) { + return ctClass.hasAnnotation(canonicalName); + } + + @Override + public String getName() { + String[] nameElements = ctClass.getSimpleName().replace('$', '.').split("\\."); + return nameElements[nameElements.length - 1]; + } + + @Override + public List getTypeParameters() { + return javassistTypeDeclarationAdapter.getTypeParameters(); + } + + @Override + public AccessSpecifier accessSpecifier() { + return JavassistFactory.modifiersToAccessLevel(ctClass.getModifiers()); + } + + @Override + public ResolvedInterfaceDeclaration asInterface() { + return this; + } + + + @Deprecated + public SymbolReference solveSymbol(String name, TypeSolver typeSolver) { + for (CtField field : ctClass.getDeclaredFields()) { + if (field.getName().equals(name)) { + return SymbolReference.solved(new JavassistFieldDeclaration(field, typeSolver)); + } + } + + String[] interfaceFQNs = getInterfaceFQNs(); + for (String interfaceFQN : interfaceFQNs) { + SymbolReference interfaceRef = solveSymbolForFQN(name, interfaceFQN); + if (interfaceRef.isSolved()) { + return interfaceRef; + } + } + + return SymbolReference.unsolved(ResolvedValueDeclaration.class); + } + + private SymbolReference solveSymbolForFQN(String symbolName, String fqn) { + if (fqn == null) { + return SymbolReference.unsolved(ResolvedValueDeclaration.class); + } + + ResolvedReferenceTypeDeclaration fqnTypeDeclaration = typeSolver.solveType(fqn); + return new SymbolSolver(typeSolver).solveSymbolInType(fqnTypeDeclaration, symbolName); + } + + private String[] getInterfaceFQNs() { + return ctClass.getClassFile().getInterfaces(); + } + + @Override + public Optional containerType() { + return javassistTypeDeclarationAdapter.containerType(); + } + + @Override + public Set internalTypes() { + return javassistTypeDeclarationAdapter.internalTypes(); + } + + @Override + public ResolvedReferenceTypeDeclaration getInternalType(String name) { + /* + The name of the ReferenceTypeDeclaration could be composed on the internal class and the outer class, e.g. A$B. That's why we search the internal type in the ending part. + In case the name is composed of the internal type only, i.e. f.getName() returns B, it will also works. + */ + Optional type = + this.internalTypes().stream().filter(f -> f.getName().endsWith(name)).findFirst(); + return type.orElseThrow(() -> + new UnsolvedSymbolException("Internal type not found: " + name)); + } + + @Override + public boolean hasInternalType(String name) { + /* + The name of the ReferenceTypeDeclaration could be composed on the internal class and the outer class, e.g. A$B. That's why we search the internal type in the ending part. + In case the name is composed of the internal type only, i.e. f.getName() returns B, it will also works. + */ + return this.internalTypes().stream().anyMatch(f -> f.getName().endsWith(name)); + } + + @Override + public List getConstructors() { + return Collections.emptyList(); + } + + @Override + public Optional toAst() { + return Optional.empty(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistMethodDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistMethodDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..f45d8ce935e80a3c9d17baf7f19a8cbbec197b52 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistMethodDeclaration.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javassistmodel; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedParameterDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.core.resolution.TypeVariableResolutionCapability; +import com.github.javaparser.symbolsolver.declarations.common.MethodDeclarationCommonLogic; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import javassist.CtMethod; + +import java.lang.reflect.Modifier; +import java.util.List; +import java.util.Optional; + +/** + * @author Federico Tomassetti + */ +public class JavassistMethodDeclaration implements ResolvedMethodDeclaration, TypeVariableResolutionCapability { + private CtMethod ctMethod; + private TypeSolver typeSolver; + private final JavassistMethodLikeDeclarationAdapter methodLikeAdaper; + + public JavassistMethodDeclaration(CtMethod ctMethod, TypeSolver typeSolver) { + this.ctMethod = ctMethod; + this.typeSolver = typeSolver; + this.methodLikeAdaper = new JavassistMethodLikeDeclarationAdapter(ctMethod, typeSolver, this); + } + + @Override + public boolean isDefaultMethod() { + return ctMethod.getDeclaringClass().isInterface() && !isAbstract(); + } + + @Override + public boolean isStatic() { + return Modifier.isStatic(ctMethod.getModifiers()); + } + + @Override + public String toString() { + return "JavassistMethodDeclaration{" + + "ctMethod=" + ctMethod + + '}'; + } + + @Override + public String getName() { + return ctMethod.getName(); + } + + @Override + public boolean isField() { + return false; + } + + @Override + public boolean isParameter() { + return false; + } + + @Override + public boolean isType() { + return false; + } + + @Override + public ResolvedReferenceTypeDeclaration declaringType() { + return JavassistFactory.toTypeDeclaration(ctMethod.getDeclaringClass(), typeSolver); + } + + @Override + public ResolvedType getReturnType() { + return methodLikeAdaper.getReturnType(); + } + + @Override + public int getNumberOfParams() { + return methodLikeAdaper.getNumberOfParams(); + } + + @Override + public ResolvedParameterDeclaration getParam(int i) { + return methodLikeAdaper.getParam(i); + } + + public MethodUsage getUsage(Node node) { + throw new UnsupportedOperationException(); + } + + @Override + public MethodUsage resolveTypeVariables(Context context, List parameterTypes) { + return new MethodDeclarationCommonLogic(this, typeSolver).resolveTypeVariables(context, parameterTypes); + } + + @Override + public boolean isAbstract() { + return Modifier.isAbstract(ctMethod.getModifiers()); + } + + @Override + public List getTypeParameters() { + return methodLikeAdaper.getTypeParameters(); + } + + @Override + public AccessSpecifier accessSpecifier() { + return JavassistFactory.modifiersToAccessLevel(ctMethod.getModifiers()); + } + + @Override + public int getNumberOfSpecifiedExceptions() { + return methodLikeAdaper.getNumberOfSpecifiedExceptions(); + } + + @Override + public ResolvedType getSpecifiedException(int index) { + return methodLikeAdaper.getSpecifiedException(index); + } + + @Override + public Optional toAst() { + return Optional.empty(); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistMethodLikeDeclarationAdapter.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistMethodLikeDeclarationAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..444aaacb0f5f94e0cfe155a89f2e6d41bf205b80 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistMethodLikeDeclarationAdapter.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javassistmodel; + +import com.github.javaparser.resolution.declarations.ResolvedMethodLikeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedParameterDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import javassist.CtBehavior; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.ExceptionsAttribute; +import javassist.bytecode.SignatureAttribute; + +import java.util.*; +import java.util.stream.Collectors; + +public class JavassistMethodLikeDeclarationAdapter { + + private CtBehavior ctBehavior; + private TypeSolver typeSolver; + private ResolvedMethodLikeDeclaration declaration; + + private SignatureAttribute.MethodSignature methodSignature; + + public JavassistMethodLikeDeclarationAdapter(CtBehavior ctBehavior, TypeSolver typeSolver, ResolvedMethodLikeDeclaration declaration) { + this.ctBehavior = ctBehavior; + this.typeSolver = typeSolver; + this.declaration = declaration; + + try { + String signature = ctBehavior.getGenericSignature(); + if (signature == null) { + signature = ctBehavior.getSignature(); + } + methodSignature = SignatureAttribute.toMethodSignature(signature); + } catch (BadBytecode e) { + throw new RuntimeException(e); + } + } + + public int getNumberOfParams() { + return methodSignature.getParameterTypes().length; + } + + public ResolvedParameterDeclaration getParam(int i) { + boolean variadic = false; + if ((ctBehavior.getModifiers() & javassist.Modifier.VARARGS) > 0) { + variadic = i == (methodSignature.getParameterTypes().length - 1); + } + + Optional paramName = JavassistUtils.extractParameterName(ctBehavior, i); + ResolvedType type = JavassistUtils.signatureTypeToType(methodSignature.getParameterTypes()[i], typeSolver, declaration); + return new JavassistParameterDeclaration(type, typeSolver, variadic, paramName.orElse(null)); + } + + public List getTypeParameters() { + if (ctBehavior.getGenericSignature() == null) { + return new ArrayList<>(); + } + return Arrays.stream(methodSignature.getTypeParameters()) + .map(jasTp -> new JavassistTypeParameter(jasTp, declaration, typeSolver)) + .collect(Collectors.toList()); + } + + public int getNumberOfSpecifiedExceptions() { + ExceptionsAttribute exceptionsAttribute = ctBehavior.getMethodInfo().getExceptionsAttribute(); + if (exceptionsAttribute == null) { + return 0; + } + + String[] exceptions = exceptionsAttribute.getExceptions(); + return exceptions == null ? 0 : exceptions.length; + } + + public ResolvedType getSpecifiedException(int index) { + if (index < 0) { + throw new IllegalArgumentException(String.format("index < 0: %d", index)); + } + + ExceptionsAttribute exceptionsAttribute = ctBehavior.getMethodInfo().getExceptionsAttribute(); + if (exceptionsAttribute == null) { + throw new IllegalArgumentException(String.format("No exception with index %d. Number of exceptions: 0", index)); + } + + String[] exceptions = exceptionsAttribute.getExceptions(); + if (exceptions == null || index >= exceptions.length) { + throw new IllegalArgumentException(String.format("No exception with index %d. Number of exceptions: %d", + index, getNumberOfSpecifiedExceptions())); + } + + ResolvedReferenceTypeDeclaration typeDeclaration = typeSolver.solveType(exceptions[index]); + return new ReferenceTypeImpl(typeDeclaration, Collections.emptyList(), typeSolver); + } + + public ResolvedType getReturnType() { + return JavassistUtils.signatureTypeToType(methodSignature.getReturnType(), typeSolver, declaration); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistParameterDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistParameterDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..a92214faef89b0f4b38216a313927f4c0b94ea97 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistParameterDeclaration.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javassistmodel; + +import com.github.javaparser.resolution.declarations.ResolvedParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import javassist.CtClass; + +/** + * @author Federico Tomassetti + */ +public class JavassistParameterDeclaration implements ResolvedParameterDeclaration { + private ResolvedType type; + private TypeSolver typeSolver; + private boolean variadic; + private String name; + + public JavassistParameterDeclaration(CtClass type, TypeSolver typeSolver, boolean variadic, String name) { + this(JavassistFactory.typeUsageFor(type, typeSolver), typeSolver, variadic, name); + } + + public JavassistParameterDeclaration(ResolvedType type, TypeSolver typeSolver, boolean variadic, String name) { + this.name = name; + this.type = type; + this.typeSolver = typeSolver; + this.variadic = variadic; + } + + @Override + public String toString() { + return "JavassistParameterDeclaration{" + + "type=" + type + + ", typeSolver=" + typeSolver + + ", variadic=" + variadic + + '}'; + } + + @Override + public boolean hasName() { + return name != null; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isField() { + return false; + } + + @Override + public boolean isParameter() { + return true; + } + + @Override + public boolean isVariadic() { + return variadic; + } + + @Override + public boolean isType() { + return false; + } + + @Override + public ResolvedType getType() { + return type; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistTypeDeclarationAdapter.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistTypeDeclarationAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..6243cb23e00c2ef08bf5c8cc93bbf732e68b0894 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistTypeDeclarationAdapter.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javassistmodel; + +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.LambdaArgumentTypePlaceholder; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import javassist.CtClass; +import javassist.CtField; +import javassist.NotFoundException; +import javassist.bytecode.AccessFlag; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.SignatureAttribute; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author Federico Tomassetti + */ +public class JavassistTypeDeclarationAdapter { + + private CtClass ctClass; + private TypeSolver typeSolver; + private ResolvedReferenceTypeDeclaration typeDeclaration; + + public JavassistTypeDeclarationAdapter(CtClass ctClass, TypeSolver typeSolver, ResolvedReferenceTypeDeclaration typeDeclaration) { + this.ctClass = ctClass; + this.typeSolver = typeSolver; + this.typeDeclaration = typeDeclaration; + } + + public Optional getSuperClass() { + try { + if ("java.lang.Object".equals(ctClass.getClassFile().getName())) { + // If this is java.lang.Object, ignore the presence of any superclass (preventing any infinite loops). + return Optional.empty(); + } + if (ctClass.getGenericSignature() == null) { + // Compiled classes have generic types erased, but can be made available for reflection via getGenericSignature(). + // If it is absent, then no further work is needed and we can return a reference type without generics. + return Optional.of(new ReferenceTypeImpl( + typeSolver.solveType(JavassistUtils.internalNameToCanonicalName(ctClass.getClassFile().getSuperclass())), + typeSolver + )); + } else { + // If there is a generic signature present, solve the types and return it. + SignatureAttribute.ClassSignature classSignature = SignatureAttribute.toClassSignature(ctClass.getGenericSignature()); + return Optional.ofNullable( + JavassistUtils.signatureTypeToType( + classSignature.getSuperClass(), + typeSolver, + typeDeclaration + ).asReferenceType() + ); + } + } catch (BadBytecode e) { + throw new RuntimeException(e); + } + } + + public List getInterfaces() { + return getInterfaces(false); + } + + private List getInterfaces(boolean acceptIncompleteList) { + List interfaces = new ArrayList<>(); + try { + if (ctClass.getGenericSignature() == null) { + for (String interfaceType : ctClass.getClassFile().getInterfaces()) { + try { + ResolvedReferenceTypeDeclaration declaration = typeSolver.solveType(JavassistUtils.internalNameToCanonicalName(interfaceType)); + interfaces.add(new ReferenceTypeImpl(declaration, typeSolver)); + } catch (UnsolvedSymbolException e) { + if (!acceptIncompleteList) { + throw e; + } + } + } + } else { + SignatureAttribute.ClassSignature classSignature = SignatureAttribute.toClassSignature(ctClass.getGenericSignature()); + for (SignatureAttribute.ClassType interfaceType : classSignature.getInterfaces()) { + try { + interfaces.add(JavassistUtils.signatureTypeToType(interfaceType, typeSolver, typeDeclaration).asReferenceType()); + } catch (UnsolvedSymbolException e) { + if (!acceptIncompleteList) { + throw e; + } + } + } + } + } catch (BadBytecode e) { + throw new RuntimeException(e); + } + + return interfaces; + } + + public List getAncestors(boolean acceptIncompleteList) { + List ancestors = new ArrayList<>(); + + try { + getSuperClass().ifPresent(superClass -> ancestors.add(superClass)); + } catch (UnsolvedSymbolException e) { + if (!acceptIncompleteList) { + throw e; + } + } + ancestors.addAll(getInterfaces(acceptIncompleteList)); + return ancestors; + } + + public Set getDeclaredMethods() { + return Arrays.stream(ctClass.getDeclaredMethods()) + .filter(m -> ((m.getMethodInfo().getAccessFlags() & AccessFlag.BRIDGE) == 0) + && ((m.getMethodInfo().getAccessFlags() & AccessFlag.SYNTHETIC) == 0)) + .map(m -> new JavassistMethodDeclaration(m, typeSolver)).collect(Collectors.toSet()); + } + + public List getConstructors() { + return Arrays.stream(ctClass.getConstructors()) + .filter(m -> (m.getMethodInfo().getAccessFlags() & AccessFlag.SYNTHETIC) == 0) + .map(m -> new JavassistConstructorDeclaration(m, typeSolver)).collect(Collectors.toList()); + } + + public List getDeclaredFields() { + List fields = new ArrayList<>(); + + // First consider fields declared on this class + for (CtField field : ctClass.getDeclaredFields()) { + fields.add(new JavassistFieldDeclaration(field, typeSolver)); + } + + // Then consider fields inherited from ancestors + for (ResolvedReferenceType ancestor : typeDeclaration.getAllAncestors()) { + ancestor.getTypeDeclaration().ifPresent(ancestorTypeDeclaration -> { + fields.addAll(ancestorTypeDeclaration.getAllFields()); + }); + } + + return fields; + } + + public List getTypeParameters() { + if (null == ctClass.getGenericSignature()) { + return Collections.emptyList(); + } else { + try { + SignatureAttribute.ClassSignature classSignature = + SignatureAttribute.toClassSignature(ctClass.getGenericSignature()); + return Arrays.stream(classSignature.getParameters()) + .map((tp) -> new JavassistTypeParameter(tp, JavassistFactory.toTypeDeclaration(ctClass, typeSolver), typeSolver)) + .collect(Collectors.toList()); + } catch (BadBytecode badBytecode) { + throw new RuntimeException(badBytecode); + } + } + } + + public Optional containerType() { + try { + return ctClass.getDeclaringClass() == null ? + Optional.empty() : + Optional.of(JavassistFactory.toTypeDeclaration(ctClass.getDeclaringClass(), typeSolver)); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + } + + public boolean isAssignableBy(ResolvedType other) { + + if (other.isNull()) { + return true; + } + + if (other instanceof LambdaArgumentTypePlaceholder) { + return typeDeclaration.isFunctionalInterface(); + } + + return other.isAssignableBy(new ReferenceTypeImpl(typeDeclaration, typeSolver)); + } + + public boolean isAssignableBy(ResolvedReferenceTypeDeclaration other) { + return isAssignableBy(new ReferenceTypeImpl(other, typeSolver)); + } + + /** + * Get the nested classes. + *
+ * {@code class Foo { class Bar {} } + * In the example above we expect the nested types for {@code Foo} to be {@code Bar}. + * + * @return The nested classes. + */ + public Set internalTypes() { + try { + return Stream.of(ctClass.getNestedClasses()) + .map(clazz -> JavassistFactory.toTypeDeclaration(clazz, typeSolver)) + .collect(Collectors.toSet()); + } catch (NotFoundException e) { + // This should never happen, since the nested type is defined in the current class + throw new UnsupportedOperationException("Please report this issue at https://github.com/javaparser/javaparser/issues/new/choose", e); + } + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistTypeParameter.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistTypeParameter.java new file mode 100644 index 0000000000000000000000000000000000000000..616d31a0c4198727121b96e75c70f94b24f425de --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistTypeParameter.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javassistmodel; + +import com.github.javaparser.resolution.declarations.ResolvedMethodLikeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParametrizable; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import javassist.bytecode.SignatureAttribute; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * @author Federico Tomassetti + */ +public class JavassistTypeParameter implements ResolvedTypeParameterDeclaration { + + private SignatureAttribute.TypeParameter wrapped; + private TypeSolver typeSolver; + private ResolvedTypeParametrizable container; + + public JavassistTypeParameter(SignatureAttribute.TypeParameter wrapped, ResolvedTypeParametrizable container, TypeSolver typeSolver) { + this.wrapped = wrapped; + this.typeSolver = typeSolver; + this.container = container; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ResolvedTypeParameterDeclaration)) return false; + + ResolvedTypeParameterDeclaration that = (ResolvedTypeParameterDeclaration) o; + + if (!getQualifiedName().equals(that.getQualifiedName())) { + return false; + } + if (declaredOnType() != that.declaredOnType()) { + return false; + } + if (declaredOnMethod() != that.declaredOnMethod()) { + return false; + } + // TODO check bounds + return true; + } + + @Override + public String toString() { + return "JavassistTypeParameter{" + + wrapped.getName() + + '}'; + } + + @Override + public String getName() { + return wrapped.getName(); + } + + @Override + public String getContainerQualifiedName() { + if (this.container instanceof ResolvedReferenceTypeDeclaration) { + return ((ResolvedReferenceTypeDeclaration) this.container).getQualifiedName(); + } else if (this.container instanceof ResolvedMethodLikeDeclaration) { + return ((ResolvedMethodLikeDeclaration) this.container).getQualifiedName(); + } + throw new UnsupportedOperationException(); + } + + @Override + public String getContainerId() { + return getContainerQualifiedName(); + } + + @Override + public ResolvedTypeParametrizable getContainer() { + return this.container; + } + + @Override + public List getBounds() { + List bounds = new ArrayList<>(); + SignatureAttribute.ObjectType classBound = wrapped.getClassBound(); + if (classBound != null) { + bounds.add(Bound.extendsBound(JavassistUtils.signatureTypeToType(classBound, typeSolver, getContainer()))); + } + for (SignatureAttribute.ObjectType ot : wrapped.getInterfaceBound()) { + bounds.add(Bound.extendsBound(JavassistUtils.signatureTypeToType(ot, typeSolver, getContainer()))); + } + return bounds; + } + + @Override + public Optional containerType() { + if (container instanceof ResolvedReferenceTypeDeclaration) { + return Optional.of((ResolvedReferenceTypeDeclaration) container); + } + return Optional.empty(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistUtils.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..553dc8696bcb2cce9b6bc679bd8d32f566629c4b --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/JavassistUtils.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.javassistmodel; + +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParametrizable; +import com.github.javaparser.resolution.types.*; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.javaparsermodel.contexts.ContextHelper; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.symbolsolver.resolution.MethodResolutionLogic; +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.Modifier; +import javassist.bytecode.*; + +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +class JavassistUtils { + + static Optional solveMethodAsUsage(String name, List argumentsTypes, TypeSolver typeSolver, + Context invokationContext, List typeParameterValues, + ResolvedReferenceTypeDeclaration scopeType, CtClass ctClass) { + List typeParameters = scopeType.getTypeParameters(); + + List methods = new ArrayList<>(); + for (CtMethod method : ctClass.getDeclaredMethods()) { + if (method.getName().equals(name) + && ((method.getMethodInfo().getAccessFlags() & AccessFlag.BRIDGE) == 0) + && ((method.getMethodInfo().getAccessFlags() & AccessFlag.SYNTHETIC) == 0)) { + MethodUsage methodUsage = new MethodUsage(new JavassistMethodDeclaration(method, typeSolver)); + for (int i = 0; i < typeParameters.size() && i < typeParameterValues.size(); i++) { + ResolvedTypeParameterDeclaration tpToReplace = typeParameters.get(i); + ResolvedType newValue = typeParameterValues.get(i); + methodUsage = methodUsage.replaceTypeParameter(tpToReplace, newValue); + } + methods.add(methodUsage); + + // no need to search for overloaded/inherited methods if the method has no parameters + if (argumentsTypes.isEmpty() && methodUsage.getNoParams() == 0) { + return Optional.of(methodUsage); + } + } + } + + for (ResolvedReferenceType ancestor : scopeType.getAncestors()) { + ancestor.getTypeDeclaration() + .flatMap(superClassTypeDeclaration -> ancestor.getTypeDeclaration()) + .flatMap(interfaceTypeDeclaration -> ContextHelper.solveMethodAsUsage(interfaceTypeDeclaration, name, argumentsTypes, invokationContext, typeParameterValues)) + .ifPresent(methods::add); + } + + return MethodResolutionLogic.findMostApplicableUsage(methods, name, argumentsTypes, typeSolver); + } + + static SymbolReference solveMethod(String name, List argumentsTypes, boolean staticOnly, + TypeSolver typeSolver, ResolvedReferenceTypeDeclaration scopeType, CtClass ctClass) { + List candidates = new ArrayList<>(); + Predicate staticOnlyCheck = m -> !staticOnly || java.lang.reflect.Modifier.isStatic(m.getModifiers()); + for (CtMethod method : ctClass.getDeclaredMethods()) { + boolean isSynthetic = method.getMethodInfo().getAttribute(SyntheticAttribute.tag) != null; + boolean isNotBridge = (method.getMethodInfo().getAccessFlags() & AccessFlag.BRIDGE) == 0; + if (method.getName().equals(name) && !isSynthetic && isNotBridge && staticOnlyCheck.test(method)) { + ResolvedMethodDeclaration candidate = new JavassistMethodDeclaration(method, typeSolver); + candidates.add(candidate); + + // no need to search for overloaded/inherited methods if the method has no parameters + if (argumentsTypes.isEmpty() && candidate.getNumberOfParams() == 0) { + return SymbolReference.solved(candidate); + } + } + } + + // add the method declaration of the interfaces to the candidates, if present + for (ResolvedReferenceType ancestorRefType : scopeType.getAncestors()) { + Optional ancestorTypeDeclOpt = ancestorRefType.getTypeDeclaration(); + if (ancestorTypeDeclOpt.isPresent()) { + SymbolReference ancestorMethodRef = MethodResolutionLogic.solveMethodInType( + ancestorTypeDeclOpt.get(), + name, + argumentsTypes, + staticOnly + ); + if (ancestorMethodRef.isSolved()) { + candidates.add(ancestorMethodRef.getCorrespondingDeclaration()); + } + } else { + // Consider IllegalStateException or similar? + } + } + + return MethodResolutionLogic.findMostApplicable(candidates, name, argumentsTypes, typeSolver); + } + + static ResolvedType signatureTypeToType(SignatureAttribute.Type signatureType, TypeSolver typeSolver, ResolvedTypeParametrizable typeParametrizable) { + if (signatureType instanceof SignatureAttribute.ClassType) { + SignatureAttribute.ClassType classType = (SignatureAttribute.ClassType) signatureType; + List typeArguments = classType.getTypeArguments() == null ? Collections.emptyList() : Arrays.stream(classType.getTypeArguments()).map(ta -> typeArgumentToType(ta, typeSolver, typeParametrizable)).collect(Collectors.toList()); + ResolvedReferenceTypeDeclaration typeDeclaration = typeSolver.solveType( + removeTypeArguments(internalNameToCanonicalName(getTypeName(classType)))); + return new ReferenceTypeImpl(typeDeclaration, typeArguments, typeSolver); + } else if (signatureType instanceof SignatureAttribute.TypeVariable) { + SignatureAttribute.TypeVariable typeVariableSignature = (SignatureAttribute.TypeVariable) signatureType; + Optional typeParameterDeclarationOpt = typeParametrizable.findTypeParameter(typeVariableSignature.getName()); + if (!typeParameterDeclarationOpt.isPresent()) { + throw new UnsolvedSymbolException("Unable to solve TypeVariable " + typeVariableSignature); + } + ResolvedTypeParameterDeclaration typeParameterDeclaration = typeParameterDeclarationOpt.get(); + return new ResolvedTypeVariable(typeParameterDeclaration); + } else if (signatureType instanceof SignatureAttribute.ArrayType) { + SignatureAttribute.ArrayType arrayType = (SignatureAttribute.ArrayType) signatureType; + return new ResolvedArrayType(signatureTypeToType(arrayType.getComponentType(), typeSolver, typeParametrizable)); + } else if (signatureType instanceof SignatureAttribute.BaseType) { + SignatureAttribute.BaseType baseType = (SignatureAttribute.BaseType) signatureType; + if (baseType.toString().equals("void")) { + return ResolvedVoidType.INSTANCE; + } else { + return ResolvedPrimitiveType.byName(baseType.toString()); + } + } else { + throw new RuntimeException(signatureType.getClass().getCanonicalName()); + } + } + + private static String getTypeName(SignatureAttribute.ClassType classType) { + SignatureAttribute.ClassType declaringClass = classType.getDeclaringClass(); + return declaringClass == null ? classType.getName() : getTypeName(declaringClass) + "." + classType.getName(); + } + + private static String removeTypeArguments(String typeName) { + if (typeName.contains("<")) { + return typeName.substring(0, typeName.indexOf('<')); + } else { + return typeName; + } + } + + static String internalNameToCanonicalName(String typeName) { + return typeName.replaceAll("\\$", "."); + } + + private static ResolvedType objectTypeArgumentToType(SignatureAttribute.ObjectType typeArgument, TypeSolver typeSolver, ResolvedTypeParametrizable typeParametrizable) { + if (typeArgument instanceof SignatureAttribute.ClassType) { + return signatureTypeToType(typeArgument, typeSolver, typeParametrizable); + } else if (typeArgument instanceof SignatureAttribute.ArrayType) { + return new ResolvedArrayType(signatureTypeToType(((SignatureAttribute.ArrayType) typeArgument).getComponentType(), typeSolver, typeParametrizable)); + } else { + String typeName = typeArgument.jvmTypeName(); + return getGenericParameterByName(typeName, typeParametrizable, typeSolver); + } + } + + private static ResolvedType getGenericParameterByName(String typeName, ResolvedTypeParametrizable typeParametrizable, TypeSolver typeSolver) { + Optional type = typeParametrizable.findTypeParameter(typeName).map(ResolvedTypeVariable::new); + return type.orElseGet(() -> new ReferenceTypeImpl( + typeSolver.solveType(removeTypeArguments(internalNameToCanonicalName(typeName))), + typeSolver)); + } + + private static ResolvedType typeArgumentToType(SignatureAttribute.TypeArgument typeArgument, TypeSolver typeSolver, ResolvedTypeParametrizable typeParametrizable) { + if (typeArgument.isWildcard()) { + if (typeArgument.getType() == null) { + return ResolvedWildcard.UNBOUNDED; + } else if (typeArgument.getKind() == '+') { + return ResolvedWildcard.extendsBound(objectTypeArgumentToType(typeArgument.getType(), typeSolver, typeParametrizable)); + } else if (typeArgument.getKind() == '-') { + return ResolvedWildcard.superBound(objectTypeArgumentToType(typeArgument.getType(), typeSolver, typeParametrizable)); + } else { + throw new UnsupportedOperationException(); + } + } else { + return objectTypeArgumentToType(typeArgument.getType(), typeSolver, typeParametrizable); + } + } + + /** + * Returns the {@code paramNumber}th parameter of a method or constructor, if it is available. + *

+ * The name is not available, if + *

    + *
  • the method is abstract, i.e. explicitly declared as abstract or it is a non-default interface method
  • + *
  • methods and constructors from jar files, which have been compiled without debug symbols
  • + *
+ *

+ * The parameters are counted from 0, skipping the implicit {@code this} parameter of non-static methods. + * + * @param method the method to look into + * @param paramNumber the number of the parameter to look for + * @return the found parameter name or empty, if the name is not available + */ + static Optional extractParameterName(CtBehavior method, int paramNumber) { + MethodInfo methodInfo = method.getMethodInfo(); + CodeAttribute codeAttribute = methodInfo.getCodeAttribute(); + if (codeAttribute != null) { + LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute + .tag); + if (attr != null) { + int pos = Modifier.isStatic(method.getModifiers()) ? 0 : 1; + return Optional.ofNullable(attr.variableName(paramNumber + pos)); + } + } + return Optional.empty(); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/package-info.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..d2faa8937d7d38b99e8d2d85519c496e4f131200 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javassistmodel/package-info.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +/** + * Implementation of model based on Javassist. + */ +package com.github.javaparser.symbolsolver.javassistmodel; diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/AbstractClassDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/AbstractClassDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..e13a88a3d1730e1f4a769a5591cd48c243cf990e --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/AbstractClassDeclaration.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.logic; + +import com.github.javaparser.resolution.declarations.ResolvedClassDeclaration; +import com.github.javaparser.resolution.types.ResolvedReferenceType; + +import java.util.ArrayList; +import java.util.List; + +/** + * A common ancestor for all ClassDeclarations. + * + * @author Federico Tomassetti + */ +public abstract class AbstractClassDeclaration extends AbstractTypeDeclaration + implements ResolvedClassDeclaration, MethodResolutionCapability { + + /// + /// Public + /// + + @Override + public boolean hasName() { + return getQualifiedName() != null; + } + + @Override + public final List getAllSuperClasses() { + List superclasses = new ArrayList<>(); + + getSuperClass().ifPresent(superClass -> { + superclasses.add(superClass); + superclasses.addAll(superClass.getAllClassesAncestors()); + }); + + if (superclasses.removeIf(ResolvedReferenceType::isJavaLangObject)) { + superclasses.add(object()); + } + return superclasses; + } + + @Override + public final List getAllInterfaces() { + List interfaces = new ArrayList<>(); + for (ResolvedReferenceType interfaceDeclaration : getInterfaces()) { + interfaces.add(interfaceDeclaration); + interfaces.addAll(interfaceDeclaration.getAllInterfacesAncestors()); + } + getSuperClass().ifPresent(superClass -> { + interfaces.addAll(superClass.getAllInterfacesAncestors()); + }); + return interfaces; + } + + @Override + public final ResolvedClassDeclaration asClass() { + return this; + } + + /// + /// Protected + /// + + /** + * An implementation of the Object class. + */ + protected abstract ResolvedReferenceType object(); + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/AbstractTypeDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/AbstractTypeDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..255db3c44bf7c794dd4b9784fdb70324712b4a63 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/AbstractTypeDeclaration.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.logic; + +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.utils.Pair; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Common ancestor for most types. + * + * @author Federico Tomassetti + */ +public abstract class AbstractTypeDeclaration implements ResolvedReferenceTypeDeclaration { + + @Override + public final Set getAllMethods() { + Set methods = new HashSet<>(); + + Set methodsSignatures = new HashSet<>(); + + for (ResolvedMethodDeclaration methodDeclaration : getDeclaredMethods()) { + MethodUsage methodUsage = new MethodUsage(methodDeclaration); + methods.add(methodUsage); + methodsSignatures.add(methodUsage.getSignature()); + } + + for (ResolvedReferenceType ancestor : getAllAncestors()) { + List> typeParametersMap = ancestor.getTypeParametersMap(); + for (MethodUsage mu : ancestor.getDeclaredMethods()) { + // replace type parameters to be able to filter away overridden generified methods + MethodUsage methodUsage = mu; + for (Pair p : typeParametersMap) { + methodUsage = methodUsage.replaceTypeParameter(p.a, p.b); + } + String signature = methodUsage.getSignature(); + if (!methodsSignatures.contains(signature)) { + methodsSignatures.add(signature); + methods.add(mu); + } + } + } + + return methods; + } + + @Override + public final boolean isFunctionalInterface() { + return FunctionalInterfaceLogic.getFunctionalMethod(this).isPresent(); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/ConfilictingGenericTypesException.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/ConfilictingGenericTypesException.java new file mode 100644 index 0000000000000000000000000000000000000000..e159585b51d0d7ecb0f7e4c08e2b501a810a5051 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/ConfilictingGenericTypesException.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.logic; + +import com.github.javaparser.resolution.types.ResolvedType; + +/** + * @author Federico Tomassetti + */ +public class ConfilictingGenericTypesException extends RuntimeException { + + public ConfilictingGenericTypesException(ResolvedType formalType, ResolvedType actualType) { + super(String.format("No matching between %s (formal) and %s (actual)", formalType, actualType)); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/FunctionalInterfaceLogic.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/FunctionalInterfaceLogic.java new file mode 100644 index 0000000000000000000000000000000000000000..2431a3d5736ce2dcfe3e56d4f259e5964f5ed141 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/FunctionalInterfaceLogic.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.logic; + +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +public final class FunctionalInterfaceLogic { + + private static String JAVA_LANG_FUNCTIONAL_INTERFACE = FunctionalInterface.class.getCanonicalName(); + + private FunctionalInterfaceLogic() { + // prevent instantiation + } + + /** + * Get the functional method defined by the type, if any. + */ + public static Optional getFunctionalMethod(ResolvedType type) { + Optional optionalTypeDeclaration = type.asReferenceType().getTypeDeclaration(); + if(!optionalTypeDeclaration.isPresent()) { + return Optional.empty(); + } + + ResolvedReferenceTypeDeclaration typeDeclaration = optionalTypeDeclaration.get(); + if (type.isReferenceType() && typeDeclaration.isInterface()) { + return getFunctionalMethod(typeDeclaration); + } else { + return Optional.empty(); + } + } + + /** + * Get the functional method defined by the type, if any. + */ + public static Optional getFunctionalMethod(ResolvedReferenceTypeDeclaration typeDeclaration) { + //We need to find all abstract methods + Set methods = typeDeclaration.getAllMethods().stream() + .filter(m -> m.getDeclaration().isAbstract()) + // Remove methods inherited by Object: + // Consider the case of Comparator which define equals. It would be considered a functional method. + .filter(m -> !declaredOnObject(m)) + .collect(Collectors.toSet()); + + if (methods.size() == 1) { + return Optional.of(methods.iterator().next()); + } else { + return Optional.empty(); + } + } + + public static boolean isFunctionalInterfaceType(ResolvedType type) { + if (type.isReferenceType()) { + Optional optionalTypeDeclaration = type.asReferenceType().getTypeDeclaration(); + if (optionalTypeDeclaration.isPresent() && optionalTypeDeclaration.get().hasAnnotation(JAVA_LANG_FUNCTIONAL_INTERFACE)) { + return true; + } + } + return getFunctionalMethod(type).isPresent(); + } + + private static String getSignature(Method m) { + return String.format("%s(%s)", m.getName(), String.join(", ", Arrays.stream(m.getParameters()).map(p -> toSignature(p)).collect(Collectors.toList()))); + } + + private static String toSignature(Parameter p) { + return p.getType().getCanonicalName(); + } + + private static List OBJECT_METHODS_SIGNATURES = Arrays.stream(Object.class.getDeclaredMethods()) + .map(method -> getSignature(method)) + .collect(Collectors.toList()); + + private static boolean declaredOnObject(MethodUsage m) { + return OBJECT_METHODS_SIGNATURES.contains(m.getDeclaration().getSignature()); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/InferenceContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/InferenceContext.java new file mode 100644 index 0000000000000000000000000000000000000000..931730a33b7c86464ff9e3352ceafb2a356ab744 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/InferenceContext.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.logic; + +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +public class InferenceContext { + + private int nextInferenceVariableId = 0; + private ObjectProvider objectProvider; + private List inferenceVariableTypes = new ArrayList<>(); + private Map inferenceVariableTypeMap = new HashMap<>(); + + public InferenceContext(ObjectProvider objectProvider) { + this.objectProvider = objectProvider; + } + + private InferenceVariableType inferenceVariableTypeForTp(ResolvedTypeParameterDeclaration tp) { + if (!inferenceVariableTypeMap.containsKey(tp.getName())) { + InferenceVariableType inferenceVariableType = new InferenceVariableType(nextInferenceVariableId++, objectProvider); + inferenceVariableTypes.add(inferenceVariableType); + inferenceVariableType.setCorrespondingTp(tp); + inferenceVariableTypeMap.put(tp.getName(), inferenceVariableType); + } + return inferenceVariableTypeMap.get(tp.getName()); + } + + /** + * @return the actual with the inference variable inserted + */ + public ResolvedType addPair(ResolvedType target, ResolvedType actual) { + target = placeInferenceVariables(target); + actual = placeInferenceVariables(actual); + registerCorrespondance(target, actual); + return target; + } + + public ResolvedType addSingle(ResolvedType actual) { + return placeInferenceVariables(actual); + } + + private void registerCorrespondance(ResolvedType formalType, ResolvedType actualType) { + if (formalType.isReferenceType() && actualType.isReferenceType()) { + ResolvedReferenceType formalTypeAsReference = formalType.asReferenceType(); + ResolvedReferenceType actualTypeAsReference = actualType.asReferenceType(); + + if (!formalTypeAsReference.getQualifiedName().equals(actualTypeAsReference.getQualifiedName())) { + List ancestors = actualTypeAsReference.getAllAncestors(); + final String formalParamTypeQName = formalTypeAsReference.getQualifiedName(); + List correspondingFormalType = ancestors.stream().filter((a) -> a.getQualifiedName().equals(formalParamTypeQName)).collect(Collectors.toList()); + if (correspondingFormalType.isEmpty()) { + ancestors = formalTypeAsReference.getAllAncestors(); + final String actualParamTypeQname = actualTypeAsReference.getQualifiedName(); + List correspondingActualType = ancestors.stream().filter(a -> a.getQualifiedName().equals(actualParamTypeQname)).collect(Collectors.toList()); + if (correspondingActualType.isEmpty()) { + throw new ConfilictingGenericTypesException(formalType, actualType); + } + correspondingFormalType = correspondingActualType; + + } + actualTypeAsReference = correspondingFormalType.get(0).asReferenceType(); + } + + if (formalTypeAsReference.getQualifiedName().equals(actualTypeAsReference.getQualifiedName())) { + if (!formalTypeAsReference.typeParametersValues().isEmpty()) { + if (actualTypeAsReference.isRawType()) { + // nothing to do + } else { + int i = 0; + for (ResolvedType formalTypeParameter : formalTypeAsReference.typeParametersValues()) { + registerCorrespondance(formalTypeParameter, actualTypeAsReference.typeParametersValues().get(i)); + i++; + } + } + } + } + } else if (formalType instanceof InferenceVariableType && !actualType.isPrimitive()) { + ((InferenceVariableType) formalType).registerEquivalentType(actualType); + if (actualType instanceof InferenceVariableType) { + ((InferenceVariableType) actualType).registerEquivalentType(formalType); + } + } else if (actualType.isNull()) { + // nothing to do + } else if (actualType.equals(formalType)) { + // nothing to do + } else if (actualType.isArray() && formalType.isArray()) { + registerCorrespondance(formalType.asArrayType().getComponentType(), actualType.asArrayType().getComponentType()); + } else if (formalType.isWildcard()) { + // nothing to do + if ((actualType instanceof InferenceVariableType) && formalType.asWildcard().isBounded()) { + ((InferenceVariableType) actualType).registerEquivalentType(formalType.asWildcard().getBoundedType()); + if (formalType.asWildcard().getBoundedType() instanceof InferenceVariableType) { + ((InferenceVariableType) formalType.asWildcard().getBoundedType()).registerEquivalentType(actualType); + } + } + if (actualType.isWildcard()) { + ResolvedWildcard formalWildcard = formalType.asWildcard(); + ResolvedWildcard actualWildcard = actualType.asWildcard(); + if (formalWildcard.isBounded() && formalWildcard.getBoundedType() instanceof InferenceVariableType) { + if (formalWildcard.isSuper() && actualWildcard.isSuper()) { + ((InferenceVariableType) formalType.asWildcard().getBoundedType()).registerEquivalentType(actualWildcard.getBoundedType()); + } else if (formalWildcard.isExtends() && actualWildcard.isExtends()) { + ((InferenceVariableType) formalType.asWildcard().getBoundedType()).registerEquivalentType(actualWildcard.getBoundedType()); + } + } + } + + if (actualType.isReferenceType()) { + if (formalType.asWildcard().isBounded()) { + registerCorrespondance(formalType.asWildcard().getBoundedType(), actualType); + } + } + } else if (actualType instanceof InferenceVariableType) { + if (formalType instanceof ResolvedReferenceType) { + ((InferenceVariableType) actualType).registerEquivalentType(formalType); + } else if (formalType instanceof InferenceVariableType) { + ((InferenceVariableType) actualType).registerEquivalentType(formalType); + } + } else if (actualType.isConstraint()) { + ResolvedLambdaConstraintType constraintType = actualType.asConstraintType(); + if (constraintType.getBound() instanceof InferenceVariableType) { + ((InferenceVariableType) constraintType.getBound()).registerEquivalentType(formalType); + } + } else if (actualType.isPrimitive()) { + if (formalType.isPrimitive()) { + // nothing to do + } else { + registerCorrespondance(formalType, objectProvider.byName(actualType.asPrimitive().getBoxTypeQName())); + } + } else if (actualType.isReferenceType()) { + if (formalType.isPrimitive()) { + if (formalType.asPrimitive().getBoxTypeQName().equals(actualType.describe())) { + registerCorrespondance(objectProvider.byName(formalType.asPrimitive().getBoxTypeQName()), actualType); + } else { + // nothing to do + } + } else { + // nothing to do + } + } else { + throw new UnsupportedOperationException(formalType.describe() + " " + actualType.describe()); + } + } + + private ResolvedType placeInferenceVariables(ResolvedType type) { + if (type.isWildcard()) { + if (type.asWildcard().isExtends()) { + return ResolvedWildcard.extendsBound(placeInferenceVariables(type.asWildcard().getBoundedType())); + } else if (type.asWildcard().isSuper()) { + return ResolvedWildcard.superBound(placeInferenceVariables(type.asWildcard().getBoundedType())); + } else { + return type; + } + } else if (type.isTypeVariable()) { + return inferenceVariableTypeForTp(type.asTypeParameter()); + } else if (type.isReferenceType()) { + return type.asReferenceType().transformTypeParameters(tp -> placeInferenceVariables(tp)); + } else if (type.isArray()) { + return new ResolvedArrayType(placeInferenceVariables(type.asArrayType().getComponentType())); + } else if (type.isNull() || type.isPrimitive() || type.isVoid()) { + return type; + } else if (type.isConstraint()) { + return ResolvedLambdaConstraintType.bound(placeInferenceVariables(type.asConstraintType().getBound())); + } else if (type instanceof InferenceVariableType) { + return type; + } else { + throw new UnsupportedOperationException(type.describe()); + } + } + + public ResolvedType resolve(ResolvedType type) { + if (type instanceof InferenceVariableType) { + InferenceVariableType inferenceVariableType = (InferenceVariableType) type; + return inferenceVariableType.equivalentType(); + } else if (type.isReferenceType()) { + return type.asReferenceType().transformTypeParameters(tp -> resolve(tp)); + } else if (type.isNull() || type.isPrimitive() || type.isVoid()) { + return type; + } else if (type.isArray()) { + return new ResolvedArrayType(resolve(type.asArrayType().getComponentType())); + } else if (type.isWildcard()) { + if (type.asWildcard().isExtends()) { + return ResolvedWildcard.extendsBound(resolve(type.asWildcard().getBoundedType())); + } else if (type.asWildcard().isSuper()) { + return ResolvedWildcard.superBound(resolve(type.asWildcard().getBoundedType())); + } else { + return type; + } + } else { + throw new UnsupportedOperationException(type.describe()); + } + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/InferenceVariableType.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/InferenceVariableType.java new file mode 100644 index 0000000000000000000000000000000000000000..e85fa228fb230574032253940c5f1be56c1f693d --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/InferenceVariableType.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.logic; + +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.resolution.types.ResolvedTypeVariable; +import com.github.javaparser.resolution.types.ResolvedWildcard; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * An element using during type inference. + * + * @author Federico Tomassetti + */ +public class InferenceVariableType implements ResolvedType { + @Override + public String toString() { + return "InferenceVariableType{" + + "id=" + id + + '}'; + } + + private int id; + private ResolvedTypeParameterDeclaration correspondingTp; + + public void setCorrespondingTp(ResolvedTypeParameterDeclaration correspondingTp) { + this.correspondingTp = correspondingTp; + } + + private Set equivalentTypes = new HashSet<>(); + private ObjectProvider objectProvider; + + public void registerEquivalentType(ResolvedType type) { + this.equivalentTypes.add(type); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof InferenceVariableType)) return false; + + InferenceVariableType that = (InferenceVariableType) o; + + return id == that.id; + + } + + @Override + public int hashCode() { + return id; + } + + private Set superTypes = new HashSet<>(); + + public InferenceVariableType(int id, ObjectProvider objectProvider) { + this.id = id; + this.objectProvider = objectProvider; + } + + public static InferenceVariableType fromWildcard(ResolvedWildcard wildcard, int id, ObjectProvider objectProvider) { + InferenceVariableType inferenceVariableType = new InferenceVariableType(id, objectProvider); + if (wildcard.isExtends()) { + inferenceVariableType.superTypes.add(wildcard.getBoundedType()); + } + if (wildcard.isSuper()) { + // I am not sure about this one... + inferenceVariableType.superTypes.add(wildcard.getBoundedType()); + } + return inferenceVariableType; + } + + @Override + public String describe() { + return "InferenceVariable_" + id; + } + + @Override + public boolean isAssignableBy(ResolvedType other) { + throw new UnsupportedOperationException(); + } + + private Set concreteEquivalentTypesAlsoIndirectly(Set considered, InferenceVariableType inferenceVariableType) { + considered.add(inferenceVariableType); + Set result = new HashSet<>(); + result.addAll(inferenceVariableType.equivalentTypes.stream().filter(t -> !t.isTypeVariable() && !(t instanceof InferenceVariableType)).collect(Collectors.toSet())); + inferenceVariableType.equivalentTypes.stream().filter(t -> t instanceof InferenceVariableType).forEach(t -> { + InferenceVariableType ivt = (InferenceVariableType)t; + if (!considered.contains(ivt)) { + result.addAll(concreteEquivalentTypesAlsoIndirectly(considered, ivt)); + } + }); + return result; + } + + public ResolvedType equivalentType() { + Set concreteEquivalent = concreteEquivalentTypesAlsoIndirectly(new HashSet<>(), this); + if (concreteEquivalent.isEmpty()) { + if (correspondingTp == null) { + return objectProvider.object(); + } else { + return new ResolvedTypeVariable(correspondingTp); + } + } + if (concreteEquivalent.size() == 1) { + return concreteEquivalent.iterator().next(); + } + Set notTypeVariables = equivalentTypes.stream() + .filter(t -> !t.isTypeVariable() && !hasInferenceVariables(t)) + .collect(Collectors.toSet()); + if (notTypeVariables.size() == 1) { + return notTypeVariables.iterator().next(); + } else if (notTypeVariables.size() == 0 && !superTypes.isEmpty()) { + if (superTypes.size() == 1) { + return superTypes.iterator().next(); + } else { + throw new IllegalStateException("Super types are: " + superTypes); + } + } else { + throw new IllegalStateException("Equivalent types are: " + equivalentTypes); + } + } + + private boolean hasInferenceVariables(ResolvedType type){ + if (type instanceof InferenceVariableType){ + return true; + } + + if (type.isReferenceType()){ + ResolvedReferenceType refType = type.asReferenceType(); + for (ResolvedType t : refType.typeParametersValues()){ + if (hasInferenceVariables(t)){ + return true; + } + } + return false; + } + + if (type.isWildcard()){ + ResolvedWildcard wildcardType = type.asWildcard(); + return hasInferenceVariables(wildcardType.getBoundedType()); + } + + return false; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/MethodResolutionCapability.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/MethodResolutionCapability.java new file mode 100644 index 0000000000000000000000000000000000000000..20783e199408cbcbcdc3182ca6eeb47653987cb2 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/MethodResolutionCapability.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.logic; + +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; + +import java.util.List; + +public interface MethodResolutionCapability { + SymbolReference solveMethod(String name, List argumentsTypes, + boolean staticOnly); +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/ObjectProvider.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/ObjectProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..ce67f756728225b4b213716924a6903708ba8a90 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/logic/ObjectProvider.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.logic; + +import com.github.javaparser.resolution.types.ResolvedReferenceType; + +/** + * @author Federico Tomassetti + */ +public interface ObjectProvider { + ResolvedReferenceType object(); + ResolvedReferenceType byName(String qname); +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/model/resolution/SymbolReference.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/model/resolution/SymbolReference.java new file mode 100644 index 0000000000000000000000000000000000000000..c5f70ff1ccd35163b2fd17e256345b338b2f06b0 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/model/resolution/SymbolReference.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.model.resolution; + +import com.github.javaparser.resolution.declarations.ResolvedDeclaration; + +import java.util.Optional; + +/** + * A reference to a symbol. It can solved or not solved. If solved the corresponding + * declaration will be provided. + * + * @author Federico Tomassetti + */ +public class SymbolReference { + + private Optional correspondingDeclaration; + + private SymbolReference(Optional correspondingDeclaration) { + this.correspondingDeclaration = correspondingDeclaration; + } + + /** + * Create a solve reference to the given symbol. + */ + public static SymbolReference solved(S2 symbolDeclaration) { + return new SymbolReference(Optional.of(symbolDeclaration)); + } + + /** + * Create an unsolved reference specifying the type of the value expected. + */ + public static SymbolReference unsolved(Class clazz) { + return new SymbolReference<>(Optional.empty()); + } + + @Override + public String toString() { + return "SymbolReference{" + correspondingDeclaration + "}"; + } + + /** + * The corresponding declaration. If not solve this throws UnsupportedOperationException. + * // TODO: Convert this to returning Optional. + */ + public S getCorrespondingDeclaration() { + if (!isSolved()) { + throw new UnsupportedOperationException("CorrespondingDeclaration not available for unsolved symbol."); + } + return correspondingDeclaration.get(); + } + + /** + * Is the reference solved? + */ + public boolean isSolved() { + return correspondingDeclaration.isPresent(); + } + + public static SymbolReference adapt(SymbolReference ref, Class clazz) { + if (ref.isSolved()) { + return SymbolReference.solved(ref.getCorrespondingDeclaration()); + } else { + return SymbolReference.unsolved(clazz); + } + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/model/resolution/TypeSolver.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/model/resolution/TypeSolver.java new file mode 100644 index 0000000000000000000000000000000000000000..1fc42425ac1e9b0e33bebfd66f16d920c1c18a75 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/model/resolution/TypeSolver.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.model.resolution; + +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; + +/** + * An element able to find TypeDeclaration from their name. + * TypeSolvers are organized in hierarchies. + * + * @author Federico Tomassetti + */ +public interface TypeSolver { + + String JAVA_LANG_OBJECT = Object.class.getCanonicalName(); + + /** + * Get the root of the hierarchy of type solver. + */ + default TypeSolver getRoot() { + if (getParent() == null) { + return this; + } else { + return getParent().getRoot(); + } + } + + /** + * Parent of the this TypeSolver. This can return null. + */ + TypeSolver getParent(); + + /** + * Set the parent of this TypeSolver. + */ + void setParent(TypeSolver parent); + + /** + * Try to solve the type with the given name. It always return a SymbolReference which can be solved + * or unsolved. + */ + SymbolReference tryToSolveType(String name); + + /** + * Solve the given type. Either the type is found and returned or an UnsolvedSymbolException is thrown. + */ + default ResolvedReferenceTypeDeclaration solveType(String name) throws UnsolvedSymbolException { + SymbolReference ref = tryToSolveType(name); + if (ref.isSolved()) { + return ref.getCorrespondingDeclaration(); + } else { + throw new UnsolvedSymbolException(name, this.toString()); + } + } + + /** + * @return A resolved reference to {@code java.lang.Object} + */ + default ResolvedReferenceTypeDeclaration getSolvedJavaLangObject() throws UnsolvedSymbolException { + return solveType(JAVA_LANG_OBJECT); + } + + default boolean hasType(String name) { + return tryToSolveType(name).isSolved(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/model/resolution/Value.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/model/resolution/Value.java new file mode 100644 index 0000000000000000000000000000000000000000..be1ab2ddb9e48f7b090017f958e35b5015db094f --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/model/resolution/Value.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.model.resolution; + +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; + +/** + * Any type of value. + * + * @author Federico Tomassetti + */ +public class Value { + private ResolvedType type; + private String name; + + public Value(ResolvedType type, String name) { + this.type = type; + this.name = name; + } + + /** + * Create a Value from a ValueDeclaration. + */ + public static Value from(ResolvedValueDeclaration decl) { + ResolvedType type = decl.getType(); + return new Value(type, decl.getName()); + } + + @Override + public String toString() { + return "Value{" + + "type=" + type + + ", name='" + name + '\'' + + '}'; + } + + public String getName() { + return name; + } + + public ResolvedType getType() { + return type; + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/model/typesystem/LazyType.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/model/typesystem/LazyType.java new file mode 100644 index 0000000000000000000000000000000000000000..d32182ec912fd59e65d285e79826d65da2f03313 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/model/typesystem/LazyType.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.model.typesystem; + +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.*; + +import java.util.Map; +import java.util.function.Function; + +public class LazyType implements ResolvedType { + private ResolvedType concrete; + private Function provider; + + public LazyType(Function provider) { + this.provider = provider; + } + + private ResolvedType getType() { + if (concrete == null) { + concrete = provider.apply(null); + } + return concrete; + } + + @Override + public boolean isArray() { + return getType().isArray(); + } + + @Override + public int arrayLevel() { + return getType().arrayLevel(); + } + + @Override + public boolean isPrimitive() { + return getType().isPrimitive(); + } + + @Override + public boolean isNull() { + return getType().isNull(); + } + + @Override + public boolean isReference() { + return getType().isReference(); + } + + @Override + public boolean isReferenceType() { + return getType().isReferenceType(); + } + + @Override + public boolean isVoid() { + return getType().isVoid(); + } + + @Override + public boolean isTypeVariable() { + return getType().isTypeVariable(); + } + + @Override + public boolean isWildcard() { + return getType().isWildcard(); + } + + @Override + public ResolvedArrayType asArrayType() { + return getType().asArrayType(); + } + + @Override + public ResolvedReferenceType asReferenceType() { + return getType().asReferenceType(); + } + + @Override + public ResolvedTypeParameterDeclaration asTypeParameter() { + return getType().asTypeParameter(); + } + + @Override + public ResolvedTypeVariable asTypeVariable() { + return getType().asTypeVariable(); + } + + @Override + public ResolvedPrimitiveType asPrimitive() { + return getType().asPrimitive(); + } + + @Override + public ResolvedWildcard asWildcard() { + return getType().asWildcard(); + } + + @Override + public String describe() { + return getType().describe(); + } + + @Override + public ResolvedType replaceTypeVariables(ResolvedTypeParameterDeclaration tp, ResolvedType replaced, + Map inferredTypes) { + return getType().replaceTypeVariables(tp, replaced, inferredTypes); + } + + @Override + public ResolvedType replaceTypeVariables(ResolvedTypeParameterDeclaration tp, ResolvedType replaced) { + return getType().replaceTypeVariables(tp, replaced); + } + + @Override + public boolean isAssignableBy(ResolvedType other) { + return getType().isAssignableBy(other); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/model/typesystem/NullType.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/model/typesystem/NullType.java new file mode 100644 index 0000000000000000000000000000000000000000..dac282ef7e3267e8df1bbbf137ede956e54a9a54 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/model/typesystem/NullType.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.model.typesystem; + +import com.github.javaparser.resolution.types.ResolvedType; + +/** + * This is a virtual type used to represent null values. + * + * @author Federico Tomassetti + */ +public class NullType implements ResolvedType { + + public static final NullType INSTANCE = new NullType(); + + private NullType() { + // prevent instantiation + } + + @Override + public boolean isArray() { + return false; + } + + @Override + public boolean isPrimitive() { + return false; + } + + public boolean isNull() { + return true; + } + + @Override + public boolean isReferenceType() { + return false; + } + + @Override + public String describe() { + return "null"; + } + + @Override + public boolean isTypeVariable() { + return false; + } + + @Override + public boolean isAssignableBy(ResolvedType other) { + throw new UnsupportedOperationException("It does not make sense to assign a value to null, it can only be assigned"); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/model/typesystem/ReferenceTypeImpl.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/model/typesystem/ReferenceTypeImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..bbb6bc5eb51194a8fa3a8671a0ba627cf6cd019a --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/model/typesystem/ReferenceTypeImpl.java @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.model.typesystem; + +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.declarations.ResolvedFieldDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.resolution.types.ResolvedTypeTransformer; +import com.github.javaparser.resolution.types.ResolvedTypeVariable; +import com.github.javaparser.resolution.types.parametrization.ResolvedTypeParametersMap; +import com.github.javaparser.symbolsolver.javaparsermodel.LambdaArgumentTypePlaceholder; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserTypeVariableDeclaration; +import com.github.javaparser.symbolsolver.logic.FunctionalInterfaceLogic; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +// TODO Remove references to typeSolver: it is needed to instantiate other instances of ReferenceTypeUsage +// and to get the Object type declaration +public class ReferenceTypeImpl extends ResolvedReferenceType { + + private TypeSolver typeSolver; + + public static ResolvedReferenceType undeterminedParameters(ResolvedReferenceTypeDeclaration typeDeclaration, TypeSolver typeSolver) { + return new ReferenceTypeImpl(typeDeclaration, typeDeclaration.getTypeParameters().stream().map( + ResolvedTypeVariable::new + ).collect(Collectors.toList()), typeSolver); + } + + @Override + protected ResolvedReferenceType create(ResolvedReferenceTypeDeclaration typeDeclaration, List typeParametersCorrected) { + return new ReferenceTypeImpl(typeDeclaration, typeParametersCorrected, typeSolver); + } + + @Override + protected ResolvedReferenceType create(ResolvedReferenceTypeDeclaration typeDeclaration) { + return new ReferenceTypeImpl(typeDeclaration, typeSolver); + } + + public ReferenceTypeImpl(ResolvedReferenceTypeDeclaration typeDeclaration, TypeSolver typeSolver) { + super(typeDeclaration); + this.typeSolver = typeSolver; + } + + public ReferenceTypeImpl(ResolvedReferenceTypeDeclaration typeDeclaration, List typeArguments, TypeSolver typeSolver) { + super(typeDeclaration, typeArguments); + this.typeSolver = typeSolver; + } + + @Override + public ResolvedTypeParameterDeclaration asTypeParameter() { + if (this.typeDeclaration instanceof JavaParserTypeVariableDeclaration) { + JavaParserTypeVariableDeclaration javaParserTypeVariableDeclaration = (JavaParserTypeVariableDeclaration) this.typeDeclaration; + return javaParserTypeVariableDeclaration.asTypeParameter(); + } + throw new UnsupportedOperationException(this.typeDeclaration.getClass().getCanonicalName()); + } + + /** + * This method checks if ThisType t = new OtherType() would compile. + */ + @Override + public boolean isAssignableBy(ResolvedType other) { + if (other instanceof NullType) { + return !this.isPrimitive(); + } + // everything is assignable to Object except void + if (!other.isVoid() && this.isJavaLangObject()) { + return true; + } + // consider boxing + if (other.isPrimitive()) { + if (this.isJavaLangObject()) { + return true; + } else { + // Check if 'other' can be boxed to match this type + if (isCorrespondingBoxingType(other.describe())) return true; + + // Resolve the boxed type and check if it can be assigned via widening reference conversion + SymbolReference type = typeSolver.tryToSolveType(other.asPrimitive().getBoxTypeQName()); + return type.getCorrespondingDeclaration().canBeAssignedTo(super.typeDeclaration); + } + } + if (other instanceof LambdaArgumentTypePlaceholder) { + return FunctionalInterfaceLogic.isFunctionalInterfaceType(this); + } else if (other instanceof ReferenceTypeImpl) { + ReferenceTypeImpl otherRef = (ReferenceTypeImpl) other; + if (compareConsideringTypeParameters(otherRef)) { + return true; + } + for (ResolvedReferenceType otherAncestor : otherRef.getAllAncestors()) { + if (compareConsideringTypeParameters(otherAncestor)) { + return true; + } + } + return false; + } else if (other.isTypeVariable()) { + for (ResolvedTypeParameterDeclaration.Bound bound : other.asTypeVariable().asTypeParameter().getBounds()) { + if (bound.isExtends()) { + if (this.isAssignableBy(bound.getType())) { + return true; + } + } + } + return false; + } else if (other.isConstraint()){ + return isAssignableBy(other.asConstraintType().getBound()); + } else if (other.isWildcard()) { + if (this.isJavaLangObject()) { + return true; + } else if (other.asWildcard().isExtends()) { + return isAssignableBy(other.asWildcard().getBoundedType()); + } else { + return false; + } + } else if (other.isUnionType()) { + return other.asUnionType().getCommonAncestor() + .map(ancestor -> isAssignableBy(ancestor)).orElse(false); + } else { + return false; + } + } + + @Override + public Set getDeclaredMethods() { + // TODO replace variables + Set methods = new HashSet<>(); + + getTypeDeclaration().ifPresent(referenceTypeDeclaration -> { + for (ResolvedMethodDeclaration methodDeclaration : referenceTypeDeclaration.getDeclaredMethods()) { + MethodUsage methodUsage = new MethodUsage(methodDeclaration); + methods.add(methodUsage); + } + }); + + return methods; + } + + @Override + public ResolvedType toRawType() { + if (this.isRawType()) { + return this; + } else { + return new ReferenceTypeImpl(typeDeclaration, typeSolver); + } + } + + @Override + public boolean mention(List typeParameters) { + return typeParametersValues().stream().anyMatch(tp -> tp.mention(typeParameters)); + } + + /** + * Execute a transformation on all the type parameters of this element. + */ + @Override + public ResolvedType transformTypeParameters(ResolvedTypeTransformer transformer) { + ResolvedType result = this; + int i = 0; + for (ResolvedType tp : this.typeParametersValues()) { + ResolvedType transformedTp = transformer.transform(tp); + // Identity comparison on purpose + if (transformedTp != tp) { + List typeParametersCorrected = result.asReferenceType().typeParametersValues(); + typeParametersCorrected.set(i, transformedTp); + result = create(typeDeclaration, typeParametersCorrected); + } + i++; + } + return result; + } + + public List getAllAncestors() { + // We need to go through the inheritance line and propagate the type parametes + + List ancestors = typeDeclaration.getAllAncestors(); + + ancestors = ancestors.stream() + .map(a -> typeParametersMap().replaceAll(a).asReferenceType()) + .collect(Collectors.toList()); + + // Avoid repetitions of Object + ancestors.removeIf(ResolvedReferenceType::isJavaLangObject); + ResolvedReferenceTypeDeclaration objectType = typeSolver.getSolvedJavaLangObject(); + ancestors.add(create(objectType)); + + return ancestors; + } + + public List getDirectAncestors() { + // We need to go through the inheritance line and propagate the type parameters + + List ancestors = typeDeclaration.getAncestors(); + + ancestors = ancestors.stream() + .map(a -> typeParametersMap().replaceAll(a).asReferenceType()) + .collect(Collectors.toList()); + + + // Avoid repetitions of Object -- remove them all and, if appropriate, add it back precisely once. + ancestors.removeIf(ResolvedReferenceType::isJavaLangObject); + + // Conditionally re-insert java.lang.Object as an ancestor. + if(this.getTypeDeclaration().isPresent()) { + ResolvedReferenceTypeDeclaration thisTypeDeclaration = this.getTypeDeclaration().get(); + if (thisTypeDeclaration.isClass()) { + Optional optionalSuperClass = thisTypeDeclaration.asClass().getSuperClass(); + boolean superClassIsJavaLangObject = optionalSuperClass.isPresent() && optionalSuperClass.get().isJavaLangObject(); + boolean thisIsJavaLangObject = thisTypeDeclaration.asClass().isJavaLangObject(); + if (superClassIsJavaLangObject && !thisIsJavaLangObject) { + ancestors.add(create(typeSolver.getSolvedJavaLangObject())); + } + } else { + // If this isn't a class (i.e. is enum or interface (or record?)), add java.lang.Object as a supertype + // TODO: Should we also add the implicit java.lang.Enum ancestor in the case of enums? + // TODO: getDirectAncestors() shouldn't be inserting implicit ancesters...? See also issue #2696 + ancestors.add(create(typeSolver.getSolvedJavaLangObject())); + } + } + + return ancestors; + } + + public ResolvedReferenceType deriveTypeParameters(ResolvedTypeParametersMap typeParametersMap) { + return create(typeDeclaration, typeParametersMap); + } + + @Override + public Set getDeclaredFields() { + Set allFields = new LinkedHashSet<>(); + + if (getTypeDeclaration().isPresent()) { + allFields.addAll(getTypeDeclaration().get().getDeclaredFields()); + } + + return allFields; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/MyObjectProvider.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/MyObjectProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..f79fdf71e78c284a76420b20b47912358ca66b0a --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/MyObjectProvider.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.reflectionmodel; + +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.symbolsolver.logic.ObjectProvider; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver; + +/** + * @author Federico Tomassetti + */ +public class MyObjectProvider implements ObjectProvider { + + public static final MyObjectProvider INSTANCE = new MyObjectProvider(); + + private MyObjectProvider() { + // prevent instantiation + } + + @Override + public ResolvedReferenceType object() { + return new ReferenceTypeImpl(new ReflectionClassDeclaration(Object.class, new ReflectionTypeSolver()), new ReflectionTypeSolver()); + } + + @Override + public ResolvedReferenceType byName(String qualifiedName) { + TypeSolver typeSolver = new ReflectionTypeSolver(); + ResolvedReferenceTypeDeclaration typeDeclaration = typeSolver.solveType(qualifiedName); + if (!typeDeclaration.getTypeParameters().isEmpty()) { + throw new UnsupportedOperationException(); + } + return new ReferenceTypeImpl(typeDeclaration, typeSolver); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionAnnotationDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionAnnotationDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..b1efa8e7d46cafeb0e68611fba920f92aadf7ffe --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionAnnotationDeclaration.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.reflectionmodel; + +import com.github.javaparser.ast.body.AnnotationDeclaration; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.core.resolution.MethodUsageResolutionCapability; +import com.github.javaparser.symbolsolver.logic.AbstractTypeDeclaration; +import com.github.javaparser.symbolsolver.logic.ConfilictingGenericTypesException; +import com.github.javaparser.symbolsolver.logic.InferenceContext; +import com.github.javaparser.symbolsolver.logic.MethodResolutionCapability; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.lang.annotation.Inherited; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author Malte Skoruppa + */ +public class ReflectionAnnotationDeclaration extends AbstractTypeDeclaration implements ResolvedAnnotationDeclaration, + MethodUsageResolutionCapability, + MethodResolutionCapability { + + /// + /// Fields + /// + + private Class clazz; + private TypeSolver typeSolver; + private ReflectionClassAdapter reflectionClassAdapter; + + /// + /// Constructor + /// + + public ReflectionAnnotationDeclaration(Class clazz, TypeSolver typeSolver) { + if (!clazz.isAnnotation()) { + throw new IllegalArgumentException("The given type is not an annotation."); + } + + this.clazz = clazz; + this.typeSolver = typeSolver; + this.reflectionClassAdapter = new ReflectionClassAdapter(clazz, typeSolver, this); + } + + /// + /// Public methods + /// + + @Override + public String getPackageName() { + if (clazz.getPackage() != null) { + return clazz.getPackage().getName(); + } + return ""; + } + + @Override + public String getClassName() { + String qualifiedName = getQualifiedName(); + if(qualifiedName.contains(".")) { + return qualifiedName.substring(qualifiedName.lastIndexOf("."), qualifiedName.length()); + } else { + return qualifiedName; + } + } + + @Override + public String getQualifiedName() { + return clazz.getCanonicalName(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + + "clazz=" + clazz.getCanonicalName() + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ReflectionAnnotationDeclaration)) return false; + + ReflectionAnnotationDeclaration that = (ReflectionAnnotationDeclaration) o; + + return clazz.getCanonicalName().equals(that.clazz.getCanonicalName()); + } + + @Override + public int hashCode() { + return clazz.getCanonicalName().hashCode(); + } + + @Override + public boolean isAssignableBy(ResolvedType type) { + // TODO #1836 + throw new UnsupportedOperationException(); + } + + @Override + public boolean isAssignableBy(ResolvedReferenceTypeDeclaration other) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasDirectlyAnnotation(String canonicalName) { + return reflectionClassAdapter.hasDirectlyAnnotation(canonicalName); + } + + @Override + public List getAllFields() { + return reflectionClassAdapter.getAllFields(); + } + + @Override + public List getAncestors(boolean acceptIncompleteList) { + // we do not attempt to perform any symbol solving when analyzing ancestors in the reflection model, so we can + // simply ignore the boolean parameter here; an UnsolvedSymbolException cannot occur + return reflectionClassAdapter.getAncestors(); + } + + @Override + public Set getDeclaredMethods() { + // TODO #1838 + throw new UnsupportedOperationException(); + } + + @Override + public String getName() { + return clazz.getSimpleName(); + } + + @Override + public Optional containerType() { + // TODO #1841 + throw new UnsupportedOperationException("containerType() is not supported for " + this.getClass().getCanonicalName()); + } + + /** + * Annotation declarations cannot have type parameters and hence this method always returns an empty list. + * + * @return An empty list. + */ + @Override + public List getTypeParameters() { + // Annotation declarations cannot have type parameters - i.e. we can always return an empty list. + return Collections.emptyList(); + } + + @Override + public Set internalTypes() { + return Arrays.stream(this.clazz.getDeclaredClasses()) + .map(ic -> ReflectionFactory.typeDeclarationFor(ic, typeSolver)) + .collect(Collectors.toSet()); + } + + @Override + public List getConstructors() { + return Collections.emptyList(); + } + + @Override + public List getAnnotationMembers() { + return Stream.of(clazz.getDeclaredMethods()) + .map(m -> new ReflectionAnnotationMemberDeclaration(m, typeSolver)) + .collect(Collectors.toList()); + } + + @Override + public Optional toAst() { + return Optional.empty(); + } + + @Override + public Optional solveMethodAsUsage(final String name, + final List parameterTypes, + final Context invokationContext, + final List typeParameterValues) { + Optional res = ReflectionMethodResolutionLogic.solveMethodAsUsage(name, parameterTypes, typeSolver, invokationContext, + typeParameterValues, this, clazz); + if (res.isPresent()) { + // We have to replace method type typeParametersValues here + InferenceContext inferenceContext = new InferenceContext(MyObjectProvider.INSTANCE); + MethodUsage methodUsage = res.get(); + int i = 0; + List parameters = new LinkedList<>(); + for (ResolvedType actualType : parameterTypes) { + ResolvedType formalType = methodUsage.getParamType(i); + // We need to replace the class type typeParametersValues (while we derive the method ones) + + parameters.add(inferenceContext.addPair(formalType, actualType)); + i++; + } + try { + ResolvedType returnType = inferenceContext.addSingle(methodUsage.returnType()); + for (int j=0;j solveMethod(final String name, + final List argumentsTypes, + final boolean staticOnly) { + return ReflectionMethodResolutionLogic.solveMethod(name, argumentsTypes, staticOnly, + typeSolver,this, clazz); + } + + @Override + public boolean isInheritable() { + return clazz.getAnnotation(Inherited.class) != null; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionAnnotationMemberDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionAnnotationMemberDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..9c86ad9e09b34414999910b06f899c87d22c27ba --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionAnnotationMemberDeclaration.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.reflectionmodel; + +import com.github.javaparser.ast.expr.*; +import com.github.javaparser.resolution.declarations.ResolvedAnnotationMemberDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.types.ResolvedPrimitiveType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +/** + * @author Malte Skoruppa + */ +public class ReflectionAnnotationMemberDeclaration implements ResolvedAnnotationMemberDeclaration { + + private static Map, Function> valueAsExressionConverter = new HashMap<>(); + static { + valueAsExressionConverter.put(Boolean.class, (value) -> new BooleanLiteralExpr(Boolean.class.cast(value))); + valueAsExressionConverter.put(Character.class, (value) -> new CharLiteralExpr(Character.class.cast(value))); + valueAsExressionConverter.put(Double.class, (value) -> new DoubleLiteralExpr(Double.class.cast(value))); + valueAsExressionConverter.put(Integer.class, (value) -> new IntegerLiteralExpr(Integer.class.cast(value))); + valueAsExressionConverter.put(Long.class, (value) -> new LongLiteralExpr(Long.class.cast(value))); + valueAsExressionConverter.put(String.class, (value) -> new StringLiteralExpr(String.class.cast(value))); + } + + private Method annotationMember; + private TypeSolver typeSolver; + + public ReflectionAnnotationMemberDeclaration(Method annotationMember, TypeSolver typeSolver) { + this.annotationMember = annotationMember; + this.typeSolver = typeSolver; + } + + @Override + public Expression getDefaultValue() { + Object value = annotationMember.getDefaultValue(); + Function fn = valueAsExressionConverter.get(value.getClass()); + if (fn == null) throw new UnsupportedOperationException(String.format("Obtaining the type of the annotation member %s is not supported yet.", annotationMember.getName())); + return fn.apply(value); + } + + @Override + public ResolvedType getType() { + Class returnType = annotationMember.getReturnType(); + if (returnType.isPrimitive()) { + return ResolvedPrimitiveType.byName(returnType.getName()); + } + SymbolReference rrtd = typeSolver.tryToSolveType(returnType.getName()); + if (rrtd.isSolved()) { + return new ReferenceTypeImpl(rrtd.getCorrespondingDeclaration(), typeSolver); + } + throw new UnsupportedOperationException(String.format("Obtaining the type of the annotation member %s is not supported yet.", annotationMember.getName())); + } + + @Override + public String getName() { + return annotationMember.getName(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionClassAdapter.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionClassAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..d36ee07a7eae4537f302687e806034fe67e9462a --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionClassAdapter.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.reflectionmodel; + +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.LambdaArgumentTypePlaceholder; +import com.github.javaparser.symbolsolver.logic.FunctionalInterfaceLogic; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.NullType; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.TypeVariable; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +class ReflectionClassAdapter { + + private Class clazz; + private TypeSolver typeSolver; + private ResolvedReferenceTypeDeclaration typeDeclaration; + + public ReflectionClassAdapter(Class clazz, TypeSolver typeSolver, ResolvedReferenceTypeDeclaration typeDeclaration) { + this.clazz = clazz; + this.typeSolver = typeSolver; + this.typeDeclaration = typeDeclaration; + } + + public Optional getSuperClass() { + if (clazz.getGenericSuperclass() == null) { + // There isn't a super class (e.g. when this refers to java.lang.Object) + return Optional.empty(); + } + java.lang.reflect.Type superType = clazz.getGenericSuperclass(); + if (superType instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) superType; + List typeParameters = Arrays.stream(parameterizedType.getActualTypeArguments()) + .map((t) -> ReflectionFactory.typeUsageFor(t, typeSolver)) + .collect(Collectors.toList()); + return Optional.of(new ReferenceTypeImpl(new ReflectionClassDeclaration(clazz.getSuperclass(), typeSolver), typeParameters, typeSolver)); + } + return Optional.of(new ReferenceTypeImpl(new ReflectionClassDeclaration(clazz.getSuperclass(), typeSolver), typeSolver)); + } + + public List getInterfaces() { + List interfaces = new ArrayList<>(); + for (java.lang.reflect.Type superInterface : clazz.getGenericInterfaces()) { + if (superInterface instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) superInterface; + List typeParameters = Arrays.stream(parameterizedType.getActualTypeArguments()) + .map((t) -> ReflectionFactory.typeUsageFor(t, typeSolver)) + .collect(Collectors.toList()); + interfaces.add(new ReferenceTypeImpl(new ReflectionInterfaceDeclaration((Class) ((ParameterizedType) superInterface).getRawType(), typeSolver), typeParameters, typeSolver)); + } else { + interfaces.add(new ReferenceTypeImpl(new ReflectionInterfaceDeclaration((Class) superInterface, typeSolver), typeSolver)); + } + } + return interfaces; + } + + public List getAncestors() { + List ancestors = new LinkedList<>(); + if (getSuperClass().isPresent()) { + ReferenceTypeImpl superClass = getSuperClass().get(); + ancestors.add(superClass); + } else { + // Inject the implicitly added extends java.lang.Object + ReferenceTypeImpl object = new ReferenceTypeImpl(new ReflectionClassDeclaration(Object.class, typeSolver), typeSolver); + ancestors.add(object); + } + ancestors.addAll(getInterfaces()); + for (int i = 0; i < ancestors.size(); i++) { + ResolvedReferenceType ancestor = ancestors.get(i); + if (ancestor.hasName() && ancestor.isJavaLangObject()) { + ancestors.remove(i); + i--; + } + } + return ancestors; + } + + public ResolvedFieldDeclaration getField(String name) { + for (Field field : clazz.getDeclaredFields()) { + if (field.getName().equals(name)) { + return new ReflectionFieldDeclaration(field, typeSolver); + } + } + for (ResolvedReferenceType ancestor : typeDeclaration.getAllAncestors()) { + if (ancestor.getTypeDeclaration().isPresent()) { + ResolvedReferenceTypeDeclaration typeDeclaration = ancestor.getTypeDeclaration().get(); + if (typeDeclaration.hasField(name)) { + ReflectionFieldDeclaration reflectionFieldDeclaration = (ReflectionFieldDeclaration) typeDeclaration.getField(name); + return reflectionFieldDeclaration.replaceType(ancestor.getFieldType(name).get()); + } + } + } + throw new UnsolvedSymbolException(name, "Field in " + this); + } + + public boolean hasField(String name) { + // First consider fields declared on this class + for (Field field : clazz.getDeclaredFields()) { + if (field.getName().equals(name)) { + return true; + } + } + + // Then consider fields inherited from ancestors + for (ResolvedReferenceType ancestor : typeDeclaration.getAllAncestors()) { + if (ancestor.getTypeDeclaration().isPresent() && ancestor.getTypeDeclaration().get().hasField(name)) { + return true; + } + } + + return false; + } + + public List getAllFields() { + ArrayList fields = new ArrayList<>(); + + // First consider fields declared on this class + for (Field field : clazz.getDeclaredFields()) { + fields.add(new ReflectionFieldDeclaration(field, typeSolver)); + } + + // Then consider fields inherited from ancestors + for (ResolvedReferenceType ancestor : typeDeclaration.getAllAncestors()) { + ancestor.getTypeDeclaration().ifPresent(ancestorTypeDeclaration -> { + fields.addAll(ancestorTypeDeclaration.getAllFields()); + }); + } + + return fields; + } + + public Set getDeclaredMethods() { + return Arrays.stream(clazz.getDeclaredMethods()) + .filter(m -> !m.isSynthetic() && !m.isBridge()) + .map(m -> new ReflectionMethodDeclaration(m, typeSolver)) + .collect(Collectors.toSet()); + } + + public List getTypeParameters() { + List params = new ArrayList<>(); + for (TypeVariable tv : this.clazz.getTypeParameters()) { + params.add(new ReflectionTypeParameter(tv, true, typeSolver)); + } + return params; + } + + public boolean isAssignableBy(ResolvedType type) { + if (type instanceof NullType) { + return true; + } + if (type instanceof LambdaArgumentTypePlaceholder) { + return isFunctionalInterface(); + } + if (type.isArray()) { + return false; + } + if (type.isPrimitive()) { + return false; + } + if (type.describe().equals(typeDeclaration.getQualifiedName())) { + return true; + } + if (type instanceof ReferenceTypeImpl) { + ReferenceTypeImpl otherTypeDeclaration = (ReferenceTypeImpl) type; + if(otherTypeDeclaration.getTypeDeclaration().isPresent()) { + return otherTypeDeclaration.getTypeDeclaration().get().canBeAssignedTo(typeDeclaration); + } + } + + return false; + } + + public boolean hasDirectlyAnnotation(String canonicalName) { + for (Annotation a : clazz.getDeclaredAnnotations()) { + if (a.annotationType().getCanonicalName().equals(canonicalName)) { + return true; + } + } + return false; + } + + private final boolean isFunctionalInterface() { + return FunctionalInterfaceLogic.getFunctionalMethod(typeDeclaration).isPresent(); + } + + public List getConstructors() { + return Arrays.stream(clazz.getDeclaredConstructors()) + .filter(m -> !m.isSynthetic()) + .map(m -> new ReflectionConstructorDeclaration(m, typeSolver)) + .collect(Collectors.toList()); + } + + public Optional containerType() { + Class declaringClass = clazz.getDeclaringClass(); + return declaringClass == null ? + Optional.empty() : + Optional.of(ReflectionFactory.typeDeclarationFor(declaringClass, typeSolver)); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionClassDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionClassDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..9ceea929209b178752f71c9237840d6083ee0ec1 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionClassDeclaration.java @@ -0,0 +1,405 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.reflectionmodel; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.ast.Node; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.core.resolution.MethodUsageResolutionCapability; +import com.github.javaparser.symbolsolver.javaparsermodel.LambdaArgumentTypePlaceholder; +import com.github.javaparser.symbolsolver.javaparsermodel.contexts.ContextHelper; +import com.github.javaparser.symbolsolver.logic.AbstractClassDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.symbolsolver.reflectionmodel.comparators.MethodComparator; +import com.github.javaparser.symbolsolver.resolution.MethodResolutionLogic; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +public class ReflectionClassDeclaration extends AbstractClassDeclaration implements MethodUsageResolutionCapability { + + /// + /// Fields + /// + + private Class clazz; + private TypeSolver typeSolver; + private ReflectionClassAdapter reflectionClassAdapter; + + /// + /// Constructors + /// + + public ReflectionClassDeclaration(Class clazz, TypeSolver typeSolver) { + if (clazz == null) { + throw new IllegalArgumentException("Class should not be null"); + } + if (clazz.isInterface()) { + throw new IllegalArgumentException("Class should not be an interface"); + } + if (clazz.isPrimitive()) { + throw new IllegalArgumentException("Class should not represent a primitive class"); + } + if (clazz.isArray()) { + throw new IllegalArgumentException("Class should not be an array"); + } + if (clazz.isEnum()) { + throw new IllegalArgumentException("Class should not be an enum"); + } + this.clazz = clazz; + this.typeSolver = typeSolver; + this.reflectionClassAdapter = new ReflectionClassAdapter(clazz, typeSolver, this); + } + + /// + /// Public methods + /// + + @Override + public Set getDeclaredMethods() { + return reflectionClassAdapter.getDeclaredMethods(); + } + + @Override + public List getAncestors(boolean acceptIncompleteList) { + // we do not attempt to perform any symbol solving when analyzing ancestors in the reflection model, so we can + // simply ignore the boolean parameter here; an UnsolvedSymbolException cannot occur + return reflectionClassAdapter.getAncestors(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ReflectionClassDeclaration that = (ReflectionClassDeclaration) o; + + if (!clazz.getCanonicalName().equals(that.clazz.getCanonicalName())) return false; + + return true; + } + + @Override + public int hashCode() { + return clazz.hashCode(); + } + + + @Override + public String getPackageName() { + if (clazz.getPackage() != null) { + return clazz.getPackage().getName(); + } + return null; + } + + @Override + public String getClassName() { + String canonicalName = clazz.getCanonicalName(); + if (canonicalName != null && getPackageName() != null) { + return canonicalName.substring(getPackageName().length() + 1, canonicalName.length()); + } + return null; + } + + @Override + public String getQualifiedName() { + return clazz.getCanonicalName(); + } + + @Override + @Deprecated + public SymbolReference solveMethod(String name, List argumentsTypes, boolean staticOnly) { + Predicate staticFilter = m -> !staticOnly || (staticOnly && Modifier.isStatic(m.getModifiers())); + + List candidateSolvedMethods = new ArrayList<>(); + + // First consider the directly-declared methods. + List methods = Arrays.stream(clazz.getDeclaredMethods()) + .filter(m -> m.getName().equals(name)) + .filter(staticFilter) + .filter(method -> !method.isBridge()) + .filter(method -> !method.isSynthetic()) + .sorted(new MethodComparator()) + .collect(Collectors.toList()); + + // Transform into resolved method declarations + for (Method method : methods) { + ResolvedMethodDeclaration methodDeclaration = new ReflectionMethodDeclaration(method, typeSolver); + candidateSolvedMethods.add(methodDeclaration); + + // no need to search for overloaded/inherited candidateSolvedMethods if the method has no parameters + if (argumentsTypes.isEmpty() && methodDeclaration.getNumberOfParams() == 0) { + return SymbolReference.solved(methodDeclaration); + } + } + + // Next consider methods declared within extended superclasses. + getSuperClass() + .flatMap(ResolvedReferenceType::getTypeDeclaration) + .ifPresent(superClassTypeDeclaration -> { + SymbolReference ref = MethodResolutionLogic.solveMethodInType(superClassTypeDeclaration, name, argumentsTypes, staticOnly); + if (ref.isSolved()) { + candidateSolvedMethods.add(ref.getCorrespondingDeclaration()); + } + }); + + // Next consider methods declared within implemented interfaces. + for (ResolvedReferenceType interfaceDeclaration : getInterfaces()) { + interfaceDeclaration.getTypeDeclaration() + .ifPresent(interfaceTypeDeclaration -> { + SymbolReference ref = MethodResolutionLogic.solveMethodInType(interfaceTypeDeclaration, name, argumentsTypes, staticOnly); + if (ref.isSolved()) { + candidateSolvedMethods.add(ref.getCorrespondingDeclaration()); + } + }); + } + + // When empty there is no sense in trying to find the most applicable. + // This is useful for debugging. Performance is not affected as + // MethodResolutionLogic.findMostApplicable method returns very early + // when candidateSolvedMethods is empty. + if (candidateSolvedMethods.isEmpty()) { + return SymbolReference.unsolved(ResolvedMethodDeclaration.class); + } + return MethodResolutionLogic.findMostApplicable(candidateSolvedMethods, name, argumentsTypes, typeSolver); + } + + @Override + public String toString() { + return "ReflectionClassDeclaration{" + + "clazz=" + getId() + + '}'; + } + + public ResolvedType getUsage(Node node) { + + return new ReferenceTypeImpl(this, typeSolver); + } + + public Optional solveMethodAsUsage(String name, List argumentsTypes, Context invokationContext, List typeParameterValues) { + List methodUsages = new ArrayList<>(); + + List allMethods = Arrays.stream(clazz.getDeclaredMethods()) + .filter((m) -> m.getName().equals(name)) + .sorted(new MethodComparator()) + .collect(Collectors.toList()); + + for (Method method : allMethods) { + if (method.isBridge() || method.isSynthetic()) { + continue; + } + ResolvedMethodDeclaration methodDeclaration = new ReflectionMethodDeclaration(method, typeSolver); + MethodUsage methodUsage = new MethodUsage(methodDeclaration); + for (int i = 0; i < getTypeParameters().size() && i < typeParameterValues.size(); i++) { + ResolvedTypeParameterDeclaration tpToReplace = getTypeParameters().get(i); + ResolvedType newValue = typeParameterValues.get(i); + methodUsage = methodUsage.replaceTypeParameter(tpToReplace, newValue); + } + methodUsages.add(methodUsage); + + // no need to search for overloaded/inherited methodUsages if the method has no parameters + if (argumentsTypes.isEmpty() && methodUsage.getNoParams() == 0) { + return Optional.of(methodUsage); + } + } + + getSuperClass().ifPresent(superClass -> { + superClass.getTypeDeclaration().ifPresent(superClassTypeDeclaration -> { + ContextHelper.solveMethodAsUsage(superClassTypeDeclaration, name, argumentsTypes, invokationContext, typeParameterValues) + .ifPresent(methodUsages::add); + }); + }); + + for (ResolvedReferenceType interfaceDeclaration : getInterfaces()) { + interfaceDeclaration.getTypeDeclaration() + .flatMap(superClassTypeDeclaration -> interfaceDeclaration.getTypeDeclaration()) + .flatMap(interfaceTypeDeclaration -> ContextHelper.solveMethodAsUsage(interfaceTypeDeclaration, name, argumentsTypes, invokationContext, typeParameterValues)) + .ifPresent(methodUsages::add); + } + Optional ref = MethodResolutionLogic.findMostApplicableUsage(methodUsages, name, argumentsTypes, typeSolver); + return ref; + } + + @Override + public boolean canBeAssignedTo(ResolvedReferenceTypeDeclaration other) { + if (other instanceof LambdaArgumentTypePlaceholder) { + return isFunctionalInterface(); + } + if (other.getQualifiedName().equals(getQualifiedName())) { + return true; + } + if (this.clazz.getSuperclass() != null + && new ReflectionClassDeclaration(clazz.getSuperclass(), typeSolver).canBeAssignedTo(other)) { + return true; + } + for (Class interfaze : clazz.getInterfaces()) { + if (new ReflectionInterfaceDeclaration(interfaze, typeSolver).canBeAssignedTo(other)) { + return true; + } + } + + return false; + } + + @Override + public boolean isAssignableBy(ResolvedType type) { + return reflectionClassAdapter.isAssignableBy(type); + } + + @Override + public boolean isTypeParameter() { + return false; + } + + @Override + public ResolvedFieldDeclaration getField(String name) { + return reflectionClassAdapter.getField(name); + } + + @Override + public List getAllFields() { + return reflectionClassAdapter.getAllFields(); + } + + @Deprecated + public SymbolReference solveSymbol(String name, TypeSolver typeSolver) { + for (Field field : clazz.getFields()) { + if (field.getName().equals(name)) { + return SymbolReference.solved(new ReflectionFieldDeclaration(field, typeSolver)); + } + } + return SymbolReference.unsolved(ResolvedValueDeclaration.class); + } + + @Override + public boolean hasDirectlyAnnotation(String canonicalName) { + return reflectionClassAdapter.hasDirectlyAnnotation(canonicalName); + } + + @Override + public boolean hasField(String name) { + return reflectionClassAdapter.hasField(name); + } + + @Override + public boolean isAssignableBy(ResolvedReferenceTypeDeclaration other) { + return isAssignableBy(new ReferenceTypeImpl(other, typeSolver)); + } + + @Override + public String getName() { + return clazz.getSimpleName(); + } + + @Override + public boolean isField() { + return false; + } + + @Override + public boolean isParameter() { + return false; + } + + @Override + public boolean isType() { + return true; + } + + @Override + public boolean isClass() { + return !clazz.isInterface(); + } + + @Override + public Optional getSuperClass() { + if(!reflectionClassAdapter.getSuperClass().isPresent()) { + return Optional.empty(); + } + return Optional.of(reflectionClassAdapter.getSuperClass().get()); + } + + @Override + public List getInterfaces() { + return reflectionClassAdapter.getInterfaces(); + } + + @Override + public boolean isInterface() { + return clazz.isInterface(); + } + + @Override + public List getTypeParameters() { + return reflectionClassAdapter.getTypeParameters(); + } + + @Override + public AccessSpecifier accessSpecifier() { + return ReflectionFactory.modifiersToAccessLevel(this.clazz.getModifiers()); + } + + @Override + public List getConstructors() { + return reflectionClassAdapter.getConstructors(); + } + + @Override + public Optional containerType() { + return reflectionClassAdapter.containerType(); + } + + @Override + public Set internalTypes() { + return Arrays.stream(this.clazz.getDeclaredClasses()) + .map(ic -> ReflectionFactory.typeDeclarationFor(ic, typeSolver)) + .collect(Collectors.toSet()); + } + + @Override + public Optional toAst() { + return Optional.empty(); + } + + /// + /// Protected methods + /// + + @Override + protected ResolvedReferenceType object() { + return new ReferenceTypeImpl(typeSolver.getSolvedJavaLangObject(), typeSolver); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionConstructorDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionConstructorDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..51e1b98ddaf3018bf2405cae1c86e26c83e474ca --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionConstructorDeclaration.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.reflectionmodel; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.ast.body.ConstructorDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedClassDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedConstructorDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedParameterDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * @author Fred Lefévère-Laoide + */ +public class ReflectionConstructorDeclaration implements ResolvedConstructorDeclaration { + + private Constructor constructor; + private TypeSolver typeSolver; + + public ReflectionConstructorDeclaration(Constructor constructor, TypeSolver typeSolver) { + this.constructor = constructor; + this.typeSolver = typeSolver; + } + + @Override + public ResolvedClassDeclaration declaringType() { + return new ReflectionClassDeclaration(constructor.getDeclaringClass(), typeSolver); + } + + @Override + public int getNumberOfParams() { + return constructor.getParameterCount(); + } + + @Override + public ResolvedParameterDeclaration getParam(int i) { + if (i < 0 || i >= getNumberOfParams()) { + throw new IllegalArgumentException(String.format("No param with index %d. Number of params: %d", i, getNumberOfParams())); + } + boolean variadic = false; + if (constructor.isVarArgs()) { + variadic = i == (constructor.getParameterCount() - 1); + } + return new ReflectionParameterDeclaration(constructor.getParameterTypes()[i], + constructor.getGenericParameterTypes()[i], typeSolver, variadic, + constructor.getParameters()[i].getName()); + } + + @Override + public String getName() { + return constructor.getDeclaringClass().getSimpleName(); + } + + @Override + public AccessSpecifier accessSpecifier() { + return ReflectionFactory.modifiersToAccessLevel(constructor.getModifiers()); + } + + @Override + public List getTypeParameters() { + return Arrays.stream(constructor.getTypeParameters()).map((refTp) -> new ReflectionTypeParameter(refTp, false, typeSolver)).collect(Collectors.toList()); + } + + @Override + public int getNumberOfSpecifiedExceptions() { + return this.constructor.getExceptionTypes().length; + } + + @Override + public ResolvedType getSpecifiedException(int index) { + if (index < 0 || index >= getNumberOfSpecifiedExceptions()) { + throw new IllegalArgumentException(); + } + return ReflectionFactory.typeUsageFor(this.constructor.getExceptionTypes()[index], typeSolver); + } + + @Override + public Optional toAst() { + return Optional.empty(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionEnumConstantDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionEnumConstantDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..af013d74ce0c4af1b6d81d3d610f98f906b6dae3 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionEnumConstantDeclaration.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.reflectionmodel; + +import com.github.javaparser.resolution.declarations.ResolvedEnumConstantDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; + +import java.lang.reflect.Field; + +public class ReflectionEnumConstantDeclaration implements ResolvedEnumConstantDeclaration { + + private Field enumConstant; + private TypeSolver typeSolver; + + public ReflectionEnumConstantDeclaration(Field enumConstant, TypeSolver typeSolver) { + if (!enumConstant.isEnumConstant()) { + throw new IllegalArgumentException("The given field does not represent an enum constant"); + } + this.enumConstant = enumConstant; + this.typeSolver = typeSolver; + } + + @Override + public String getName() { + return enumConstant.getName(); + } + + @Override + public ResolvedType getType() { + Class enumClass = enumConstant.getDeclaringClass(); + ResolvedReferenceTypeDeclaration typeDeclaration = new ReflectionEnumDeclaration(enumClass, typeSolver); + return new ReferenceTypeImpl(typeDeclaration, typeSolver); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionEnumDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionEnumDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..b5bea9e76a7050b4456766600b9a15619c128ce5 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionEnumDeclaration.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.reflectionmodel; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.core.resolution.MethodUsageResolutionCapability; +import com.github.javaparser.symbolsolver.logic.AbstractTypeDeclaration; +import com.github.javaparser.symbolsolver.logic.ConfilictingGenericTypesException; +import com.github.javaparser.symbolsolver.logic.InferenceContext; +import com.github.javaparser.symbolsolver.logic.MethodResolutionCapability; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; + +import java.lang.reflect.Field; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +public class ReflectionEnumDeclaration extends AbstractTypeDeclaration + implements ResolvedEnumDeclaration, MethodResolutionCapability, MethodUsageResolutionCapability { + + /// + /// Fields + /// + + private Class clazz; + private TypeSolver typeSolver; + private ReflectionClassAdapter reflectionClassAdapter; + + /// + /// Constructors + /// + + public ReflectionEnumDeclaration(Class clazz, TypeSolver typeSolver) { + if (clazz == null) { + throw new IllegalArgumentException("Class should not be null"); + } + if (clazz.isInterface()) { + throw new IllegalArgumentException("Class should not be an interface"); + } + if (clazz.isPrimitive()) { + throw new IllegalArgumentException("Class should not represent a primitive class"); + } + if (clazz.isArray()) { + throw new IllegalArgumentException("Class should not be an array"); + } + if (!clazz.isEnum()) { + throw new IllegalArgumentException("Class should be an enum"); + } + this.clazz = clazz; + this.typeSolver = typeSolver; + this.reflectionClassAdapter = new ReflectionClassAdapter(clazz, typeSolver, this); + } + + /// + /// Public methods + /// + + @Override + public AccessSpecifier accessSpecifier() { + return ReflectionFactory.modifiersToAccessLevel(this.clazz.getModifiers()); + } + + @Override + public Optional containerType() { + return reflectionClassAdapter.containerType(); + } + + @Override + public String getPackageName() { + if (clazz.getPackage() != null) { + return clazz.getPackage().getName(); + } + return null; + } + + @Override + public String getClassName() { + String canonicalName = clazz.getCanonicalName(); + if (canonicalName != null && getPackageName() != null) { + return canonicalName.substring(getPackageName().length() + 1, canonicalName.length()); + } + return null; + } + + @Override + public String getQualifiedName() { + return clazz.getCanonicalName(); + } + + @Override + public List getAncestors(boolean acceptIncompleteList) { + // we do not attempt to perform any symbol solving when analyzing ancestors in the reflection model, so we can + // simply ignore the boolean parameter here; an UnsolvedSymbolException cannot occur + return reflectionClassAdapter.getAncestors(); + } + + @Override + public ResolvedFieldDeclaration getField(String name) { + return reflectionClassAdapter.getField(name); + } + + @Override + public boolean hasField(String name) { + return reflectionClassAdapter.hasField(name); + } + + @Override + public List getAllFields() { + return reflectionClassAdapter.getAllFields(); + } + + @Override + public Set getDeclaredMethods() { + return reflectionClassAdapter.getDeclaredMethods(); + } + + @Override + public boolean isAssignableBy(ResolvedType type) { + return reflectionClassAdapter.isAssignableBy(type); + } + + @Override + public boolean isAssignableBy(ResolvedReferenceTypeDeclaration other) { + return isAssignableBy(new ReferenceTypeImpl(other, typeSolver)); + } + + @Override + public boolean hasDirectlyAnnotation(String qualifiedName) { + return reflectionClassAdapter.hasDirectlyAnnotation(qualifiedName); + } + + @Override + public String getName() { + return clazz.getSimpleName(); + } + + @Override + public List getTypeParameters() { + return reflectionClassAdapter.getTypeParameters(); + } + + @Override + public SymbolReference solveMethod(String name, List parameterTypes, boolean staticOnly) { + return ReflectionMethodResolutionLogic.solveMethod(name, parameterTypes, staticOnly, + typeSolver,this, clazz); + } + + public Optional solveMethodAsUsage(String name, List parameterTypes, + Context invokationContext, List typeParameterValues) { + Optional res = ReflectionMethodResolutionLogic.solveMethodAsUsage(name, parameterTypes, typeSolver, invokationContext, + typeParameterValues, this, clazz); + if (res.isPresent()) { + // We have to replace method type typeParametersValues here + InferenceContext inferenceContext = new InferenceContext(MyObjectProvider.INSTANCE); + MethodUsage methodUsage = res.get(); + int i = 0; + List parameters = new LinkedList<>(); + for (ResolvedType actualType : parameterTypes) { + ResolvedType formalType = methodUsage.getParamType(i); + // We need to replace the class type typeParametersValues (while we derive the method ones) + + parameters.add(inferenceContext.addPair(formalType, actualType)); + i++; + } + try { + ResolvedType returnType = inferenceContext.addSingle(methodUsage.returnType()); + for (int j=0;j getEnumConstants() { + return Arrays.stream(clazz.getFields()) + .filter(Field::isEnumConstant) + .map(c -> new ReflectionEnumConstantDeclaration(c, typeSolver)) + .collect(Collectors.toList()); + } + + @Override + public Set internalTypes() { + return Arrays.stream(this.clazz.getDeclaredClasses()) + .map(ic -> ReflectionFactory.typeDeclarationFor(ic, typeSolver)) + .collect(Collectors.toSet()); + } + + @Override + public List getConstructors() { + return reflectionClassAdapter.getConstructors(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionFactory.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..39205dc407b19da6e53caf13948013f1771d3b27 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionFactory.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.reflectionmodel; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.*; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; + +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.WildcardType; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author Federico Tomassetti + */ +public class ReflectionFactory { + + private static String JAVA_LANG_OBJECT = Object.class.getCanonicalName(); + + public static ResolvedReferenceTypeDeclaration typeDeclarationFor(Class clazz, TypeSolver typeSolver) { + if (clazz.isArray()) { + throw new IllegalArgumentException("No type declaration available for an Array"); + } else if (clazz.isPrimitive()) { + throw new IllegalArgumentException(); + } else if (clazz.isAnnotation()) { + return new ReflectionAnnotationDeclaration(clazz, typeSolver); + } else if (clazz.isInterface()) { + return new ReflectionInterfaceDeclaration(clazz, typeSolver); + } else if (clazz.isEnum()) { + return new ReflectionEnumDeclaration(clazz, typeSolver); + } else { + return new ReflectionClassDeclaration(clazz, typeSolver); + } + } + + public static ResolvedType typeUsageFor(java.lang.reflect.Type type, TypeSolver typeSolver) { + if (type instanceof java.lang.reflect.TypeVariable) { + java.lang.reflect.TypeVariable tv = (java.lang.reflect.TypeVariable) type; + boolean declaredOnClass = tv.getGenericDeclaration() instanceof java.lang.reflect.Type; + ResolvedTypeParameterDeclaration typeParameter = new ReflectionTypeParameter(tv, declaredOnClass, typeSolver); + return new ResolvedTypeVariable(typeParameter); + } else if (type instanceof ParameterizedType) { + ParameterizedType pt = (ParameterizedType) type; + ResolvedReferenceType rawType = typeUsageFor(pt.getRawType(), typeSolver).asReferenceType(); + List actualTypes = new ArrayList<>(); + actualTypes.addAll(Arrays.asList(pt.getActualTypeArguments())); + // we consume the actual types + rawType = rawType.transformTypeParameters(tp -> typeUsageFor(actualTypes.remove(0), typeSolver)).asReferenceType(); + return rawType; + } else if (type instanceof Class) { + Class c = (Class) type; + if (c.isPrimitive()) { + if (c.getName().equals(Void.TYPE.getName())) { + return ResolvedVoidType.INSTANCE; + } else { + return ResolvedPrimitiveType.byName(c.getName()); + } + } else if (c.isArray()) { + return new ResolvedArrayType(typeUsageFor(c.getComponentType(), typeSolver)); + } else { + return new ReferenceTypeImpl(typeDeclarationFor(c, typeSolver), typeSolver); + } + } else if (type instanceof GenericArrayType) { + GenericArrayType genericArrayType = (GenericArrayType) type; + return new ResolvedArrayType(typeUsageFor(genericArrayType.getGenericComponentType(), typeSolver)); + } else if (type instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) type; + if (wildcardType.getLowerBounds().length > 0 && wildcardType.getUpperBounds().length > 0) { + if (wildcardType.getUpperBounds().length == 1 && wildcardType.getUpperBounds()[0].getTypeName().equals(JAVA_LANG_OBJECT)) { + // ok, it does not matter + } + } + if (wildcardType.getLowerBounds().length > 0) { + if (wildcardType.getLowerBounds().length > 1) { + throw new UnsupportedOperationException(); + } + return ResolvedWildcard.superBound(typeUsageFor(wildcardType.getLowerBounds()[0], typeSolver)); + } + if (wildcardType.getUpperBounds().length > 0) { + if (wildcardType.getUpperBounds().length > 1) { + throw new UnsupportedOperationException(); + } + return ResolvedWildcard.extendsBound(typeUsageFor(wildcardType.getUpperBounds()[0], typeSolver)); + } + return ResolvedWildcard.UNBOUNDED; + } else { + throw new UnsupportedOperationException(type.getClass().getCanonicalName() + " " + type); + } + } + + static AccessSpecifier modifiersToAccessLevel(final int modifiers) { + if (Modifier.isPublic(modifiers)) { + return AccessSpecifier.PUBLIC; + } else if (Modifier.isProtected(modifiers)) { + return AccessSpecifier.PROTECTED; + } else if (Modifier.isPrivate(modifiers)) { + return AccessSpecifier.PRIVATE; + } else { + return AccessSpecifier.PACKAGE_PRIVATE; + } + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionFieldDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionFieldDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..4416adcf5fc065d31fe728a444645c71ef3bbe24 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionFieldDeclaration.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.reflectionmodel; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.resolution.declarations.ResolvedFieldDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +/** + * @author Federico Tomassetti + */ +public class ReflectionFieldDeclaration implements ResolvedFieldDeclaration { + + private Field field; + private TypeSolver typeSolver; + private ResolvedType type; + + public ReflectionFieldDeclaration(Field field, TypeSolver typeSolver) { + this.field = field; + this.typeSolver = typeSolver; + this.type = calcType(); + } + + private ReflectionFieldDeclaration(Field field, TypeSolver typeSolver, ResolvedType type) { + this.field = field; + this.typeSolver = typeSolver; + this.type = type; + } + + @Override + public ResolvedType getType() { + return type; + } + + private ResolvedType calcType() { + // TODO consider interfaces, enums, primitive types, arrays + return ReflectionFactory.typeUsageFor(field.getGenericType(), typeSolver); + } + + @Override + public String getName() { + return field.getName(); + } + + @Override + public boolean isStatic() { + return Modifier.isStatic(field.getModifiers()); + } + + @Override + public boolean isVolatile() { + return Modifier.isVolatile(field.getModifiers()); + } + + @Override + public boolean isField() { + return true; + } + + @Override + public ResolvedTypeDeclaration declaringType() { + return ReflectionFactory.typeDeclarationFor(field.getDeclaringClass(), typeSolver); + } + + public ResolvedFieldDeclaration replaceType(ResolvedType fieldType) { + return new ReflectionFieldDeclaration(field, typeSolver, fieldType); + } + + @Override + public boolean isParameter() { + return false; + } + + @Override + public boolean isType() { + return false; + } + + @Override + public AccessSpecifier accessSpecifier() { + return ReflectionFactory.modifiersToAccessLevel(field.getModifiers()); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionInterfaceDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionInterfaceDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..f2bd21baf36a11afc28f3e43aa33aa5da7335b98 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionInterfaceDeclaration.java @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.reflectionmodel; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.core.resolution.MethodUsageResolutionCapability; +import com.github.javaparser.symbolsolver.javaparsermodel.LambdaArgumentTypePlaceholder; +import com.github.javaparser.symbolsolver.logic.AbstractTypeDeclaration; +import com.github.javaparser.symbolsolver.logic.ConfilictingGenericTypesException; +import com.github.javaparser.symbolsolver.logic.InferenceContext; +import com.github.javaparser.symbolsolver.logic.MethodResolutionCapability; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.NullType; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; + +import java.lang.reflect.Field; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +public class ReflectionInterfaceDeclaration extends AbstractTypeDeclaration + implements ResolvedInterfaceDeclaration, MethodResolutionCapability, MethodUsageResolutionCapability { + + /// + /// Fields + /// + + private Class clazz; + private TypeSolver typeSolver; + private ReflectionClassAdapter reflectionClassAdapter; + + /// + /// Constructor + /// + + public ReflectionInterfaceDeclaration(Class clazz, TypeSolver typeSolver) { + if (!clazz.isInterface()) { + throw new IllegalArgumentException(); + } + + this.clazz = clazz; + this.typeSolver = typeSolver; + this.reflectionClassAdapter = new ReflectionClassAdapter(clazz, typeSolver, this); + } + + /// + /// Public methods + /// + + @Override + public boolean isAssignableBy(ResolvedReferenceTypeDeclaration other) { + return isAssignableBy(new ReferenceTypeImpl(other, typeSolver)); + } + + @Override + public String getPackageName() { + if (clazz.getPackage() != null) { + return clazz.getPackage().getName(); + } + return null; + } + + @Override + public String getClassName() { + String canonicalName = clazz.getCanonicalName(); + if (canonicalName != null && getPackageName() != null) { + return canonicalName.substring(getPackageName().length() + 1); + } + return null; + } + + @Override + public String getQualifiedName() { + return clazz.getCanonicalName(); + } + + @Override + @Deprecated + public SymbolReference solveMethod(String name, List parameterTypes, boolean staticOnly) { + return ReflectionMethodResolutionLogic.solveMethod(name, parameterTypes, staticOnly, + typeSolver,this, clazz); + } + + @Override + public String toString() { + return "ReflectionInterfaceDeclaration{" + + "clazz=" + clazz.getCanonicalName() + + '}'; + } + + public ResolvedType getUsage(Node node) { + return new ReferenceTypeImpl(this, typeSolver); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ReflectionInterfaceDeclaration)) return false; + + ReflectionInterfaceDeclaration that = (ReflectionInterfaceDeclaration) o; + + if (!clazz.getCanonicalName().equals(that.clazz.getCanonicalName())) return false; + + if (!getTypeParameters().equals(that.getTypeParameters())) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return clazz.hashCode(); + } + + public Optional solveMethodAsUsage(String name, List parameterTypes, + Context invokationContext, List typeParameterValues) { + Optional res = ReflectionMethodResolutionLogic.solveMethodAsUsage(name, parameterTypes, typeSolver, invokationContext, + typeParameterValues, this, clazz); + if (res.isPresent()) { + // We have to replace method type typeParametersValues here + InferenceContext inferenceContext = new InferenceContext(MyObjectProvider.INSTANCE); + MethodUsage methodUsage = res.get(); + int i = 0; + List parameters = new LinkedList<>(); + for (ResolvedType actualType : parameterTypes) { + ResolvedType formalType = methodUsage.getParamType(i); + // We need to replace the class type typeParametersValues (while we derive the method ones) + + parameters.add(inferenceContext.addPair(formalType, actualType)); + i++; + } + try { + ResolvedType returnType = inferenceContext.addSingle(methodUsage.returnType()); + for (int j=0;j getAllFields() { + return reflectionClassAdapter.getAllFields(); + } + + @Deprecated + public SymbolReference solveSymbol(String name, TypeSolver typeSolver) { + for (Field field : clazz.getFields()) { + if (field.getName().equals(name)) { + return SymbolReference.solved(new ReflectionFieldDeclaration(field, typeSolver)); + } + } + return SymbolReference.unsolved(ResolvedValueDeclaration.class); + } + + @Override + public List getAncestors(boolean acceptIncompleteList) { + // we do not attempt to perform any symbol solving when analyzing ancestors in the reflection model, so we can + // simply ignore the boolean parameter here; an UnsolvedSymbolException cannot occur + return reflectionClassAdapter.getAncestors(); + } + + @Override + public Set getDeclaredMethods() { + return reflectionClassAdapter.getDeclaredMethods(); + } + + @Override + public boolean hasField(String name) { + return reflectionClassAdapter.hasField(name); + } + + @Override + public String getName() { + return clazz.getSimpleName(); + } + + @Override + public boolean isInterface() { + return true; + } + + @Override + public List getInterfacesExtended() { + List res = new ArrayList<>(); + for (Class i : clazz.getInterfaces()) { + res.add(new ReferenceTypeImpl(new ReflectionInterfaceDeclaration(i, typeSolver), typeSolver)); + } + return res; + } + + @Override + public Optional containerType() { + return reflectionClassAdapter.containerType(); + } + + @Override + public Set internalTypes() { + return Arrays.stream(this.clazz.getDeclaredClasses()) + .map(ic -> ReflectionFactory.typeDeclarationFor(ic, typeSolver)) + .collect(Collectors.toSet()); + } + + @Override + public ResolvedInterfaceDeclaration asInterface() { + return this; + } + + @Override + public boolean hasDirectlyAnnotation(String canonicalName) { + return reflectionClassAdapter.hasDirectlyAnnotation(canonicalName); + } + + @Override + public List getTypeParameters() { + return reflectionClassAdapter.getTypeParameters(); + } + + @Override + public AccessSpecifier accessSpecifier() { + return ReflectionFactory.modifiersToAccessLevel(this.clazz.getModifiers()); + } + + @Override + public List getConstructors() { + return Collections.emptyList(); + } + + @Override + public Optional toAst() { + return Optional.empty(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionMethodDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionMethodDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..e37b7901b0620a6b0de609963b9d55deddeae57d --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionMethodDeclaration.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.reflectionmodel; + +import com.github.javaparser.ast.AccessSpecifier; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedParameterDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.core.resolution.TypeVariableResolutionCapability; +import com.github.javaparser.symbolsolver.declarations.common.MethodDeclarationCommonLogic; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +public class ReflectionMethodDeclaration implements ResolvedMethodDeclaration, TypeVariableResolutionCapability { + + private Method method; + private TypeSolver typeSolver; + + public ReflectionMethodDeclaration(Method method, TypeSolver typeSolver) { + this.method = method; + if (method.isSynthetic() || method.isBridge()) { + throw new IllegalArgumentException(); + } + this.typeSolver = typeSolver; + } + + @Override + public String getName() { + return method.getName(); + } + + @Override + public boolean isField() { + return false; + } + + @Override + public boolean isParameter() { + return false; + } + + @Override + public String toString() { + return "ReflectionMethodDeclaration{" + + "method=" + method + + '}'; + } + + @Override + public boolean isType() { + return false; + } + + @Override + public ResolvedReferenceTypeDeclaration declaringType() { + if (method.getDeclaringClass().isInterface()) { + return new ReflectionInterfaceDeclaration(method.getDeclaringClass(), typeSolver); + } + if (method.getDeclaringClass().isEnum()) { + return new ReflectionEnumDeclaration(method.getDeclaringClass(), typeSolver); + } else { + return new ReflectionClassDeclaration(method.getDeclaringClass(), typeSolver); + } + } + + @Override + public ResolvedType getReturnType() { + return ReflectionFactory.typeUsageFor(method.getGenericReturnType(), typeSolver); + } + + @Override + public int getNumberOfParams() { + return method.getParameterTypes().length; + } + + @Override + public ResolvedParameterDeclaration getParam(int i) { + boolean variadic = false; + if (method.isVarArgs()) { + variadic = i == (method.getParameterCount() - 1); + } + return new ReflectionParameterDeclaration(method.getParameterTypes()[i], method.getGenericParameterTypes()[i], + typeSolver, variadic, method.getParameters()[i].getName()); + } + + @Override + public List getTypeParameters() { + return Arrays.stream(method.getTypeParameters()).map((refTp) -> new ReflectionTypeParameter(refTp, false, typeSolver)).collect(Collectors.toList()); + } + + public MethodUsage resolveTypeVariables(Context context, List parameterTypes) { + return new MethodDeclarationCommonLogic(this, typeSolver).resolveTypeVariables(context, parameterTypes); + } + + @Override + public boolean isAbstract() { + return Modifier.isAbstract(method.getModifiers()); + } + + @Override + public boolean isDefaultMethod() { + return method.isDefault(); + } + + @Override + public boolean isStatic() { + return Modifier.isStatic(method.getModifiers()); + } + + @Override + public AccessSpecifier accessSpecifier() { + return ReflectionFactory.modifiersToAccessLevel(this.method.getModifiers()); + } + + @Override + public int getNumberOfSpecifiedExceptions() { + return this.method.getExceptionTypes().length; + } + + @Override + public ResolvedType getSpecifiedException(int index) { + if (index < 0 || index >= getNumberOfSpecifiedExceptions()) { + throw new IllegalArgumentException(); + } + return ReflectionFactory.typeUsageFor(this.method.getExceptionTypes()[index], typeSolver); + } + + @Override + public Optional toAst() { + return Optional.empty(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionMethodResolutionLogic.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionMethodResolutionLogic.java new file mode 100644 index 0000000000000000000000000000000000000000..dbdee4f30a766d1b9815fb69d5559e7f34a44b71 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionMethodResolutionLogic.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.reflectionmodel; + +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.resolution.types.ResolvedTypeVariable; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.symbolsolver.resolution.MethodResolutionLogic; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +class ReflectionMethodResolutionLogic { + + static SymbolReference solveMethod(String name, List parameterTypes, boolean staticOnly, + TypeSolver typeSolver, ResolvedReferenceTypeDeclaration scopeType, + Class clazz){ + List methods = new ArrayList<>(); + Predicate staticOnlyCheck = m -> !staticOnly || (staticOnly && Modifier.isStatic(m.getModifiers())); + for (Method method : clazz.getMethods()) { + if (method.isBridge() || method.isSynthetic() || !method.getName().equals(name)|| !staticOnlyCheck.test(method)) continue; + ResolvedMethodDeclaration methodDeclaration = new ReflectionMethodDeclaration(method, typeSolver); + methods.add(methodDeclaration); + } + + for (ResolvedReferenceType ancestor : scopeType.getAncestors()) { + ancestor.getTypeDeclaration().ifPresent(ancestorTypeDeclaration -> { + SymbolReference ref = MethodResolutionLogic.solveMethodInType(ancestorTypeDeclaration, name, parameterTypes, staticOnly); + if (ref.isSolved()) { + methods.add(ref.getCorrespondingDeclaration()); + } + }); + } + + if (scopeType.getAncestors().isEmpty()){ + ReferenceTypeImpl objectClass = new ReferenceTypeImpl(new ReflectionClassDeclaration(Object.class, typeSolver), typeSolver); + objectClass.getTypeDeclaration().ifPresent(objectTypeDeclaration -> { + SymbolReference ref = MethodResolutionLogic.solveMethodInType(objectTypeDeclaration, name, parameterTypes, staticOnly); + if (ref.isSolved()) { + methods.add(ref.getCorrespondingDeclaration()); + } + }); + } + return MethodResolutionLogic.findMostApplicable(methods, name, parameterTypes, typeSolver); + } + + static Optional solveMethodAsUsage(String name, List argumentsTypes, TypeSolver typeSolver, + Context invokationContext, List typeParameterValues, + ResolvedReferenceTypeDeclaration scopeType, Class clazz) { + if (typeParameterValues.size() != scopeType.getTypeParameters().size()) { + // if it is zero we are going to ignore them + if (!scopeType.getTypeParameters().isEmpty()) { + // Parameters not specified, so default to Object + typeParameterValues = new ArrayList<>(); + for (int i = 0; i < scopeType.getTypeParameters().size(); i++) { + typeParameterValues.add(new ReferenceTypeImpl(new ReflectionClassDeclaration(Object.class, typeSolver), typeSolver)); + } + } + } + List methods = new ArrayList<>(); + for (Method method : clazz.getMethods()) { + if (method.getName().equals(name) && !method.isBridge() && !method.isSynthetic()) { + ResolvedMethodDeclaration methodDeclaration = new ReflectionMethodDeclaration(method, typeSolver); + MethodUsage methodUsage = replaceParams(typeParameterValues, scopeType, methodDeclaration); + methods.add(methodUsage); + } + + } + + for(ResolvedReferenceType ancestor : scopeType.getAncestors()){ + if(ancestor.getTypeDeclaration().isPresent()) { + ResolvedReferenceTypeDeclaration ancestorTypeDeclaration = ancestor.getTypeDeclaration().get(); + SymbolReference ref = MethodResolutionLogic.solveMethodInType(ancestorTypeDeclaration, name, argumentsTypes); + if (ref.isSolved()){ + ResolvedMethodDeclaration correspondingDeclaration = ref.getCorrespondingDeclaration(); + MethodUsage methodUsage = replaceParams(typeParameterValues, ancestorTypeDeclaration, correspondingDeclaration); + methods.add(methodUsage); + } + } + } + + if (scopeType.getAncestors().isEmpty()) { + Optional optionalObjectClass = new ReferenceTypeImpl(new ReflectionClassDeclaration(Object.class, typeSolver), typeSolver).getTypeDeclaration(); + if (optionalObjectClass.isPresent()) { + SymbolReference ref = MethodResolutionLogic.solveMethodInType(optionalObjectClass.get(), name, argumentsTypes); + if (ref.isSolved()) { + MethodUsage usage = replaceParams(typeParameterValues, optionalObjectClass.get(), ref.getCorrespondingDeclaration()); + methods.add(usage); + } + } + } + + final List finalTypeParameterValues = typeParameterValues; + argumentsTypes = argumentsTypes.stream().map((pt) -> { + int i = 0; + for (ResolvedTypeParameterDeclaration tp : scopeType.getTypeParameters()) { + pt = pt.replaceTypeVariables(tp, finalTypeParameterValues.get(i)); + i++; + } + return pt; + }).collect(Collectors.toList()); + return MethodResolutionLogic.findMostApplicableUsage(methods, name, argumentsTypes, typeSolver); + } + + private static MethodUsage replaceParams(List typeParameterValues, ResolvedReferenceTypeDeclaration typeParametrizable, ResolvedMethodDeclaration methodDeclaration) { + MethodUsage methodUsage = new MethodUsage(methodDeclaration); + int i = 0; + + // Only replace if we have enough values provided + if (typeParameterValues.size() == typeParametrizable.getTypeParameters().size()){ + for (ResolvedTypeParameterDeclaration tp : typeParametrizable.getTypeParameters()) { + methodUsage = methodUsage.replaceTypeParameter(tp, typeParameterValues.get(i)); + i++; + } + } + + for (ResolvedTypeParameterDeclaration methodTypeParameter : methodDeclaration.getTypeParameters()) { + methodUsage = methodUsage.replaceTypeParameter(methodTypeParameter, new ResolvedTypeVariable(methodTypeParameter)); + } + + return methodUsage; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionParameterDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionParameterDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..729d240a607dd12b9a8d7bd66ffa6298b0d27e0b --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionParameterDeclaration.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.reflectionmodel; + +import com.github.javaparser.resolution.declarations.ResolvedParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.Objects; + +/** + * @author Federico Tomassetti + */ +public class ReflectionParameterDeclaration implements ResolvedParameterDeclaration { + private Class type; + private java.lang.reflect.Type genericType; + private TypeSolver typeSolver; + private boolean variadic; + private String name; + + /** + * + * @param type + * @param genericType + * @param typeSolver + * @param variadic + * @param name can potentially be null + */ + public ReflectionParameterDeclaration(Class type, java.lang.reflect.Type genericType, TypeSolver typeSolver, + boolean variadic, String name) { + this.type = type; + this.genericType = genericType; + this.typeSolver = typeSolver; + this.variadic = variadic; + this.name = name; + } + + /** + * + * @return the name, which can be potentially null + */ + @Override + public String getName() { + return name; + } + + @Override + public boolean hasName() { + return name != null; + } + + @Override + public String toString() { + return "ReflectionParameterDeclaration{" + + "type=" + type + + ", name=" + name + + '}'; + } + + @Override + public boolean isField() { + return false; + } + + @Override + public boolean isParameter() { + return true; + } + + @Override + public boolean isVariadic() { + return variadic; + } + + @Override + public boolean isType() { + return false; + } + + @Override + public ResolvedType getType() { + return ReflectionFactory.typeUsageFor(genericType, typeSolver); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ReflectionParameterDeclaration that = (ReflectionParameterDeclaration) o; + return variadic == that.variadic && + Objects.equals(type, that.type) && + Objects.equals(genericType, that.genericType) && + Objects.equals(typeSolver, that.typeSolver) && + Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(type, genericType, typeSolver, variadic, name); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionPatternDeclaration.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionPatternDeclaration.java new file mode 100644 index 0000000000000000000000000000000000000000..96caa0af8cee2e064b10254458750e77eade91f7 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionPatternDeclaration.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.reflectionmodel; + +import com.github.javaparser.resolution.declarations.ResolvedPatternDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +/** + * WARNING: Implemented fairly blindly. Unsure if required or even appropriate. Use with extreme caution. + * + * @author Roger Howell + */ +public class ReflectionPatternDeclaration implements ResolvedPatternDeclaration { + + private Class type; + private TypeSolver typeSolver; + private String name; + + /** + * @param type + * @param typeSolver + * @param name can potentially be null + */ + public ReflectionPatternDeclaration(Class type, TypeSolver typeSolver, String name) { + this.type = type; + this.typeSolver = typeSolver; + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean hasName() { + return name != null; + } + + @Override + public boolean isField() { + return false; + } + + @Override + public boolean isParameter() { + return false; + } + + @Override + public boolean isPattern() { + return true; + } + + @Override + public boolean isType() { + return false; + } + + @Override + public ResolvedType getType() { + return ReflectionFactory.typeUsageFor(type, typeSolver); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionTypeParameter.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionTypeParameter.java new file mode 100644 index 0000000000000000000000000000000000000000..76b37230ffbf4182b82c89401cbfb83610ae070e --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/ReflectionTypeParameter.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.reflectionmodel; + +import com.github.javaparser.resolution.declarations.ResolvedMethodLikeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParametrizable; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.lang.reflect.Constructor; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.Method; +import java.lang.reflect.TypeVariable; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +public class ReflectionTypeParameter implements ResolvedTypeParameterDeclaration { + + private TypeVariable typeVariable; + private TypeSolver typeSolver; + private ResolvedTypeParametrizable container; + + public ReflectionTypeParameter(TypeVariable typeVariable, boolean declaredOnClass, TypeSolver typeSolver) { + GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration(); + if (genericDeclaration instanceof Class) { + container = ReflectionFactory.typeDeclarationFor((Class) genericDeclaration, typeSolver); + } else if (genericDeclaration instanceof Method) { + container = new ReflectionMethodDeclaration((Method) genericDeclaration, typeSolver); + } else if (genericDeclaration instanceof Constructor) { + container = new ReflectionConstructorDeclaration((Constructor) genericDeclaration, typeSolver); + } + this.typeVariable = typeVariable; + this.typeSolver = typeSolver; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ResolvedTypeParameterDeclaration)) return false; + + ResolvedTypeParameterDeclaration that = (ResolvedTypeParameterDeclaration) o; + + if (!getQualifiedName().equals(that.getQualifiedName())) { + return false; + } + if (declaredOnType() != that.declaredOnType()) { + return false; + } + if (declaredOnMethod() != that.declaredOnMethod()) { + return false; + } + // TODO check bounds + return true; + } + + @Override + public int hashCode() { + int result = typeVariable.hashCode(); + result = 31 * result + container.hashCode(); + return result; + } + + @Override + public String getName() { + return typeVariable.getName(); + } + + @Override + public String getContainerQualifiedName() { + if (container instanceof ResolvedReferenceTypeDeclaration) { + return ((ResolvedReferenceTypeDeclaration) container).getQualifiedName(); + } else { + return ((ResolvedMethodLikeDeclaration) container).getQualifiedSignature(); + } + } + + @Override + public String getContainerId() { + if (container instanceof ResolvedReferenceTypeDeclaration) { + return ((ResolvedReferenceTypeDeclaration) container).getId(); + } else { + return ((ResolvedMethodLikeDeclaration) container).getQualifiedSignature(); + } + } + + @Override + public ResolvedTypeParametrizable getContainer() { + return this.container; + } + + @Override + public List getBounds() { + return Arrays.stream(typeVariable.getBounds()).map((refB) -> Bound.extendsBound(ReflectionFactory.typeUsageFor(refB, typeSolver))).collect(Collectors.toList()); + } + + @Override + public String toString() { + return "ReflectionTypeParameter{" + + "typeVariable=" + typeVariable + + '}'; + } + + @Override + public Optional containerType() { + if (container instanceof ResolvedReferenceTypeDeclaration) { + return Optional.of((ResolvedReferenceTypeDeclaration) container); + } + return Optional.empty(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/comparators/ClassComparator.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/comparators/ClassComparator.java new file mode 100644 index 0000000000000000000000000000000000000000..fcd19ff81ed81fea40b2c72e09304667c5499f8a --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/comparators/ClassComparator.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.reflectionmodel.comparators; + +import java.util.Comparator; + +/** + * @author Federico Tomassetti + */ +public class ClassComparator implements Comparator> { + + @Override + public int compare(Class o1, Class o2) { + int subCompare; + subCompare = o1.getCanonicalName().compareTo(o2.getCanonicalName()); + if (subCompare != 0) return subCompare; + subCompare = Boolean.compare(o1.isAnnotation(), o2.isAnnotation()); + if (subCompare != 0) return subCompare; + subCompare = Boolean.compare(o1.isArray(), o2.isArray()); + if (subCompare != 0) return subCompare; + subCompare = Boolean.compare(o1.isEnum(), o2.isEnum()); + if (subCompare != 0) return subCompare; + subCompare = Boolean.compare(o1.isInterface(), o2.isInterface()); + if (subCompare != 0) return subCompare; + return 0; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/comparators/MethodComparator.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/comparators/MethodComparator.java new file mode 100644 index 0000000000000000000000000000000000000000..2544c9bbec6daf3740e8ed52c01793d478e46541 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/comparators/MethodComparator.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.reflectionmodel.comparators; + +import java.lang.reflect.Method; +import java.util.Comparator; + +/** + * @author Federico Tomassetti + */ +public class MethodComparator implements Comparator { + + @Override + public int compare(Method o1, Method o2) { + int compareName = o1.getName().compareTo(o2.getName()); + if (compareName != 0) return compareName; + int compareNParams = o1.getParameterCount() - o2.getParameterCount(); + if (compareNParams != 0) return compareNParams; + for (int i = 0; i < o1.getParameterCount(); i++) { + int compareParam = new ParameterComparator().compare(o1.getParameters()[i], o2.getParameters()[i]); + if (compareParam != 0) return compareParam; + } + int compareResult = new ClassComparator().compare(o1.getReturnType(), o2.getReturnType()); + if (compareResult != 0) return compareResult; + return 0; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/comparators/ParameterComparator.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/comparators/ParameterComparator.java new file mode 100644 index 0000000000000000000000000000000000000000..16e18897a5829526c3a6873e7d0accf14363a31f --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/comparators/ParameterComparator.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.reflectionmodel.comparators; + +import java.lang.reflect.Parameter; +import java.util.Comparator; + +/** + * @author Federico Tomassetti + */ +public class ParameterComparator implements Comparator { + + @Override + public int compare(Parameter o1, Parameter o2) { + int compareName = o1.getName().compareTo(o2.getName()); + if (compareName != 0) return compareName; + int compareType = new ClassComparator().compare(o1.getType(), o2.getType()); + if (compareType != 0) return compareType; + return 0; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/package-info.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..fc75fbc5a547035c35569d6c054e35fc883c3213 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/reflectionmodel/package-info.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +/** + * Implementation of model based on reflection. + */ +package com.github.javaparser.symbolsolver.reflectionmodel; diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/ConstructorResolutionLogic.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/ConstructorResolutionLogic.java new file mode 100644 index 0000000000000000000000000000000000000000..84848a62b66a3bcf3a7486f1128a42b7d29f7fee --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/ConstructorResolutionLogic.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution; + +import com.github.javaparser.resolution.MethodAmbiguityException; +import com.github.javaparser.resolution.declarations.ResolvedConstructorDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedArrayType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author Fred Lefévère-Laoide + */ +public class ConstructorResolutionLogic { + + private static List groupVariadicParamValues(List argumentsTypes, int startVariadic, + ResolvedType variadicType) { + List res = new ArrayList<>(argumentsTypes.subList(0, startVariadic)); + List variadicValues = argumentsTypes.subList(startVariadic, argumentsTypes.size()); + if (variadicValues.isEmpty()) { + // TODO if there are no variadic values we should default to the bound of the formal type + res.add(variadicType); + } else { + ResolvedType componentType = findCommonType(variadicValues); + res.add(new ResolvedArrayType(componentType)); + } + return res; + } + + private static ResolvedType findCommonType(List variadicValues) { + if (variadicValues.isEmpty()) { + throw new IllegalArgumentException(); + } + // TODO implement this decently + return variadicValues.get(0); + } + + public static boolean isApplicable(ResolvedConstructorDeclaration constructor, List argumentsTypes, + TypeSolver typeSolver) { + return isApplicable(constructor, argumentsTypes, typeSolver, false); + } + + private static boolean isApplicable(ResolvedConstructorDeclaration constructor, List argumentsTypes, + TypeSolver typeSolver, boolean withWildcardTolerance) { + if (constructor.hasVariadicParameter()) { + int pos = constructor.getNumberOfParams() - 1; + if (constructor.getNumberOfParams() == argumentsTypes.size()) { + // check if the last value is directly assignable as an array + ResolvedType expectedType = constructor.getLastParam().getType(); + ResolvedType actualType = argumentsTypes.get(pos); + if (!expectedType.isAssignableBy(actualType)) { + for (ResolvedTypeParameterDeclaration tp : constructor.getTypeParameters()) { + expectedType = MethodResolutionLogic.replaceTypeParam(expectedType, tp, typeSolver); + } + if (!expectedType.isAssignableBy(actualType)) { + if (actualType.isArray() + && expectedType.isAssignableBy(actualType.asArrayType().getComponentType())) { + argumentsTypes.set(pos, actualType.asArrayType().getComponentType()); + } else { + argumentsTypes = groupVariadicParamValues(argumentsTypes, pos, + constructor.getLastParam().getType()); + } + } + } // else it is already assignable, nothing to do + } else { + if (pos > argumentsTypes.size()) { + return false; + } + argumentsTypes = + groupVariadicParamValues(argumentsTypes, pos, constructor.getLastParam().getType()); + } + } + + if (constructor.getNumberOfParams() != argumentsTypes.size()) { + return false; + } + Map matchedParameters = new HashMap<>(); + boolean needForWildCardTolerance = false; + for (int i = 0; i < constructor.getNumberOfParams(); i++) { + ResolvedType expectedType = constructor.getParam(i).getType(); + ResolvedType actualType = argumentsTypes.get(i); + if ((expectedType.isTypeVariable() && !(expectedType.isWildcard())) + && expectedType.asTypeParameter().declaredOnMethod()) { + matchedParameters.put(expectedType.asTypeParameter().getName(), actualType); + continue; + } + boolean isAssignableWithoutSubstitution = + expectedType.isAssignableBy(actualType) || (constructor.getParam(i).isVariadic() + && new ResolvedArrayType(expectedType).isAssignableBy(actualType)); + if (!isAssignableWithoutSubstitution && expectedType.isReferenceType() + && actualType.isReferenceType()) { + isAssignableWithoutSubstitution = MethodResolutionLogic.isAssignableMatchTypeParameters( + expectedType.asReferenceType(), actualType.asReferenceType(), matchedParameters); + } + if (!isAssignableWithoutSubstitution) { + for (ResolvedTypeParameterDeclaration tp : constructor.getTypeParameters()) { + expectedType = MethodResolutionLogic.replaceTypeParam(expectedType, tp, typeSolver); + } + for (ResolvedTypeParameterDeclaration tp : constructor.declaringType().getTypeParameters()) { + expectedType = MethodResolutionLogic.replaceTypeParam(expectedType, tp, typeSolver); + } + + if (!expectedType.isAssignableBy(actualType)) { + if (actualType.isWildcard() && withWildcardTolerance && !expectedType.isPrimitive()) { + needForWildCardTolerance = true; + continue; + } + if (constructor.hasVariadicParameter() && i == constructor.getNumberOfParams() - 1) { + if (new ResolvedArrayType(expectedType).isAssignableBy(actualType)) { + continue; + } + } + return false; + } + } + } + return !withWildcardTolerance || needForWildCardTolerance; + } + + /** + * @param constructors we expect the methods to be ordered such that inherited methods are later in the list + * @param argumentsTypes + * @param typeSolver + * @return + */ + public static SymbolReference findMostApplicable( + List constructors, List argumentsTypes, TypeSolver typeSolver) { + SymbolReference res = + findMostApplicable(constructors, argumentsTypes, typeSolver, false); + if (res.isSolved()) { + return res; + } + return findMostApplicable(constructors, argumentsTypes, typeSolver, true); + } + + public static SymbolReference findMostApplicable( + List constructors, List argumentsTypes, TypeSolver typeSolver, boolean wildcardTolerance) { + List applicableConstructors = constructors.stream().filter((m) -> isApplicable(m, argumentsTypes, typeSolver, wildcardTolerance)).collect(Collectors.toList()); + if (applicableConstructors.isEmpty()) { + return SymbolReference.unsolved(ResolvedConstructorDeclaration.class); + } + if (applicableConstructors.size() == 1) { + return SymbolReference.solved(applicableConstructors.get(0)); + } else { + ResolvedConstructorDeclaration winningCandidate = applicableConstructors.get(0); + ResolvedConstructorDeclaration other = null; + boolean possibleAmbiguity = false; + for (int i = 1; i < applicableConstructors.size(); i++) { + other = applicableConstructors.get(i); + if (isMoreSpecific(winningCandidate, other, typeSolver)) { + possibleAmbiguity = false; + } else if (isMoreSpecific(other, winningCandidate, typeSolver)) { + possibleAmbiguity = false; + winningCandidate = other; + } else { + if (winningCandidate.declaringType().getQualifiedName() + .equals(other.declaringType().getQualifiedName())) { + possibleAmbiguity = true; + } else { + // we expect the methods to be ordered such that inherited methods are later in the list + } + } + if (possibleAmbiguity) { + // pick the first exact match if it exists + if (!MethodResolutionLogic.isExactMatch(winningCandidate, argumentsTypes)) { + if (MethodResolutionLogic.isExactMatch(other, argumentsTypes)) { + winningCandidate = other; + } else { + throw new MethodAmbiguityException("Ambiguous constructor call: cannot find a most applicable constructor: " + winningCandidate + ", " + other); + } + } + } + } + + return SymbolReference.solved(winningCandidate); + } + } + + private static boolean isMoreSpecific(ResolvedConstructorDeclaration constructorA, + ResolvedConstructorDeclaration constructorB, TypeSolver typeSolver) { + boolean oneMoreSpecificFound = false; + if (constructorA.getNumberOfParams() < constructorB.getNumberOfParams()) { + return true; + } + if (constructorA.getNumberOfParams() > constructorB.getNumberOfParams()) { + return false; + } + for (int i = 0; i < constructorA.getNumberOfParams(); i++) { + ResolvedType tdA = constructorA.getParam(i).getType(); + ResolvedType tdB = constructorB.getParam(i).getType(); + // B is more specific + if (tdB.isAssignableBy(tdA) && !tdA.isAssignableBy(tdB)) { + oneMoreSpecificFound = true; + } + // A is more specific + if (tdA.isAssignableBy(tdB) && !tdB.isAssignableBy(tdA)) { + return false; + } + // if it matches a variadic and a not variadic I pick the not variadic + if (!tdA.isArray() && tdB.isArray()) { + return true; + } + // FIXME + if (i == (constructorA.getNumberOfParams() - 1) && tdA.arrayLevel() > tdB.arrayLevel()) { + return true; + } + } + return oneMoreSpecificFound; + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/MethodResolutionLogic.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/MethodResolutionLogic.java new file mode 100644 index 0000000000000000000000000000000000000000..a23894877d38bdcbe0e3b1af9a3139baf2ce5356 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/MethodResolutionLogic.java @@ -0,0 +1,868 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution; + +import com.github.javaparser.resolution.MethodAmbiguityException; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.*; +import com.github.javaparser.symbolsolver.logic.MethodResolutionCapability; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * @author Federico Tomassetti + */ +public class MethodResolutionLogic { + + private static String JAVA_LANG_OBJECT = Object.class.getCanonicalName(); + + private static List groupVariadicParamValues(List argumentsTypes, int startVariadic, ResolvedType variadicType) { + List res = new ArrayList<>(argumentsTypes.subList(0, startVariadic)); + List variadicValues = argumentsTypes.subList(startVariadic, argumentsTypes.size()); + if (variadicValues.isEmpty()) { + // TODO if there are no variadic values we should default to the bound of the formal type + res.add(variadicType); + } else { + ResolvedType componentType = findCommonType(variadicValues); + res.add(new ResolvedArrayType(componentType)); + } + return res; + } + + private static ResolvedType findCommonType(List variadicValues) { + if (variadicValues.isEmpty()) { + throw new IllegalArgumentException(); + } + // TODO implement this decently + return variadicValues.get(0); + } + + public static boolean isApplicable(ResolvedMethodDeclaration method, String name, List argumentsTypes, TypeSolver typeSolver) { + return isApplicable(method, name, argumentsTypes, typeSolver, false); + } + + /** + * Note the specific naming here -- parameters are part of the method declaration, + * while arguments are the values passed when calling a method. + * Note that "needle" refers to that value being used as a search/query term to match against. + * + * @return true, if the given ResolvedMethodDeclaration matches the given name/types (normally obtained from a MethodUsage) + * + * @see {@link MethodResolutionLogic#isApplicable(MethodUsage, String, List, TypeSolver)} + */ + private static boolean isApplicable(ResolvedMethodDeclaration methodDeclaration, String needleName, List needleArgumentTypes, TypeSolver typeSolver, boolean withWildcardTolerance) { + if (!methodDeclaration.getName().equals(needleName)) { + return false; + } + + // The index of the final method parameter (on the method declaration). + int countOfMethodParametersDeclared = methodDeclaration.getNumberOfParams(); + int lastMethodParameterIndex = Math.max(0, countOfMethodParametersDeclared - 1); + + // The index of the final argument passed (on the method usage). + int countOfNeedleArgumentsPassed = needleArgumentTypes.size(); + int lastNeedleArgumentIndex = Math.max(0, countOfNeedleArgumentsPassed - 1); + + boolean methodIsDeclaredWithVariadicParameter = methodDeclaration.hasVariadicParameter(); + + if (!methodIsDeclaredWithVariadicParameter && (countOfNeedleArgumentsPassed != countOfMethodParametersDeclared)) { + // If it is not variadic, and the number of parameters/arguments are unequal -- this is not a match. + return false; + } + + if (methodIsDeclaredWithVariadicParameter) { + // If the method declaration we're considering has a variadic parameter, + // attempt to convert the given list of arguments to fit this pattern + // e.g. foo(String s, String... s2) {} --- consider the first argument, then group the remainder as an array + + ResolvedType expectedVariadicParameterType = methodDeclaration.getLastParam().getType(); + for (ResolvedTypeParameterDeclaration tp : methodDeclaration.getTypeParameters()) { + expectedVariadicParameterType = replaceTypeParam(expectedVariadicParameterType, tp, typeSolver); + } + + if(countOfNeedleArgumentsPassed <= (countOfMethodParametersDeclared - 2)) { + // If it is variadic, and the number of arguments are short by **two or more** -- this is not a match. + // Note that omitting the variadic parameter is treated as an empty array + // (thus being short of only 1 argument is fine, but being short of 2 or more is not). + return false; + } else if (countOfNeedleArgumentsPassed == (countOfMethodParametersDeclared - 1)) { + // If it is variadic and we are short of **exactly one** parameter, this is a match. + // Note that omitting the variadic parameter is treated as an empty array + // (thus being short of only 1 argument is fine, but being short of 2 or more is not). + + // thus group the "empty" value into an empty array... + needleArgumentTypes = groupVariadicParamValues(needleArgumentTypes, lastMethodParameterIndex, methodDeclaration.getLastParam().getType()); + } else if (countOfNeedleArgumentsPassed > countOfMethodParametersDeclared) { + // If it is variadic, and we have an "excess" of arguments, group the "trailing" arguments into an array. + // Confirm all of these grouped "trailing" arguments have the required type -- if not, this is not a valid type. (Maybe this is also done later..?) + for(int variadicArgumentIndex = countOfMethodParametersDeclared; variadicArgumentIndex < countOfNeedleArgumentsPassed; variadicArgumentIndex++) { + ResolvedType currentArgumentType = needleArgumentTypes.get(variadicArgumentIndex); + boolean argumentIsAssignableToVariadicComponentType = expectedVariadicParameterType.asArrayType().getComponentType().isAssignableBy(currentArgumentType); + if(!argumentIsAssignableToVariadicComponentType) { + // If any of the arguments are not assignable to the expected variadic type, this is not a match. + return false; + } + } + needleArgumentTypes = groupVariadicParamValues(needleArgumentTypes, lastMethodParameterIndex, methodDeclaration.getLastParam().getType()); + } else if (countOfNeedleArgumentsPassed == countOfMethodParametersDeclared) { + ResolvedType actualArgumentType = needleArgumentTypes.get(lastNeedleArgumentIndex); + boolean finalArgumentIsArray = actualArgumentType.isArray() && expectedVariadicParameterType.isAssignableBy(actualArgumentType.asArrayType().getComponentType()); + if(finalArgumentIsArray) { + // Treat as an array of values -- in which case the expected parameter type is the common type of this array. + // no need to do anything +// expectedVariadicParameterType = actualArgumentType.asArrayType().getComponentType(); + } else { + // Treat as a single value -- in which case, the expected parameter type is the same as the single value. + needleArgumentTypes = groupVariadicParamValues(needleArgumentTypes, lastMethodParameterIndex, methodDeclaration.getLastParam().getType()); + } + } else { + // Should be unreachable. + } + } + + + // The index of the final argument passed (on the method usage). + int countOfNeedleArgumentsPassedAfterGrouping = needleArgumentTypes.size(); + int lastNeedleArgumentIndexAfterGrouping = Math.max(0, countOfNeedleArgumentsPassed - 1); + + // If variadic parameters are possible then they will have been "grouped" into a single argument. + // At this point, therefore, the number of arguments must be equal -- if they're not, then there is no match. + if (countOfNeedleArgumentsPassedAfterGrouping != countOfMethodParametersDeclared) { + return false; + } + + + Map matchedParameters = new HashMap<>(); + boolean needForWildCardTolerance = false; + for (int i = 0; i < countOfMethodParametersDeclared; i++) { + ResolvedType expectedDeclaredType = methodDeclaration.getParam(i).getType(); + ResolvedType actualArgumentType = needleArgumentTypes.get(i); + if ((expectedDeclaredType.isTypeVariable() && !(expectedDeclaredType.isWildcard())) && expectedDeclaredType.asTypeParameter().declaredOnMethod()) { + matchedParameters.put(expectedDeclaredType.asTypeParameter().getName(), actualArgumentType); + continue; + } + + boolean isAssignableWithoutSubstitution = expectedDeclaredType.isAssignableBy(actualArgumentType) || + (methodDeclaration.getParam(i).isVariadic() && new ResolvedArrayType(expectedDeclaredType).isAssignableBy(actualArgumentType)); + + if (!isAssignableWithoutSubstitution && expectedDeclaredType.isReferenceType() && actualArgumentType.isReferenceType()) { + isAssignableWithoutSubstitution = isAssignableMatchTypeParameters( + expectedDeclaredType.asReferenceType(), + actualArgumentType.asReferenceType(), + matchedParameters); + } + if (!isAssignableWithoutSubstitution) { + List typeParameters = methodDeclaration.getTypeParameters(); + typeParameters.addAll(methodDeclaration.declaringType().getTypeParameters()); + for (ResolvedTypeParameterDeclaration tp : typeParameters) { + expectedDeclaredType = replaceTypeParam(expectedDeclaredType, tp, typeSolver); + } + + if (!expectedDeclaredType.isAssignableBy(actualArgumentType)) { + if (actualArgumentType.isWildcard() && withWildcardTolerance && !expectedDeclaredType.isPrimitive()) { + needForWildCardTolerance = true; + continue; + } + // if the expected is java.lang.Math.max(double,double) and the type parameters are defined with constrain + // for example LambdaConstraintType{bound=TypeVariable {ReflectionTypeParameter{typeVariable=T}}}, LambdaConstraintType{bound=TypeVariable {ReflectionTypeParameter{typeVariable=U}}} + // we want to keep this method for future resolution + if (actualArgumentType.isConstraint() && withWildcardTolerance && expectedDeclaredType.isPrimitive()) { + needForWildCardTolerance = true; + continue; + } + if (methodIsDeclaredWithVariadicParameter && i == countOfMethodParametersDeclared - 1) { + if (new ResolvedArrayType(expectedDeclaredType).isAssignableBy(actualArgumentType)) { + continue; + } + } + return false; + } + } + } + return !withWildcardTolerance || needForWildCardTolerance; + } + + public static boolean isAssignableMatchTypeParameters(ResolvedType expected, ResolvedType actual, + Map matchedParameters) { + if (expected.isReferenceType() && actual.isReferenceType()) { + return isAssignableMatchTypeParameters(expected.asReferenceType(), actual.asReferenceType(), matchedParameters); + } else if (expected.isTypeVariable()) { + matchedParameters.put(expected.asTypeParameter().getName(), actual); + return true; + } else if (expected.isArray()) { + matchedParameters.put(expected.asArrayType().getComponentType().toString(), actual); + return true; + } else { + throw new UnsupportedOperationException(expected.getClass().getCanonicalName() + " " + actual.getClass().getCanonicalName()); + } + } + + public static boolean isAssignableMatchTypeParameters(ResolvedReferenceType expected, ResolvedReferenceType actual, + Map matchedParameters) { + if (actual.getQualifiedName().equals(expected.getQualifiedName())) { + return isAssignableMatchTypeParametersMatchingQName(expected, actual, matchedParameters); + } else { + List ancestors = actual.getAllAncestors(); + for (ResolvedReferenceType ancestor : ancestors) { + if (isAssignableMatchTypeParametersMatchingQName(expected, ancestor, matchedParameters)) { + return true; + } + } + } + return false; + } + + private static boolean isAssignableMatchTypeParametersMatchingQName(ResolvedReferenceType expected, ResolvedReferenceType actual, + Map matchedParameters) { + + if (!expected.getQualifiedName().equals(actual.getQualifiedName())) { + return false; + } + if (expected.typeParametersValues().size() != actual.typeParametersValues().size()) { + throw new UnsupportedOperationException(); + //return true; + } + for (int i = 0; i < expected.typeParametersValues().size(); i++) { + ResolvedType expectedParam = expected.typeParametersValues().get(i); + ResolvedType actualParam = actual.typeParametersValues().get(i); + + // In the case of nested parameterizations eg. List <-> List + // we should peel off one layer and ensure R <-> Integer + if (expectedParam.isReferenceType() && actualParam.isReferenceType()) { + ResolvedReferenceType r1 = expectedParam.asReferenceType(); + ResolvedReferenceType r2 = actualParam.asReferenceType(); + // we can have r1=A and r2=A.B (with B extends A and B is an inner class of A) + // in this case we want to verify expected parameter from the actual parameter ancestors + return isAssignableMatchTypeParameters(r1, r2, matchedParameters); + } + + if (expectedParam.isTypeVariable()) { + String expectedParamName = expectedParam.asTypeParameter().getName(); + if (!actualParam.isTypeVariable() || !actualParam.asTypeParameter().getName().equals(expectedParamName)) { + return matchTypeVariable(expectedParam.asTypeVariable(), actualParam, matchedParameters); + } + } else if (expectedParam.isReferenceType()) { + if (actualParam.isTypeVariable()) { + return matchTypeVariable(actualParam.asTypeVariable(), expectedParam, matchedParameters); + } else if (!expectedParam.equals(actualParam)) { + return false; + } + } else if (expectedParam.isWildcard()) { + if (expectedParam.asWildcard().isExtends()) { + return isAssignableMatchTypeParameters(expectedParam.asWildcard().getBoundedType(), actual, matchedParameters); + } + // TODO verify super bound + return true; + } else { + throw new UnsupportedOperationException(expectedParam.describe()); + } + } + return true; + } + + private static boolean matchTypeVariable(ResolvedTypeVariable typeVariable, ResolvedType type, Map matchedParameters) { + String typeParameterName = typeVariable.asTypeParameter().getName(); + if (matchedParameters.containsKey(typeParameterName)) { + ResolvedType matchedParameter = matchedParameters.get(typeParameterName); + if (matchedParameter.isAssignableBy(type)) { + return true; + } else if (type.isAssignableBy(matchedParameter)) { + // update matchedParameters to contain the more general type + matchedParameters.put(typeParameterName, type); + return true; + } + return false; + } else { + matchedParameters.put(typeParameterName, type); + } + return true; + } + + public static ResolvedType replaceTypeParam(ResolvedType type, ResolvedTypeParameterDeclaration tp, TypeSolver typeSolver) { + if (type.isTypeVariable() || type.isWildcard()) { + if (type.describe().equals(tp.getName())) { + List bounds = tp.getBounds(); + if (bounds.size() > 1) { + throw new UnsupportedOperationException(); + } else if (bounds.size() == 1) { + return bounds.get(0).getType(); + } else { + return new ReferenceTypeImpl(typeSolver.solveType(JAVA_LANG_OBJECT), typeSolver); + } + } + return type; + } else if (type.isPrimitive()) { + return type; + } else if (type.isArray()) { + return new ResolvedArrayType(replaceTypeParam(type.asArrayType().getComponentType(), tp, typeSolver)); + } else if (type.isReferenceType()) { + ResolvedReferenceType result = type.asReferenceType(); + result = result.transformTypeParameters(typeParam -> replaceTypeParam(typeParam, tp, typeSolver)).asReferenceType(); + return result; + } else { + throw new UnsupportedOperationException("Replacing " + type + ", param " + tp + " with " + type.getClass().getCanonicalName()); + } + } + + /** + * Note the specific naming here -- parameters are part of the method declaration, + * while arguments are the values passed when calling a method. + * Note that "needle" refers to that value being used as a search/query term to match against. + * + * @return true, if the given MethodUsage matches the given name/types (normally obtained from a ResolvedMethodDeclaration) + * + * @see {@link MethodResolutionLogic#isApplicable(ResolvedMethodDeclaration, String, List, TypeSolver)} } + * @see {@link MethodResolutionLogic#isApplicable(ResolvedMethodDeclaration, String, List, TypeSolver, boolean)} + */ + public static boolean isApplicable(MethodUsage methodUsage, String needleName, List needleParameterTypes, TypeSolver typeSolver) { + if (!methodUsage.getName().equals(needleName)) { + return false; + } + + // The index of the final method parameter (on the method declaration). + int countOfMethodUsageArgumentsPassed = methodUsage.getNoParams(); + int lastMethodUsageArgumentIndex = Math.max(0, countOfMethodUsageArgumentsPassed - 1); + + // The index of the final argument passed (on the method usage). + int needleParameterCount = needleParameterTypes.size(); + int lastNeedleParameterIndex = Math.max(0, needleParameterCount - 1); + + // TODO: Does the method usage have a declaration at this point..? + boolean methodIsDeclaredWithVariadicParameter = methodUsage.getDeclaration().hasVariadicParameter(); + + // If the counts do not match and the method is not variadic, this is not a match. + if (!methodIsDeclaredWithVariadicParameter && !(needleParameterCount == countOfMethodUsageArgumentsPassed)) { + return false; + } + + // If the counts do not match and we have provided too few arguments, this is not a match. Note that variadic parameters + // allow you to omit the vararg, which would allow a difference of one, but a difference in count of 2 or more is not a match. + if (!(needleParameterCount == countOfMethodUsageArgumentsPassed) && needleParameterCount < lastMethodUsageArgumentIndex) { + return false; + } + + // Iterate over the arguments given to the method, and compare their types against the given method's declared parameter types + for (int i = 0; i < needleParameterCount; i++) { + ResolvedType actualArgumentType = needleParameterTypes.get(i); + + ResolvedType expectedArgumentType; + boolean reachedVariadicParam = methodIsDeclaredWithVariadicParameter && i >= lastMethodUsageArgumentIndex; + if (!reachedVariadicParam) { + // Not yet reached the variadic parameters -- the expected type is just whatever is at that position. + expectedArgumentType = methodUsage.getParamType(i); + } else { + // We have reached the variadic parameters -- the expected type is the type of the last declared parameter. + expectedArgumentType = methodUsage.getParamType(lastMethodUsageArgumentIndex); + // Note that the given variadic value might be an array - if so, use the array's component type rather. + // This is only valid if ONE argument has been given to the vararg parameter. + // Example: {@code void test(String... s) {}} and {@code test(stringArray)} -- {@code String... is assignable by stringArray} + // Example: {@code void test(String[]... s) {}} and {@code test(stringArrayArray)} -- {@code String[]... is assignable by stringArrayArray} + boolean argumentIsArray = (needleParameterCount == countOfMethodUsageArgumentsPassed) && expectedArgumentType.isAssignableBy(actualArgumentType); + if (!argumentIsArray) { + // Get the component type of the declared parameter type. + expectedArgumentType = expectedArgumentType.asArrayType().getComponentType(); + } + } + + // Consider type parameters directly on the method declaration, and ALSO on the enclosing type (e.g. a class) + List typeParameters = methodUsage.getDeclaration().getTypeParameters(); + typeParameters.addAll(methodUsage.declaringType().getTypeParameters()); + + ResolvedType expectedTypeWithoutSubstitutions = expectedArgumentType; + ResolvedType expectedTypeWithInference = expectedArgumentType; + Map derivedValues = new HashMap<>(); + + // For each declared parameter, infer the types that will replace generics (type parameters) + for (int j = 0; j < countOfMethodUsageArgumentsPassed; j++) { + ResolvedParameterDeclaration parameter = methodUsage.getDeclaration().getParam(j); + ResolvedType parameterType = parameter.getType(); + if (parameter.isVariadic()) { + // Don't continue if a vararg parameter is reached and there are no arguments left + if (needleParameterCount == j) { + break; + } + parameterType = parameterType.asArrayType().getComponentType(); + } + inferTypes(needleParameterTypes.get(j), parameterType, derivedValues); + } + + for (Map.Entry entry : derivedValues.entrySet()) { + ResolvedTypeParameterDeclaration tp = entry.getKey(); + expectedTypeWithInference = expectedTypeWithInference.replaceTypeVariables(tp, entry.getValue()); + } + + // Consider cases where type variables can be replaced (e.g. add(E element) vs add(String element)) + for (ResolvedTypeParameterDeclaration tp : typeParameters) { + if (tp.getBounds().isEmpty()) { + //expectedArgumentType = expectedArgumentType.replaceTypeVariables(tp.getName(), new ReferenceTypeUsageImpl(typeSolver.solveType(JAVA_LANG_OBJECT), typeSolver)); + expectedArgumentType = expectedArgumentType.replaceTypeVariables(tp, ResolvedWildcard.extendsBound(new ReferenceTypeImpl(typeSolver.solveType(JAVA_LANG_OBJECT), typeSolver))); + } else if (tp.getBounds().size() == 1) { + ResolvedTypeParameterDeclaration.Bound bound = tp.getBounds().get(0); + if (bound.isExtends()) { + //expectedArgumentType = expectedArgumentType.replaceTypeVariables(tp.getName(), bound.getType()); + expectedArgumentType = expectedArgumentType.replaceTypeVariables(tp, ResolvedWildcard.extendsBound(bound.getType())); + } else { + //expectedArgumentType = expectedArgumentType.replaceTypeVariables(tp.getName(), new ReferenceTypeUsageImpl(typeSolver.solveType(JAVA_LANG_OBJECT), typeSolver)); + expectedArgumentType = expectedArgumentType.replaceTypeVariables(tp, ResolvedWildcard.superBound(bound.getType())); + } + } else { + throw new UnsupportedOperationException(); + } + } + + // Consider cases where type variables involve bounds e.g. super/extends + ResolvedType expectedTypeWithSubstitutions = expectedTypeWithoutSubstitutions; + for (ResolvedTypeParameterDeclaration tp : typeParameters) { + if (tp.getBounds().isEmpty()) { + expectedTypeWithSubstitutions = expectedTypeWithSubstitutions.replaceTypeVariables(tp, new ReferenceTypeImpl(typeSolver.solveType(JAVA_LANG_OBJECT), typeSolver)); + } else if (tp.getBounds().size() == 1) { + ResolvedTypeParameterDeclaration.Bound bound = tp.getBounds().get(0); + if (bound.isExtends()) { + expectedTypeWithSubstitutions = expectedTypeWithSubstitutions.replaceTypeVariables(tp, bound.getType()); + } else { + expectedTypeWithSubstitutions = expectedTypeWithSubstitutions.replaceTypeVariables(tp, new ReferenceTypeImpl(typeSolver.solveType(JAVA_LANG_OBJECT), typeSolver)); + } + } else { + throw new UnsupportedOperationException(); + } + } + + // If the given argument still isn't applicable even after considering type arguments/generics, this is not a match. + if (!expectedArgumentType.isAssignableBy(actualArgumentType) + && !expectedTypeWithSubstitutions.isAssignableBy(actualArgumentType) + && !expectedTypeWithInference.isAssignableBy(actualArgumentType) + && !expectedTypeWithoutSubstitutions.isAssignableBy(actualArgumentType)) { + return false; + } + } + + // If the checks above haven't failed, then we've found a match. + return true; + } + + /** + * Filters by given function {@param keyExtractor} using a stateful filter mechanism. + * + *

+     *      persons.stream().filter(distinctByKey(Person::getName))
+     * 
+ *

+ * The example above would return a distinct list of persons containing only one person per name. + */ + private static Predicate distinctByKey(Function keyExtractor) { + Set seen = ConcurrentHashMap.newKeySet(); + return t -> seen.add(keyExtractor.apply(t)); + } + + /** + * @param methods we expect the methods to be ordered such that inherited methods are later in the list + */ + public static SymbolReference findMostApplicable(List methods, + String name, List argumentsTypes, TypeSolver typeSolver) { + SymbolReference res = findMostApplicable(methods, name, argumentsTypes, typeSolver, false); + if (res.isSolved()) { + return res; + } + return findMostApplicable(methods, name, argumentsTypes, typeSolver, true); + } + + public static SymbolReference findMostApplicable(List methods, + String name, List argumentsTypes, + TypeSolver typeSolver, + boolean wildcardTolerance + ) { + + List applicableMethods = methods.stream() + // Only consider methods with a matching name + .filter(m -> m.getName().equals(name)) + // Filters out duplicate ResolvedMethodDeclaration by their signature. + .filter(distinctByKey(ResolvedMethodDeclaration::getQualifiedSignature)) + // Checks if ResolvedMethodDeclaration is applicable to argumentsTypes. + .filter((m) -> isApplicable(m, name, argumentsTypes, typeSolver, wildcardTolerance)) + .collect(Collectors.toList()); + + // If no applicable methods found, return as unsolved. + if (applicableMethods.isEmpty()) { + return SymbolReference.unsolved(ResolvedMethodDeclaration.class); + } + + // If there are multiple possible methods found, null arguments can help to eliminate some matches. + if (applicableMethods.size() > 1) { + List nullParamIndexes = new ArrayList<>(); + for (int i = 0; i < argumentsTypes.size(); i++) { + if (argumentsTypes.get(i).isNull()) { + nullParamIndexes.add(i); + } + } + + // If some null arguments have been provided, use this to eliminate some opitons. + if (!nullParamIndexes.isEmpty()) { + // remove method with array param if a non array exists and arg is null + Set removeCandidates = new HashSet<>(); + for (Integer nullParamIndex : nullParamIndexes) { + for (ResolvedMethodDeclaration methDecl : applicableMethods) { + if (methDecl.getParam(nullParamIndex).getType().isArray()) { + removeCandidates.add(methDecl); + } + } + } + + // Where candidiates for removal are found, remove them. + if (!removeCandidates.isEmpty() && removeCandidates.size() < applicableMethods.size()) { + applicableMethods.removeAll(removeCandidates); + } + } + } + + // If only one applicable method found, short-circuit and return it here. + if (applicableMethods.size() == 1) { + return SymbolReference.solved(applicableMethods.get(0)); + } + + // Examine the applicable methods found, and evaluate each to determine the "best" one + ResolvedMethodDeclaration winningCandidate = applicableMethods.get(0); + ResolvedMethodDeclaration other = null; + boolean possibleAmbiguity = false; + for (int i = 1; i < applicableMethods.size(); i++) { + other = applicableMethods.get(i); + if (isMoreSpecific(winningCandidate, other, argumentsTypes)) { + possibleAmbiguity = false; + } else if (isMoreSpecific(other, winningCandidate, argumentsTypes)) { + possibleAmbiguity = false; + winningCandidate = other; + } else { + // 15.12.2.5. Choosing the Most Specific Method + // One applicable method m1 is more specific than another applicable method m2, for an invocation with argument + // expressions e1, ..., ek, if any of the following are true: + // m2 is generic, and m1 is inferred to be more specific than m2 for argument expressions e1, ..., ek by §18.5.4. + // 18.5.4. More Specific Method Inference should be verified + // ... + if (winningCandidate.isGeneric() && !other.isGeneric()) { + winningCandidate = other; + } else if (!winningCandidate.isGeneric() && other.isGeneric()) { + // nothing to do at this stage winningCandidate is the winner + } else if (winningCandidate.declaringType().getQualifiedName().equals(other.declaringType().getQualifiedName())) { + possibleAmbiguity = true; + } else { + // we expect the methods to be ordered such that inherited methods are later in the list + } + } + } + + if (possibleAmbiguity) { + // pick the first exact match if it exists + if (!isExactMatch(winningCandidate, argumentsTypes)) { + if (isExactMatch(other, argumentsTypes)) { + winningCandidate = other; + } else { + throw new MethodAmbiguityException( + "Ambiguous method call: cannot find a most applicable method: " + winningCandidate + + ", " + other); + } + } + } + + return SymbolReference.solved(winningCandidate); + } + + protected static boolean isExactMatch(ResolvedMethodLikeDeclaration method, List argumentsTypes) { + for (int i = 0; i < method.getNumberOfParams(); i++) { + if (!method.getParam(i).getType().equals(argumentsTypes.get(i))) { + return false; + } + } + return true; + } + + private static ResolvedType getMethodsExplicitAndVariadicParameterType(ResolvedMethodDeclaration method, int i) { + int numberOfParams = method.getNumberOfParams(); + + if (i < numberOfParams) { + return method.getParam(i).getType(); + } else if (method.hasVariadicParameter()) { + return method.getParam(numberOfParams - 1).getType(); + } else { + return null; + } + } + + private static boolean isMoreSpecific(ResolvedMethodDeclaration methodA, ResolvedMethodDeclaration methodB, + List argumentTypes) { + + final boolean aVariadic = methodA.hasVariadicParameter(); + final boolean bVariadic = methodB.hasVariadicParameter(); + final int aNumberOfParams = methodA.getNumberOfParams(); + final int bNumberOfParams = methodB.getNumberOfParams(); + final int numberOfArgs = argumentTypes.size(); + final ResolvedType lastArgType = numberOfArgs > 0 ? argumentTypes.get(numberOfArgs - 1) : null; + final boolean isLastArgArray = lastArgType != null && lastArgType.isArray(); + int omittedArgs = 0; + boolean isMethodAMoreSpecific = false; + + // If one method declaration has exactly the correct amount of parameters and is not variadic then it is always + // preferred to a declaration that is variadic (and hence possibly also has a different amount of parameters). + if (!aVariadic && aNumberOfParams == numberOfArgs && (bVariadic && (bNumberOfParams != numberOfArgs || + !isLastArgArray))) { + return true; + } else if (!bVariadic && bNumberOfParams == numberOfArgs && (aVariadic && (aNumberOfParams != numberOfArgs || + !isLastArgArray))) { + return false; + } + + // If both methods are variadic but the calling method omits any varArgs, bump the omitted args to + // ensure the varargs type is considered when determining which method is more specific + if (aVariadic && bVariadic && aNumberOfParams == bNumberOfParams && numberOfArgs == aNumberOfParams - 1) { + omittedArgs++; + } + + // Either both methods are variadic or neither is. So we must compare the parameter types. + for (int i = 0; i < numberOfArgs + omittedArgs; i++) { + ResolvedType paramTypeA = getMethodsExplicitAndVariadicParameterType(methodA, i); + ResolvedType paramTypeB = getMethodsExplicitAndVariadicParameterType(methodB, i); + + ResolvedType argType = null; + if (i < argumentTypes.size()) { + argType = argumentTypes.get(i); + } + + // Safety: if a type is null it means a signature with too few parameters managed to get to this point. + // This should not happen but it also means that this signature is immediately disqualified. + if (paramTypeA == null) { + return false; + } else if (paramTypeB == null) { + return true; + } + // Widening primitive conversions have priority over boxing/unboxing conversions when finding the most + // applicable method. E.g. assume we have method call foo(1) and declarations foo(long) and foo(Integer). + // The method call will call foo(long), as it requires a widening primitive conversion from int to long + // instead of a boxing conversion from int to Integer. See JLS §15.12.2. + // This is what we check here. + else if (argType != null && + paramTypeA.isPrimitive() == argType.isPrimitive() && + paramTypeB.isPrimitive() != argType.isPrimitive() && + paramTypeA.isAssignableBy(argType)) { + + return true; + } else if (argType != null && + paramTypeB.isPrimitive() == argType.isPrimitive() && + paramTypeA.isPrimitive() != argType.isPrimitive() && + paramTypeB.isAssignableBy(argType)) { + + return false; + // if paramA and paramB are not the last parameters + // and the type of paramA or paramB (which are not more specific at this stage) is java.lang.Object + // then we have to consider others parameters before concluding + } else if ((i < numberOfArgs - 1) + && (isJavaLangObject(paramTypeB) || (isJavaLangObject(paramTypeA)))) { + // consider others parameters + // but eventually mark the method A as more specific if the methodB has an argument of type java.lang.Object + isMethodAMoreSpecific = isMethodAMoreSpecific || isJavaLangObject(paramTypeB); + } + // If we get to this point then we check whether one of the methods contains a parameter type that is more + // specific. If it does, we can assume the entire declaration is more specific as we would otherwise have + // a situation where the declarations are ambiguous in the given context. + else { + boolean aAssignableFromB = paramTypeA.isAssignableBy(paramTypeB); + boolean bAssignableFromA = paramTypeB.isAssignableBy(paramTypeA); + + if (bAssignableFromA && !aAssignableFromB) { + // A's parameter is more specific + return true; + } else if (aAssignableFromB && !bAssignableFromA) { + // B's parameter is more specific + return false; + } + } + } + + if (aVariadic && !bVariadic) { + // if the last argument is an array then m1 is more specific + return isLastArgArray; + } else if (!aVariadic && bVariadic) { + // if the last argument is an array and m1 is not variadic then + // it is not more specific + return !isLastArgArray; + } + + return isMethodAMoreSpecific; + } + + private static boolean isJavaLangObject(ResolvedType paramType ) { + return paramType.isReferenceType() && paramType.asReferenceType().getQualifiedName().equals("java.lang.Object"); + } + + private static boolean isMoreSpecific(MethodUsage methodA, MethodUsage methodB) { + boolean oneMoreSpecificFound = false; + for (int i = 0; i < methodA.getNoParams(); i++) { + ResolvedType tdA = methodA.getParamType(i); + ResolvedType tdB = methodB.getParamType(i); + + boolean aIsAssignableByB = tdA.isAssignableBy(tdB); + boolean bIsAssignableByA = tdB.isAssignableBy(tdA); + + // A is more specific + if (bIsAssignableByA && !aIsAssignableByB) { + oneMoreSpecificFound = true; + } + // B is more specific + if (aIsAssignableByB && !bIsAssignableByA) { + return false; + } + + // If B is vararg and A is not, A is more specific + if (tdB.isArray() && tdB.asArrayType().getComponentType().isAssignableBy(tdA)) { + oneMoreSpecificFound = true; + } + } + return oneMoreSpecificFound; + } + + public static Optional findMostApplicableUsage(List methods, String name, List argumentsTypes, TypeSolver typeSolver) { + List applicableMethods = methods.stream().filter((m) -> isApplicable(m, name, argumentsTypes, typeSolver)).collect(Collectors.toList()); + + if (applicableMethods.isEmpty()) { + return Optional.empty(); + } + if (applicableMethods.size() == 1) { + return Optional.of(applicableMethods.get(0)); + } else { + MethodUsage winningCandidate = applicableMethods.get(0); + for (int i = 1; i < applicableMethods.size(); i++) { + MethodUsage other = applicableMethods.get(i); + if (isMoreSpecific(winningCandidate, other)) { + // nothing to do + } else if (isMoreSpecific(other, winningCandidate)) { + winningCandidate = other; + } else { + if (winningCandidate.declaringType().getQualifiedName().equals(other.declaringType().getQualifiedName())) { + if (!areOverride(winningCandidate, other)) { + throw new MethodAmbiguityException("Ambiguous method call: cannot find a most applicable method: " + winningCandidate + ", " + other + ". First declared in " + winningCandidate.declaringType().getQualifiedName()); + } + } else { + // we expect the methods to be ordered such that inherited methods are later in the list + //throw new UnsupportedOperationException(); + } + } + } + return Optional.of(winningCandidate); + } + } + + private static boolean areOverride(MethodUsage winningCandidate, MethodUsage other) { + if (!winningCandidate.getName().equals(other.getName())) { + return false; + } + if (winningCandidate.getNoParams() != other.getNoParams()) { + return false; + } + for (int i = 0; i < winningCandidate.getNoParams(); i++) { + if (!winningCandidate.getParamTypes().get(i).equals(other.getParamTypes().get(i))) { + return false; + } + } + return true; + } + + public static SymbolReference solveMethodInType(ResolvedTypeDeclaration typeDeclaration, + String name, + List argumentsTypes) { + return solveMethodInType(typeDeclaration, name, argumentsTypes, false); + } + + // TODO: Replace TypeDeclaration.solveMethod + public static SymbolReference solveMethodInType(ResolvedTypeDeclaration typeDeclaration, + String name, + List argumentsTypes, + boolean staticOnly) { + + if (typeDeclaration instanceof MethodResolutionCapability) { + return ((MethodResolutionCapability) typeDeclaration).solveMethod(name, argumentsTypes, + staticOnly); + } else { + throw new UnsupportedOperationException(typeDeclaration.getClass().getCanonicalName()); + } + } + + private static void inferTypes(ResolvedType source, ResolvedType target, Map mappings) { + if (source.equals(target)) { + return; + } + if (source.isReferenceType() && target.isReferenceType()) { + ResolvedReferenceType sourceRefType = source.asReferenceType(); + ResolvedReferenceType targetRefType = target.asReferenceType(); + if (sourceRefType.getQualifiedName().equals(targetRefType.getQualifiedName())) { + if (!sourceRefType.isRawType() && !targetRefType.isRawType()) { + for (int i = 0; i < sourceRefType.typeParametersValues().size(); i++) { + inferTypes(sourceRefType.typeParametersValues().get(i), targetRefType.typeParametersValues().get(i), mappings); + } + } + } + return; + } + if (source.isReferenceType() && target.isWildcard()) { + if (target.asWildcard().isBounded()) { + inferTypes(source, target.asWildcard().getBoundedType(), mappings); + return; + } + return; + } + if (source.isWildcard() && target.isWildcard()) { + return; + } + if (source.isReferenceType() && target.isTypeVariable()) { + mappings.put(target.asTypeParameter(), source); + return; + } + + if (source.isWildcard() && target.isReferenceType()) { + if (source.asWildcard().isBounded()) { + inferTypes(source.asWildcard().getBoundedType(), target, mappings); + } + return; + } + + if (source.isWildcard() && target.isTypeVariable()) { + mappings.put(target.asTypeParameter(), source); + return; + } + if (source.isTypeVariable() && target.isTypeVariable()) { + mappings.put(target.asTypeParameter(), source); + return; + } + if (source.isPrimitive() || target.isPrimitive()) { + return; + } + if (source.isNull()) { + return; + } + } + + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/SymbolDeclarator.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/SymbolDeclarator.java new file mode 100644 index 0000000000000000000000000000000000000000..9a696adb4564ac63240f3976eeac5279285f916a --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/SymbolDeclarator.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution; + +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; + +import java.util.List; + +/** + * @author Federico Tomassetti + */ +public interface SymbolDeclarator { + + List getSymbolDeclarations(); + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/SymbolSolver.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/SymbolSolver.java new file mode 100644 index 0000000000000000000000000000000000000000..eae68c0a6176afa588ab9af650b033f0f78695f7 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/SymbolSolver.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.type.ClassOrInterfaceType; +import com.github.javaparser.ast.type.Type; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.*; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.core.resolution.Context; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFactory; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserClassDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserEnumDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserInterfaceDeclaration; +import com.github.javaparser.symbolsolver.javassistmodel.JavassistClassDeclaration; +import com.github.javaparser.symbolsolver.javassistmodel.JavassistEnumDeclaration; +import com.github.javaparser.symbolsolver.javassistmodel.JavassistInterfaceDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.resolution.Value; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionClassDeclaration; +import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionEnumDeclaration; +import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionInterfaceDeclaration; + +import java.util.List; +import java.util.Optional; + +/** + * @author Federico Tomassetti + */ +public class SymbolSolver { + + private final TypeSolver typeSolver; + + public SymbolSolver(TypeSolver typeSolver) { + if (typeSolver == null) { + throw new IllegalArgumentException("Missing Parameter - Cannot initialise a SymbolSolver, without a way to solve types."); + } + + this.typeSolver = typeSolver; + } + + public SymbolReference solveSymbol(String name, Context context) { + return context.solveSymbol(name); + } + + public SymbolReference solveSymbol(String name, Node node) { + return solveSymbol(name, JavaParserFactory.getContext(node, typeSolver)); + } + + public Optional solveSymbolAsValue(String name, Context context) { + return context.solveSymbolAsValue(name); + } + + public Optional solveSymbolAsValue(String name, Node node) { + Context context = JavaParserFactory.getContext(node, typeSolver); + return solveSymbolAsValue(name, context); + } + + public SymbolReference solveType(String name, Context context) { + return context.solveType(name); + } + + public SymbolReference solveType(String name, Node node) { + return solveType(name, JavaParserFactory.getContext(node, typeSolver)); + } + + public MethodUsage solveMethod(String methodName, List argumentsTypes, Context context) { + SymbolReference decl = context.solveMethod(methodName, argumentsTypes, false); + if (!decl.isSolved()) { + throw new UnsolvedSymbolException(context.toString(), methodName); + } + return new MethodUsage(decl.getCorrespondingDeclaration()); + } + + public MethodUsage solveMethod(String methodName, List argumentsTypes, Node node) { + return solveMethod(methodName, argumentsTypes, JavaParserFactory.getContext(node, typeSolver)); + } + + public ResolvedTypeDeclaration solveType(Type type) { + if (type instanceof ClassOrInterfaceType) { + + // FIXME should call typesolver here! + + String name = ((ClassOrInterfaceType) type).getName().getId(); + SymbolReference ref = JavaParserFactory.getContext(type, typeSolver).solveType(name); + if (!ref.isSolved()) { + throw new UnsolvedSymbolException(JavaParserFactory.getContext(type, typeSolver).toString(), name); + } + return ref.getCorrespondingDeclaration(); + } else { + throw new UnsupportedOperationException(type.getClass().getCanonicalName()); + } + } + + public ResolvedType solveTypeUsage(String name, Context context) { + Optional genericType = context.solveGenericType(name); + if (genericType.isPresent()) { + return genericType.get(); + } + ResolvedReferenceTypeDeclaration typeDeclaration = typeSolver.solveType(name); + return new ReferenceTypeImpl(typeDeclaration, typeSolver); + } + + /** + * Solve any possible visible symbols including: fields, internal types, type variables, the type itself or its + * containers. + *

+ * It should contain its own private fields but not inherited private fields. + */ + public SymbolReference solveSymbolInType(ResolvedTypeDeclaration typeDeclaration, String name) { + if (typeDeclaration instanceof JavaParserClassDeclaration) { + Context ctx = ((JavaParserClassDeclaration) typeDeclaration).getContext(); + return ctx.solveSymbol(name); + } + if (typeDeclaration instanceof JavaParserInterfaceDeclaration) { + Context ctx = ((JavaParserInterfaceDeclaration) typeDeclaration).getContext(); + return ctx.solveSymbol(name); + } + if (typeDeclaration instanceof JavaParserEnumDeclaration) { + Context ctx = ((JavaParserEnumDeclaration) typeDeclaration).getContext(); + return ctx.solveSymbol(name); + } + if (typeDeclaration instanceof ReflectionClassDeclaration) { + return ((ReflectionClassDeclaration) typeDeclaration).solveSymbol(name, typeSolver); + } + if (typeDeclaration instanceof ReflectionInterfaceDeclaration) { + return ((ReflectionInterfaceDeclaration) typeDeclaration).solveSymbol(name, typeSolver); + } + if (typeDeclaration instanceof ReflectionEnumDeclaration) { + ResolvedEnumConstantDeclaration red = ((ReflectionEnumDeclaration) typeDeclaration).getEnumConstant(name); + return SymbolReference.solved(red); + } + if (typeDeclaration instanceof JavassistClassDeclaration) { + return ((JavassistClassDeclaration) typeDeclaration).solveSymbol(name, typeSolver); + } + if (typeDeclaration instanceof JavassistEnumDeclaration) { + return ((JavassistEnumDeclaration) typeDeclaration).solveSymbol(name, typeSolver); + } + if (typeDeclaration instanceof JavassistInterfaceDeclaration) { + return ((JavassistInterfaceDeclaration) typeDeclaration).solveSymbol(name, typeSolver); + } + return SymbolReference.unsolved(ResolvedValueDeclaration.class); + } + + /** + * Try to solve a symbol just in the declaration, it does not delegate to the container. + * + * @deprecated Similarly to solveType this should eventually disappear as the symbol resolution logic should be more general + * and do not be specific to JavaParser classes like in this case. + */ + @Deprecated + public SymbolReference solveTypeInType(ResolvedTypeDeclaration typeDeclaration, String name) { + if (typeDeclaration instanceof JavaParserClassDeclaration) { + return ((JavaParserClassDeclaration) typeDeclaration).solveType(name); + } + if (typeDeclaration instanceof JavaParserInterfaceDeclaration) { + return ((JavaParserInterfaceDeclaration) typeDeclaration).solveType(name); + } + return SymbolReference.unsolved(ResolvedReferenceTypeDeclaration.class); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/naming/NameCategory.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/naming/NameCategory.java new file mode 100644 index 0000000000000000000000000000000000000000..33562c8913c38be9c543c83e241693ba8ca01af5 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/naming/NameCategory.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.naming; + +/** + * Context causes a name syntactically to fall into one of seven categories: ModuleName, PackageName, TypeName, + * ExpressionName, MethodName, PackageOrTypeName, or AmbiguousName. + * TypeName is less expressive than the other six categories, because it is denoted with TypeIdentifier, which excludes + * the character sequence var (§3.8). + * + * See JLS 6.5 (https://docs.oracle.com/javase/specs/jls/se10/html/jls-6.html#jls-6.5) + */ +public enum NameCategory { + MODULE_NAME(false), + PACKAGE_NAME(false), + TYPE_NAME(false), + EXPRESSION_NAME(false), + METHOD_NAME(false), + PACKAGE_OR_TYPE_NAME(true), + AMBIGUOUS_NAME(true), + COMPILATION_ERROR(false); + + private boolean needDisambiguation; + + NameCategory(boolean needDisambiguation) { + this.needDisambiguation = needDisambiguation; + } + + /** + * Certain category include two or more unambiguous categories. + * These ambiguous categories are recognized solely through a syntactic process. In order to disambiguate them + * a semantic process (i.e., consider the symbols which are actually visible in a given context) is needed. + */ + public boolean isNeedingDisambiguation() { + return needDisambiguation; + } + + /** + * Is the given name acceptable for the given category? + */ + public boolean isNameAcceptable(String name) { + return this != TYPE_NAME || !name.equals("var"); + } + + public boolean isValid() { + return this != COMPILATION_ERROR; + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/naming/NameLogic.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/naming/NameLogic.java new file mode 100644 index 0000000000000000000000000000000000000000..7bb2b3597d28241e9891ae992b94749683feeec6 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/naming/NameLogic.java @@ -0,0 +1,1025 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +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.ReturnStmt; +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; + +/** + * 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; + } + } + + 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? + *

+ * 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(PatternExpr.class, name, (p, c) -> p.getName() == c)) { + return NameRole.DECLARATION; + } + if (whenParentIs(PatternExpr.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.hasScope() && 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.hasScope() && 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 reclassificationOfContextuallyAmbiguousPackageOrTypeName(name, typeSolver); + } + throw new UnsupportedOperationException("I do not know how to handle this semantic reclassification of ambiguous name categories"); + } + + private static NameCategory reclassificationOfContextuallyAmbiguousPackageOrTypeName(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 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.patternExprInScope(name).isPresent()) { + return NameCategory.EXPRESSION_NAME; + } + 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. + *

+ * 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.hasScope() && 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. 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.hasScope() && ne.getScope().get() == c2) + )) { + return true; + } + if (whenParentIs(ObjectCreationExpr.class, name, (ne, c2) -> + ne.hasScope() && 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

{ + boolean isSatisfied(P parent, C child); + } + + private static

boolean whenParentIs(Class

parentClass, C child) { + return whenParentIs(parentClass, child, (p, c) -> true); + } + + private static

boolean whenParentIs( + Class

parentClass, + C child, + PredicateOnParentAndChild predicate) { + if (child.getParentNode().isPresent()) { + Node parent = child.getParentNode().get(); + return parentClass.isInstance(parent) && predicate.isSatisfied(parentClass.cast(parent), child); + } else { + return false; + } + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/naming/NameRole.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/naming/NameRole.java new file mode 100644 index 0000000000000000000000000000000000000000..228c230968f2a61cdc07bf19cc77e90908d59dd3 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/naming/NameRole.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +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 +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/Bound.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/Bound.java new file mode 100644 index 0000000000000000000000000000000000000000..d39885f6bb7ee8d2699b966625cee55414fbdfab --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/Bound.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference; + +import com.github.javaparser.symbolsolver.resolution.typeinference.bounds.FalseBound; + +import java.util.Optional; +import java.util.Set; + +/** + * Bounds are defined for Inference Variables. + * + * @author Federico Tomassetti + */ +public abstract class Bound { + + /// + /// Creation of bounds + /// + + static Bound falseBound() { + return FalseBound.getInstance(); + } + + /// + /// Satisfiability + /// + + /** + * A bound is satisfied by an inference variable substitution if, after applying the substitution, + * the assertion is true. + */ + public abstract boolean isSatisfied(InferenceVariableSubstitution inferenceVariableSubstitution); + + /// + /// Classification of bounds + /// + + /** + * Given a bound of the form α = T or T = α, we say T is an instantiation of α. + * + * Return empty if it is not an instantiation. Otherwise it returns the variable of which this is an + * instantiation. + */ + public Optional isAnInstantiation() { + return Optional.empty(); + } + + boolean isAnInstantiationFor(InferenceVariable v) { + return isAnInstantiation().isPresent() && isAnInstantiation().get().getInferenceVariable().equals(v); + } + + /** + * Given a bound of the form α <: T, we say T is a proper upper bound of α. + * + * Return empty if it is not a proper upper bound. Otherwise it returns the variable of which this is an + * proper upper bound. + */ + public Optional isProperUpperBound() { + return Optional.empty(); + } + + /** + * Given a bound of the form T <: α, we say T is a proper lower bound of α. + * + * Return empty if it is not a proper lower bound. Otherwise it returns the variable of which this is an + * proper lower bound. + */ + public Optional isProperLowerBound() { + return Optional.empty(); + } + + Optional isProperLowerBoundFor(InferenceVariable inferenceVariable) { + Optional partial = isProperLowerBound(); + if (partial.isPresent() && partial.get().getInferenceVariable().equals(inferenceVariable)) { + return partial; + } else { + return Optional.empty(); + } + } + + Optional isProperUpperBoundFor(InferenceVariable inferenceVariable) { + Optional partial = isProperUpperBound(); + if (partial.isPresent() && partial.get().getInferenceVariable().equals(inferenceVariable)) { + return partial; + } else { + return Optional.empty(); + } + } + + /** + * Other bounds relate two inference variables, or an inference variable to a type that contains inference + * variables. Such bounds, of the form S = T or S <: T, are called dependencies. + */ + public boolean isADependency() { + return false; + } + + boolean isThrowsBoundOn(InferenceVariable inferenceVariable) { + return false; + } + + /// + /// Other methods + /// + + public abstract Set usedInferenceVariables(); +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/BoundSet.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/BoundSet.java new file mode 100644 index 0000000000000000000000000000000000000000..d2701fc2a80c2a7f63bc0e46d0bcf176eeb98956 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/BoundSet.java @@ -0,0 +1,835 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference; + +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.resolution.types.ResolvedReferenceType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.symbolsolver.resolution.typeinference.bounds.CapturesBound; +import com.github.javaparser.symbolsolver.resolution.typeinference.bounds.FalseBound; +import com.github.javaparser.symbolsolver.resolution.typeinference.bounds.SameAsBound; +import com.github.javaparser.symbolsolver.resolution.typeinference.bounds.SubtypeOfBound; +import com.github.javaparser.symbolsolver.resolution.typeinference.constraintformulas.TypeSameAsType; +import com.github.javaparser.symbolsolver.resolution.typeinference.constraintformulas.TypeSubtypeOfType; +import com.github.javaparser.utils.Pair; + +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import static com.github.javaparser.symbolsolver.resolution.typeinference.TypeHelper.*; + +/** + * @author Federico Tomassetti + */ +public class BoundSet { + + private static String JAVA_LANG_RUNTIME_EXCEPTION = RuntimeException.class.getCanonicalName(); + + private static final BoundSet EMPTY = new BoundSet(); + + private List bounds = new LinkedList<>(); + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + BoundSet boundSet = (BoundSet) o; + + return new HashSet<>(bounds).equals(new HashSet<>(boundSet.bounds)); + } + + @Override + public int hashCode() { + return bounds.hashCode(); + } + + @Override + public String toString() { + return "BoundSet{" + + "bounds=" + bounds + + '}'; + } + + /** + + * It is sometimes convenient to refer to an empty bound set with the symbol true; this is merely out of + * convenience, and the two are interchangeable. + */ + public boolean isTrue() { + return bounds.isEmpty(); + } + + public static BoundSet empty() { + return EMPTY; + } + + public BoundSet withBound(Bound bound) { + if (this.bounds.contains(bound)) { + return this; + } + BoundSet boundSet = new BoundSet(); + boundSet.bounds.addAll(this.bounds); + boundSet.bounds.add(bound); + return boundSet; + } + + private Optional> findPairSameAs(Predicate> condition) { + for (int i=0;i pair = new Pair(si, sj); + if (condition.test(pair)) { + return Optional.of(pair); + } + } + } + } + } + return Optional.empty(); + } + + public boolean isEmpty() { + return bounds.isEmpty(); + } + + interface Processor { + R process(B1 a, B2 b, R initialValue); + } + + private T forEachPairSameAs(Processor processor, T initialValue) { + T currentValue = initialValue; + for (int i=0;i T forEachPairSameAndSubtype(Processor processor, T initialValue) { + T currentValue = initialValue; + for (int i=0;i T forEachPairSubtypeAndSubtype(Processor processor, T initialValue) { + T currentValue = initialValue; + for (int i=0;i> findPairsOfCommonAncestors(ResolvedReferenceType r1, ResolvedReferenceType r2) { + List set1 = new LinkedList<>(); + set1.add(r1); + set1.addAll(r1.getAllAncestors()); + List set2 = new LinkedList<>(); + set2.add(r2); + set2.addAll(r2.getAllAncestors()); + List> pairs = new LinkedList<>(); + for (ResolvedReferenceType rtFrom1 : set1) { + for (ResolvedReferenceType rtFrom2 : set2) { + Optional rtFrom1TypeDeclaration = rtFrom1.getTypeDeclaration(); + Optional rtFrom2TypeDeclaration = rtFrom2.getTypeDeclaration(); + if (rtFrom1TypeDeclaration.isPresent() && rtFrom2TypeDeclaration.isPresent()) { + if(rtFrom1TypeDeclaration.get().equals(rtFrom2TypeDeclaration.get())) { + pairs.add(new Pair<>(rtFrom1, rtFrom2)); + } + } + } + } + return pairs; + } + + /** + * Maintains a set of inference variable bounds, ensuring that these are consistent as new bounds are added. + * Because the bounds on one variable can sometimes impact the possible choices for another variable, this process + * propagates bounds between such interdependent variables. + */ + public BoundSet incorporate(BoundSet otherBounds, TypeSolver typeSolver) { + BoundSet newBoundSet = this; + for (Bound b : otherBounds.bounds) { + newBoundSet = newBoundSet.withBound(b); + } + return newBoundSet.deriveImpliedBounds(typeSolver); + } + + public BoundSet deriveImpliedBounds(TypeSolver typeSolver) { + // As bound sets are constructed and grown during inference, it is possible that new bounds can be inferred + // based on the assertions of the original bounds. The process of incorporation identifies these new bounds + // and adds them to the bound set. + // + // Incorporation can happen in two scenarios. One scenario is that the bound set contains complementary pairs + // of bounds; this implies new constraint formulas, as specified in §18.3.1. The other scenario is that the + // bound set contains a bound involving capture conversion; this implies new bounds and may imply new + // constraint formulas, as specified in §18.3.2. In both scenarios, any new constraint formulas are reduced, + // and any new bounds are added to the bound set. This may trigger further incorporation; ultimately, the set + // will reach a fixed point and no further bounds can be inferred. + // + // If incorporation of a bound set has reached a fixed point, and the set does not contain the bound false, + // then the bound set has the following properties: + // + // - For each combination of a proper lower bound L and a proper upper bound U of an inference variable, L <: U. + // + // - If every inference variable mentioned by a bound has an instantiation, the bound is satisfied by the + // corresponding substitution. + // + // - Given a dependency α = β, every bound of α matches a bound of β, and vice versa. + // + // - Given a dependency α <: β, every lower bound of α is a lower bound of β, and every upper bound of β is an + // upper bound of α. + + ConstraintFormulaSet newConstraintsSet = ConstraintFormulaSet.empty(); + + // SECTION Complementary Pairs of Bounds + // (In this section, S and T are inference variables or types, and U is a proper type. For conciseness, a bound + // of the form α = T may also match a bound of the form T = α.) + // + // When a bound set contains a pair of bounds that match one of the following rules, a new constraint formula + // is implied: + // + // - α = S and α = T imply ‹S = T› + + newConstraintsSet = forEachPairSameAs((a, b, currentConstraintSet) -> { + if (areSameTypeInference(a.getS(), b.getS())) { + currentConstraintSet = currentConstraintSet.withConstraint(new TypeSameAsType(a.getT(), b.getT())); + } + if (areSameTypeInference(a.getS(), b.getT())) { + currentConstraintSet = currentConstraintSet.withConstraint(new TypeSameAsType(a.getS(), b.getT())); + } + if (areSameTypeInference(a.getT(), b.getS())) { + currentConstraintSet = currentConstraintSet.withConstraint(new TypeSameAsType(a.getT(), b.getS())); + } + if (areSameTypeInference(a.getT(), b.getT())) { + currentConstraintSet = currentConstraintSet.withConstraint(new TypeSameAsType(a.getS(), b.getS())); + } + return currentConstraintSet; + }, newConstraintsSet); + + // - α = S and α <: T imply ‹S <: T› + + newConstraintsSet = forEachPairSameAndSubtype((a, b, currentConstraintSet) -> { + if (areSameTypeInference(a.getS(), b.getS())) { + currentConstraintSet = currentConstraintSet.withConstraint(new TypeSubtypeOfType(typeSolver, a.getT(), b.getT())); + } + if (areSameTypeInference(a.getT(), b.getS())) { + currentConstraintSet = currentConstraintSet.withConstraint(new TypeSubtypeOfType(typeSolver, a.getS(), b.getT())); + } + return currentConstraintSet; + }, newConstraintsSet); + + // - α = S and T <: α imply ‹T <: S› + + newConstraintsSet = forEachPairSameAndSubtype((a, b, currentConstraintSet) -> { + if (areSameTypeInference(a.getS(), b.getT())) { + currentConstraintSet = currentConstraintSet.withConstraint(new TypeSubtypeOfType(typeSolver, b.getS(), a.getT())); + } + if (areSameTypeInference(a.getT(), b.getT())) { + currentConstraintSet = currentConstraintSet.withConstraint(new TypeSubtypeOfType(typeSolver, b.getS(), a.getS())); + } + return currentConstraintSet; + }, newConstraintsSet); + + // - S <: α and α <: T imply ‹S <: T› + + newConstraintsSet = forEachPairSubtypeAndSubtype((a, b, currentConstraintSet) -> { + if (areSameTypeInference(a.getT(), b.getS())) { + currentConstraintSet = currentConstraintSet.withConstraint(new TypeSubtypeOfType(typeSolver, b.getS(), a.getT())); + } + return currentConstraintSet; + }, newConstraintsSet); + + // - α = U and S = T imply ‹S[α:=U] = T[α:=U]› + + newConstraintsSet = forEachPairSameAs((a, b, currentConstraintSet) -> { + if (a.getS().isInferenceVariable() && isProperType(a.getT())) { + InferenceVariable alpha = (InferenceVariable)a.getS(); + ResolvedType U = a.getT(); + ResolvedType S = b.getS(); + ResolvedType T = b.getT(); + Substitution sub = Substitution.empty().withPair(alpha.getTypeParameterDeclaration(), U); + currentConstraintSet = currentConstraintSet.withConstraint(new TypeSameAsType(sub.apply(S), sub.apply(T))); + } + if (a.getT().isInferenceVariable() && isProperType(a.getS())) { + InferenceVariable alpha = (InferenceVariable)a.getT(); + ResolvedType U = a.getS(); + ResolvedType S = b.getS(); + ResolvedType T = b.getT(); + Substitution sub = Substitution.empty().withPair(alpha.getTypeParameterDeclaration(), U); + currentConstraintSet = currentConstraintSet.withConstraint(new TypeSameAsType(sub.apply(S), sub.apply(T))); + } + if (b.getS().isInferenceVariable() && isProperType(b.getT())) { + InferenceVariable alpha = (InferenceVariable)b.getS(); + ResolvedType U = b.getT(); + ResolvedType S = a.getS(); + ResolvedType T = a.getT(); + Substitution sub = Substitution.empty().withPair(alpha.getTypeParameterDeclaration(), U); + currentConstraintSet = currentConstraintSet.withConstraint(new TypeSameAsType(sub.apply(S), sub.apply(T))); + } + if (b.getT().isInferenceVariable() && isProperType(b.getS())) { + InferenceVariable alpha = (InferenceVariable)b.getT(); + ResolvedType U = b.getS(); + ResolvedType S = a.getS(); + ResolvedType T = a.getT(); + Substitution sub = Substitution.empty().withPair(alpha.getTypeParameterDeclaration(), U); + currentConstraintSet = currentConstraintSet.withConstraint(new TypeSameAsType(sub.apply(S), sub.apply(T))); + } + return currentConstraintSet; + }, newConstraintsSet); + + // - α = U and S <: T imply ‹S[α:=U] <: T[α:=U]› + + newConstraintsSet = forEachPairSameAndSubtype((a, b, currentConstraintSet) -> { + if (a.getS().isInferenceVariable() && isProperType(a.getT())) { + InferenceVariable alpha = (InferenceVariable)a.getS(); + ResolvedType U = a.getT(); + ResolvedType S = b.getS(); + ResolvedType T = b.getT(); + Substitution sub = Substitution.empty().withPair(alpha.getTypeParameterDeclaration(), U); + currentConstraintSet = currentConstraintSet.withConstraint(new TypeSubtypeOfType(typeSolver, sub.apply(S), sub.apply(T))); + } + if (a.getT().isInferenceVariable() && isProperType(a.getS())) { + InferenceVariable alpha = (InferenceVariable)a.getT(); + ResolvedType U = a.getS(); + ResolvedType S = b.getS(); + ResolvedType T = b.getT(); + Substitution sub = Substitution.empty().withPair(alpha.getTypeParameterDeclaration(), U); + currentConstraintSet = currentConstraintSet.withConstraint(new TypeSubtypeOfType(typeSolver, sub.apply(S), sub.apply(T))); + } + return currentConstraintSet; + }, newConstraintsSet); + + // When a bound set contains a pair of bounds α <: S and α <: T, and there exists a supertype of S of the + // form G and a supertype of T of the form G (for some generic class or interface, G), + // then for all i (1 ≤ i ≤ n), if Si and Ti are types (not wildcards), the constraint formula ‹Si = Ti› is + // implied. + + newConstraintsSet = forEachPairSubtypeAndSubtype((a, b, currentConstraintSet) -> { + if (a.getS().isInferenceVariable() && b.getS().isInferenceVariable()) { + if (a.getT().isReferenceType() && b.getT().isReferenceType()) { + ResolvedReferenceType S = a.getT().asReferenceType(); + ResolvedReferenceType T = b.getT().asReferenceType(); + List> pairs = findPairsOfCommonAncestors(S, T); + for (Pair pair : pairs) { + for (int i=0;i = capture(G), new bounds are + // implied and new constraint formulas may be implied, as follows. + + for (Bound b : this.bounds.stream().filter(b -> b instanceof CapturesBound).collect(Collectors.toList())) { + CapturesBound capturesBound = (CapturesBound)b; + + throw new UnsupportedOperationException(); + + // Let P1, ..., Pn represent the type parameters of G and let B1, ..., Bn represent the bounds of these type + // parameters. Let θ represent the substitution [P1:=α1, ..., Pn:=αn]. Let R be a type that is not an inference + // variable (but is not necessarily a proper type). + // + // A set of bounds on α1, ..., αn is implied, constructed from the declared bounds of P1, ..., Pn as specified + // in §18.1.3. + // + // In addition, for all i (1 ≤ i ≤ n): + // + // - If Ai is not a wildcard, then the bound αi = Ai is implied. + // + // - If Ai is a wildcard of the form ?: + // + // - αi = R implies the bound false + // + // - αi <: R implies the constraint formula ‹Bi θ <: R› + // + // - R <: αi implies the bound false + // + // - If Ai is a wildcard of the form ? extends T: + // + // - αi = R implies the bound false + // + // - If Bi is Object, then αi <: R implies the constraint formula ‹T <: R› + // + // - If T is Object, then αi <: R implies the constraint formula ‹Bi θ <: R› + // + // - R <: αi implies the bound false + // + // - If Ai is a wildcard of the form ? super T: + // + // - αi = R implies the bound false + // + // - αi <: R implies the constraint formula ‹Bi θ <: R› + // + // - R <: αi implies the constraint formula ‹R <: T› + } + + if (newConstraintsSet.isEmpty()) { + return this; + } else { + BoundSet newBounds = newConstraintsSet.reduce(typeSolver); + if (newBounds.isEmpty()) { + return this; + } + return this.incorporate(newBounds, typeSolver); + } + } + + public boolean containsFalse() { + return bounds.stream().anyMatch(it -> it instanceof FalseBound); + } + + private class VariableDependency { + private InferenceVariable depending; + private InferenceVariable dependedOn; + + public VariableDependency(InferenceVariable depending, InferenceVariable dependedOn) { + this.depending = depending; + this.dependedOn = dependedOn; + } + + public InferenceVariable getDepending() { + return depending; + } + + public InferenceVariable getDependedOn() { + return dependedOn; + } + + public boolean isReflexive() { + return dependedOn.equals(depending); + } + } + + private Set allInferenceVariables() { + Set variables = new HashSet<>(); + for (Bound b : bounds) { + variables.addAll(b.usedInferenceVariables()); + } + return variables; + } + + private boolean hasInstantiationFor(InferenceVariable v) { + for (Bound b : bounds) { + if (b.isAnInstantiationFor(v)) { + return true; + } + } + return false; + } + + private Instantiation getInstantiationFor(InferenceVariable v) { + for (Bound b : bounds) { + if (b.isAnInstantiationFor(v)) { + return b.isAnInstantiation().get(); + } + } + throw new IllegalArgumentException(); + } + + private boolean thereIsSomeJSuchThatβequalAlphaJ(Set alphas, InferenceVariable beta) { + for (InferenceVariable alphaJ : alphas) { + for (Bound b : bounds) { + if (b instanceof SameAsBound) { + SameAsBound sameAsBound = (SameAsBound)b; + if (sameAsBound.getS().equals(alphaJ) && sameAsBound.getT().equals(beta)) { + return true; + } + if (sameAsBound.getT().equals(alphaJ) && sameAsBound.getS().equals(beta)) { + return true; + } + } + } + } + return false; + } + + private List> buildAllSubsetsOfSize(Set allElements, int desiredSize) { + if (desiredSize == allElements.size()) { + return Arrays.asList(allElements); + } else { + List> res = new LinkedList<>(); + for (T element : allElements) { + Set subset = allButOne(allElements, element); + res.addAll(buildAllSubsetsOfSize(subset, desiredSize)); + } + return res; + } + } + + private Set allButOne(Set elements, T element) { + Set set = new HashSet(elements); + set.remove(element); + return set; + } + + /** + * there exists no non-empty proper subset of { α1, ..., αn } with this property. + */ + private Optional> smallestSetWithProperty(Set uninstantiatedVariables, + List dependencies) { + for (int i=1;i<=uninstantiatedVariables.size();i++) { + for (Set aSubSet : buildAllSubsetsOfSize(uninstantiatedVariables, i)){ + if (hasProperty(aSubSet, dependencies)) { + return Optional.of(aSubSet); + } + } + } + return Optional.empty(); + } + + /** + * if αi depends on the resolution of a variable β, then either β has an instantiation + * or there is some j such that β = αj + * @return + */ + private boolean hasProperty(Set alphas, List dependencies) { + for (InferenceVariable alphaI: alphas) { + for (InferenceVariable beta: dependencies.stream() + .filter(d -> d.depending.equals(alphaI)) + .filter(d -> !d.isReflexive()) + .map(d -> d.dependedOn) + .collect(Collectors.toList())) { + if (!hasInstantiationFor(beta) && !thereIsSomeJSuchThatβequalAlphaJ(alphas, beta)) { + return false; + } + } + } + return true; + } + + /** + * Examines the bounds on an inference variable and determines an instantiation that is compatible with those + * bounds. It also decides the order in which interdependent inference variables are to be resolved. + */ + public Optional performResolution(List variablesToResolve, TypeSolver typeSolver) { + + if (this.containsFalse()) { + return Optional.empty(); + } + + List dependencies = new LinkedList<>(); + + // Given a bound set that does not contain the bound false, a subset of the inference variables mentioned by + // the bound set may be resolved. This means that a satisfactory instantiation may be added to the set for each + // inference variable, until all the requested variables have instantiations. + // + // Dependencies in the bound set may require that the variables be resolved in a particular order, or that + // additional variables be resolved. Dependencies are specified as follows: + // + // - Given a bound of one of the following forms, where T is either an inference variable β or a type that + // mentions β: + // + // - α = T + // + // - α <: T + // + // - T = α + // + // - T <: α + // + // If α appears on the left-hand side of another bound of the form G<..., α, ...> = capture(G<...>), then β + // depends on the resolution of α. Otherwise, α depends on the resolution of β. + + for (Bound b : bounds) { + if (b instanceof CapturesBound) { + throw new UnsupportedOperationException(); + } + } + + // - An inference variable α appearing on the left-hand side of a bound of the form + // G<..., α, ...> = capture(G<...>) depends on the resolution of every other inference variable mentioned in + // this bound (on both sides of the = sign). + + for (Bound b : bounds) { + if (b instanceof CapturesBound) { + throw new UnsupportedOperationException(); + } + } + + // - An inference variable α depends on the resolution of an inference variable β if there exists an inference + // variable γ such that α depends on the resolution of γ and γ depends on the resolution of β. + + for (int i=0;i V = new HashSet<>(); + V.addAll(variablesToResolve); + for (VariableDependency dependency : dependencies) { + if (variablesToResolve.contains(dependency.depending)) { + V.add(dependency.dependedOn); + } + } + + // If every variable in V has an instantiation, then resolution succeeds and this procedure terminates. + + boolean ok = true; + for (InferenceVariable v : V) { + if (!hasInstantiationFor(v)) { + ok = false; + } + } + if (ok) { + InstantiationSet instantiationSet = InstantiationSet.empty(); + for (InferenceVariable v : V) { + instantiationSet = instantiationSet.withInstantiation(getInstantiationFor(v)); + } + return Optional.of(instantiationSet); + } + + // Otherwise, let { α1, ..., αn } be a non-empty subset of uninstantiated variables in V such that i) + // for all i (1 ≤ i ≤ n), if αi depends on the resolution of a variable β, then either β has an instantiation + // or there is some j such that β = αj; and ii) there exists no non-empty proper subset of { α1, ..., αn } + // with this property. + + Set uninstantiatedPortionOfV = new HashSet<>(); + for (InferenceVariable v : V) { + if (!hasInstantiationFor(v)) { + uninstantiatedPortionOfV.add(v); + } + } + for (Set alphas: allSetsWithProperty(uninstantiatedPortionOfV, dependencies)) { + + // Resolution proceeds by generating an instantiation for each of α1, ..., αn based on the + // bounds in the bound set: + + boolean hasSomeCaptureForAlphas = alphas.stream().anyMatch( + alphaI -> appearInLeftPartOfCapture(alphaI) + ); + + // - If the bound set does not contain a bound of the form G<..., αi, ...> = capture(G<...>) + // for all i (1 ≤ i ≤ n), then a candidate instantiation Ti is defined for each αi: + + if (!hasSomeCaptureForAlphas) { + BoundSet newBounds = BoundSet.empty(); + for (InferenceVariable alphaI : alphas) { + Set properLowerBounds = bounds.stream() + .filter(b -> b.isProperLowerBoundFor(alphaI).isPresent()) + .map(b -> b.isProperLowerBoundFor(alphaI).get().getProperType()) + .collect(Collectors.toSet()); + + ResolvedType Ti = null; + + // - If αi has one or more proper lower bounds, L1, ..., Lk, then Ti = lub(L1, ..., Lk) (§4.10.4). + + if (properLowerBounds.size() > 0) { + Ti = leastUpperBound(properLowerBounds); + } + + // - Otherwise, if the bound set contains throws αi, and the proper upper bounds of αi are, at most, + // Exception, Throwable, and Object, then Ti = RuntimeException. + + boolean throwsBound = bounds.stream().anyMatch(b -> b.isThrowsBoundOn(alphaI)); + if (Ti == null && throwsBound && properUpperBoundsAreAtMostExceptionThrowableAndObject(alphaI)) { + Ti = new ReferenceTypeImpl(typeSolver.solveType(JAVA_LANG_RUNTIME_EXCEPTION), typeSolver); + } + + // - Otherwise, where αi has proper upper bounds U1, ..., Uk, Ti = glb(U1, ..., Uk) (§5.1.10). + + if (Ti == null) { + Set properUpperBounds = bounds.stream() + .filter(b -> b.isProperUpperBoundFor(alphaI).isPresent()) + .map(b -> b.isProperUpperBoundFor(alphaI).get().getProperType()) + .collect(Collectors.toSet()); + if (properUpperBounds.size() == 0) { + throw new IllegalStateException(); + } + Ti = glb(properUpperBounds); + } + + newBounds = newBounds.withBound(new SameAsBound(alphaI, Ti)); + } + + // The bounds α1 = T1, ..., αn = Tn are incorporated with the current bound set. + + BoundSet incorporatedBoundSet = this.incorporate(newBounds, typeSolver); + + // If the result does not contain the bound false, then the result becomes the new bound set, and resolution + // proceeds by selecting a new set of variables to instantiate (if necessary), as described above. + + if (!incorporatedBoundSet.containsFalse()) { + return incorporatedBoundSet.performResolution(variablesToResolve, typeSolver); + } + + // Otherwise, the result contains the bound false, so a second attempt is made to instantiate { α1, ..., αn } + // by performing the step below. + + throw new UnsupportedOperationException(); + } + + // - If the bound set contains a bound of the form G<..., αi, ...> = capture(G<...>) for some i (1 ≤ i ≤ n), or; + + else { + + // If the bound set produced in the step above contains the bound false; + // + // then let Y1, ..., Yn be fresh type variables whose bounds are as follows: + // + // - For all i (1 ≤ i ≤ n), if αi has one or more proper lower bounds L1, ..., Lk, then let the lower bound + // of Yi be lub(L1, ..., Lk); if not, then Yi has no lower bound. + // + // - For all i (1 ≤ i ≤ n), where αi has upper bounds U1, ..., Uk, let the upper bound of Yi be + // glb(U1 θ, ..., Uk θ), where θ is the substitution [α1:=Y1, ..., αn:=Yn]. + // + // If the type variables Y1, ..., Yn do not have well-formed bounds (that is, a lower bound is not a subtype + // of an upper bound, or an intersection type is inconsistent), then resolution fails. + // + // Otherwise, for all i (1 ≤ i ≤ n), all bounds of the form G<..., αi, ...> = capture(G<...>) are removed + // from the current bound set, and the bounds α1 = Y1, ..., αn = Yn are incorporated. + // + // If the result does not contain the bound false, then the result becomes the new bound set, and resolution + // proceeds by selecting a new set of variables to instantiate (if necessary), as described above. + // + // Otherwise, the result contains the bound false, and resolution fails. + + throw new UnsupportedOperationException(); + } + } + return Optional.empty(); + } + + private Set> allPossibleSetsWithProperty(Set allElements, List dependencies) { + Set> result = new HashSet<>(); + for (int i=1;i<=allElements.size();i++) { + for (Set aSubSet : buildAllSubsetsOfSize(allElements, i)){ + if (hasProperty(aSubSet, dependencies)) { + result.add(aSubSet); + } + } + } + return result; + } + + private boolean thereAreProperSubsets(Set aSet, Set> allPossibleSets) { + for (Set anotherSet : allPossibleSets) { + if (!anotherSet.equals(aSet)) { + if (isTheFirstAProperSubsetOfTheSecond(anotherSet, aSet)) { + return true; + } + } + } + return false; + } + + private boolean isTheFirstAProperSubsetOfTheSecond(Set subset, Set originalSet) { + return originalSet.containsAll(subset) && originalSet.size() > subset.size(); + } + + private Set> allSetsWithProperty(Set allElements, List dependencies) { + Set> allPossibleSets = allPossibleSetsWithProperty(allElements, dependencies); + Set> selected = new HashSet<>(); + for (Set aSet : allPossibleSets) { + if (!thereAreProperSubsets(aSet, allPossibleSets)) { + selected.add(aSet); + } + } + return selected; + } + + private boolean properUpperBoundsAreAtMostExceptionThrowableAndObject(InferenceVariable inferenceVariable) { + throw new UnsupportedOperationException(); + } + + private boolean appearInLeftPartOfCapture(InferenceVariable inferenceVariable) { + for (Bound b : bounds) { + if (b instanceof CapturesBound) { + CapturesBound capturesBound = (CapturesBound)b; + if (capturesBound.getInferenceVariables().contains(inferenceVariable)) { + return true; + } + } + } + return false; + } + + public List getProperUpperBoundsFor(InferenceVariable inferenceVariable) { + return bounds.stream().filter(b -> b.isProperUpperBoundFor(inferenceVariable).isPresent()).collect(Collectors.toList()); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/ConstraintFormula.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/ConstraintFormula.java new file mode 100644 index 0000000000000000000000000000000000000000..f9812f95739b86ab339f16db84448d0e80d4fce0 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/ConstraintFormula.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * Constraint formulas are assertions of compatibility or subtyping that may involve inference variables. + * + * @author Federico Tomassetti + */ +public abstract class ConstraintFormula { + + public static class ReductionResult { + private BoundSet boundSet; + private List constraintFormulas; + + public BoundSet getBoundSet() { + return boundSet; + } + + public List getConstraintFormulas() { + return constraintFormulas; + } + + public static ReductionResult empty() { + return new ReductionResult(); + } + + public ReductionResult withConstraint(ConstraintFormula constraintFormula) { + ReductionResult newInstance = new ReductionResult(); + newInstance.boundSet = this.boundSet; + newInstance.constraintFormulas = new LinkedList<>(); + newInstance.constraintFormulas.addAll(this.constraintFormulas); + newInstance.constraintFormulas.add(constraintFormula); + return newInstance; + } + + public ReductionResult withBound(Bound bound) { + ReductionResult newInstance = new ReductionResult(); + newInstance.boundSet = this.boundSet.withBound(bound); + newInstance.constraintFormulas = this.constraintFormulas; + return newInstance; + } + + private ReductionResult() { + this.boundSet = BoundSet.empty(); + this.constraintFormulas = new LinkedList<>(); + } + + public static ReductionResult trueResult() { + return empty(); + } + + public static ReductionResult falseResult() { + return empty().withBound(Bound.falseBound()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ReductionResult that = (ReductionResult) o; + + if (!boundSet.equals(that.boundSet)) return false; + return constraintFormulas.equals(that.constraintFormulas); + } + + @Override + public int hashCode() { + int result = boundSet.hashCode(); + result = 31 * result + constraintFormulas.hashCode(); + return result; + } + + @Override + public String toString() { + return "ReductionResult{" + + "boundSet=" + boundSet + + ", constraintFormulas=" + constraintFormulas + + '}'; + } + + public ConstraintFormula getConstraint(int index) { + if (constraintFormulas.size() <= index) { + throw new IllegalArgumentException("Constraint with index " + index + " is not available as there are " + constraintFormulas.size() + " constraints"); + } + return constraintFormulas.get(index); + } + + public static ReductionResult oneConstraint(ConstraintFormula constraintFormula) { + return empty().withConstraint(constraintFormula); + } + + public static ReductionResult withConstraints(ConstraintFormula... constraints) { + return withConstraints(Arrays.asList(constraints)); + } + + public static ReductionResult oneBound(Bound bound) { + return empty().withBound(bound); + } + + public static ReductionResult withConstraints(List constraints) { + ReductionResult reductionResult = new ReductionResult(); + reductionResult.constraintFormulas.addAll(constraints); + return reductionResult; + } + + public static ReductionResult bounds(BoundSet bounds) { + ReductionResult reductionResult = new ReductionResult(); + reductionResult.boundSet = bounds; + return reductionResult; + } + } + + /** + * A formula is reduced to one or both of: + * i) A bound or bound set, which is to be incorporated with the "current" bound set. Initially, the current bound + * set is empty. + * ii) Further constraint formulas, which are to be reduced recursively. + */ + public abstract ReductionResult reduce(BoundSet currentBoundSet); + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/ConstraintFormulaSet.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/ConstraintFormulaSet.java new file mode 100644 index 0000000000000000000000000000000000000000..d568f84a2559dd6dc2433151808f27646f014892 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/ConstraintFormulaSet.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference; + +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.LinkedList; +import java.util.List; + +/** + * @author Federico Tomassetti + */ +public class ConstraintFormulaSet { + private List constraintFormulas; + + public ConstraintFormulaSet withConstraint(ConstraintFormula constraintFormula) { + ConstraintFormulaSet newInstance = new ConstraintFormulaSet(); + newInstance.constraintFormulas.addAll(this.constraintFormulas); + newInstance.constraintFormulas.add(constraintFormula); + return newInstance; + } + + private static final ConstraintFormulaSet EMPTY = new ConstraintFormulaSet(); + + public static ConstraintFormulaSet empty() { + return EMPTY; + } + + private ConstraintFormulaSet() { + constraintFormulas = new LinkedList<>(); + } + + /** + * Takes a compatibility assertion about an expression or type, called a constraint formula, and reduces it to a + * set of bounds on inference variables. Often, a constraint formula reduces to other constraint formulas, + * which must be recursively reduced. A procedure is followed to identify these additional constraint formulas and, + * ultimately, to express via a bound set the conditions under which the choices for inferred types would render + * each constraint formula true. + */ + public BoundSet reduce(TypeSolver typeSolver) { + List constraints = new LinkedList<>(constraintFormulas); + BoundSet boundSet = BoundSet.empty(); + while (constraints.size() > 0) { + ConstraintFormula constraintFormula = constraints.remove(0); + ConstraintFormula.ReductionResult reductionResult = constraintFormula.reduce(boundSet); + constraints.addAll(reductionResult.getConstraintFormulas()); + boundSet.incorporate(reductionResult.getBoundSet(), typeSolver); + } + return boundSet; + } + + public boolean isEmpty() { + return constraintFormulas.isEmpty(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/ControlFlowLogic.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/ControlFlowLogic.java new file mode 100644 index 0000000000000000000000000000000000000000..74da87b5110b097552e2820cd316b26b5fdc7100 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/ControlFlowLogic.java @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.ConstructorDeclaration; +import com.github.javaparser.ast.body.InitializerDeclaration; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.expr.VariableDeclarationExpr; +import com.github.javaparser.ast.stmt.*; +import com.github.javaparser.ast.visitor.GenericVisitor; +import com.github.javaparser.ast.visitor.GenericVisitorAdapter; + +import java.util.List; + +/** + * Consider Control Flow to determine which statements are reachable. + * + * Except for the special treatment of while, do, and for statements whose condition expression has the constant value + * true, the values of expressions are not taken into account in the flow analysis. + * + * See JLS 14.21 + * + * @author Federico Tomassetti + */ +public class ControlFlowLogic { + + private static ControlFlowLogic instance = new ControlFlowLogic(); + + public static ControlFlowLogic getInstance() { + return instance; + } + + private ControlFlowLogic() { + + } + + /** + * A break statement with no label attempts to transfer control to the innermost enclosing switch, while, do, or + * for statement of the immediately enclosing method or initializer; this statement, which is called the break + * target, then immediately completes normally. + * + * + * A break statement with label Identifier attempts to transfer control to the enclosing labeled statement (§14.7) + * that has the same Identifier as its label; this statement, which is called the break target, then immediately + * completes normally. In this case, the break target need not be a switch, while, do, or for statement. + */ + public Statement breakTarget(BreakStmt breakStmt) { + throw new UnsupportedOperationException(); + } + + /** + * A reachable break statement exits a statement if, within the break target, either there are no try statements + * whose try blocks contain the break statement, or there are try statements whose try blocks contain the break + * statement and all finally clauses of those try statements can complete normally. + */ + public boolean exitTheStatement(BreakStmt breakStmt) { + if (!isReachable(breakStmt)) { + return false; + } + Statement breakTarget = breakTarget(breakStmt); + for (TryStmt tryStmt : containedTryStmts(breakTarget)) { + if (contains(tryStmt.getTryBlock(), breakStmt)) { + if (!tryStmt.getFinallyBlock().isPresent() && !canCompleteNormally(tryStmt.getFinallyBlock().get())) { + return false; + } + } + } + return true; + } + + public boolean continueADoStatement(ContinueStmt continueStmt, DoStmt doStmt) { + for (TryStmt tryStmt : containedTryStmts(continueStmt)) { + if (contains(tryStmt.getTryBlock(), continueStmt)) { + if (!tryStmt.getFinallyBlock().isPresent() && !canCompleteNormally(tryStmt.getFinallyBlock().get())) { + return false; + } + } + } + return true; + } + + private boolean contains(Statement container, Statement contained) { + throw new UnsupportedOperationException(); + } + + private List containedTryStmts(Statement statement) { + throw new UnsupportedOperationException(); + } + + private

boolean parentIs(Node node, Class

parentClass) { + if (node.getParentNode().isPresent()) { + return parentClass.isInstance(node.getParentNode().get()); + } else { + return false; + } + } + + // See JLS 14.21 + public boolean canCompleteNormally(Statement statement) { + if (!isReachable(statement)) { + return false; + } + GenericVisitor visitor = new GenericVisitorAdapter(){ + @Override + public Boolean visit(BlockStmt n, Void arg) { + // An empty block that is not a switch block can complete normally iff it is reachable + if (n.isEmpty() && !parentIs(statement, SwitchStmt.class)) { + return isReachable(statement); + } + // A non-empty block that is not a switch block can complete normally iff the last statement in + // it can complete normally. + if (!n.isEmpty() && !parentIs(statement, SwitchStmt.class)) { + return canCompleteNormally(n.getStatement(n.getStatements().size() - 1)); + } + throw new UnsupportedOperationException(); + } + + @Override + public Boolean visit(LabeledStmt n, Void arg) { + // A labeled statement can complete normally if at least one of the following is true: + // – The contained statement can complete normally. + // – There is a reachable break statement that exits the labeled statement. + throw new UnsupportedOperationException(); + } + + @Override + public Boolean visit(EmptyStmt n, Void arg) { + // An empty statement can complete normally iff it is reachable. + return isReachable(n); + } + + @Override + public Boolean visit(LocalClassDeclarationStmt n, Void arg) { + // A local class declaration statement can complete normally if it is reachable. + return isReachable(n); + } + + @Override + public Boolean visit(LocalRecordDeclarationStmt n, Void arg) { + // A local record declaration statement can complete normally if it is reachable. + return isReachable(n); + } + + @Override + public Boolean visit(IfStmt n, Void arg) { + if (n.getElseStmt().isPresent()) { + // An if-then-else statement can complete normally iff the then-statement can + // complete normally or the else-statement can complete normally. + return canCompleteNormally(n.getThenStmt()) || canCompleteNormally(n.getElseStmt().get()); + } else { + // An if-then statement can complete normally iff it is reachable. + return isReachable(n); + } + } + + @Override + public Boolean visit(AssertStmt n, Void arg) { + // An assert statement can complete normally iff it is reachable. + return isReachable(n); + } + + @Override + public Boolean visit(ExpressionStmt n, Void arg) { + // A local variable declaration statement can complete normally iff it is reachable. + if (n.getExpression() instanceof VariableDeclarationExpr) { + VariableDeclarationExpr expr = (VariableDeclarationExpr) n.getExpression(); + return isReachable(n); + } + // An expression statement can complete normally iff it is reachable. + return isReachable(n); + } + }; + return statement.accept(visitor, null); + } + + private boolean isReachableBecauseOfPosition(Statement statement) { + // The first statement in a non-empty block that is not a switch block is reachable iff the block is reachable. + + // Every other statement S in a non-empty block that is not a switch block is reachable iff the statement + // preceding S can complete normally. + + // The contained statement of a Labelled Statement is reachable iff the labeled statement is reachable. + + // The then-statement of an if-then statement is reachable iff the if-then statement is reachable. + + // The then-statement of an if-then-else statement is reachable iff the if-then-else statement is reachable. + // The else-statement is reachable iff the if-then-else statement is reachable. + + throw new UnsupportedOperationException(); + } + + public boolean isReachable(Statement statement) { + + GenericVisitor visitor = new GenericVisitorAdapter(){ + @Override + public Boolean visit(BlockStmt n, Void arg) { + // The block that is the body of a constructor, method, instance initializer, or static initializer is + // reachable + if (statement.getParentNode().isPresent()) { + if (statement.getParentNode().get() instanceof ConstructorDeclaration) { + return true; + } + if (statement.getParentNode().get() instanceof MethodDeclaration) { + return true; + } + if (statement.getParentNode().get() instanceof InitializerDeclaration) { + return true; + } + } + return isReachableBecauseOfPosition(statement); + } + + @Override + public Boolean visit(LocalClassDeclarationStmt n, Void arg) { + return super.visit(n, arg); + } + + @Override + public Boolean visit(LocalRecordDeclarationStmt n, Void arg) { + return super.visit(n, arg); + } + }; + return statement.accept(visitor, null); + + // + // + // + // A switch statement can complete normally iff at least one of the following is + //true: + //– The switch block is empty or contains only switch labels. + //– The last statement in the switch block can complete normally. + //– There is at least one switch label after the last switch block statement group. – The switch block does not contain a default label. + //– There is a reachable break statement that exits the switch statement. + // BLOCKS AND STATEMENTS Unreachable Statements 14.21 + // + //A switch block is reachable iff its switch statement is reachable. + // + // A statement in a switch block is reachable iff its switch statement is reachable + //and at least one of the following is true: + //– It bears a case or default label. + //– There is a statement preceding it in the switch block and that preceding statement can complete normally. + // + //A while statement can complete normally iff at least one of the following is true: + //– Thewhilestatementisreachableandtheconditionexpressionisnotaconstant + //expression (§15.28) with value true. + //– There is a reachable break statement that exits the while statement. + // The contained statement is reachable iff the while statement is reachable and the condition expression is not a constant expression whose value is false. + // + // A do statement can complete normally iff at least one of the following is true: + //– The contained statement can complete normally and the condition expression + //is not a constant expression (§15.28) with value true. + //– The do statement contains a reachable continue statement with no label, and the do statement is the innermost while, do, or for statement that contains that continue statement, and the continue statement continues that do statement, and the condition expression is not a constant expression with value true. + //– The do statement contains a reachable continue statement with a label L, and the do statement has label L, and the continue statement continues that do statement, and the condition expression is not a constant expression with value true. + //– There is a reachable break statement that exits the do statement. + // The contained statement is reachable iff the do statement is reachable. + // + // A basic for statement can complete normally iff at least one of the following + //is true: + //– The for statement is reachable, there is a condition expression, and the + //condition expression is not a constant expression (§15.28) with value true. + //– There is a reachable break statement that exits the for statement. + // The contained statement is reachable iff the for statement is reachable and the condition expression is not a constant expression whose value is false. + // + //• An enhanced for statement can complete normally iff it is reachable. + // + //• A break, continue, return, or throw statement cannot complete normally. + // + //• A synchronized statement can complete normally iff the contained statement can complete normally. + // The contained statement is reachable iff the synchronized statement is reachable. + // + //• A try statement can complete normally iff both of the following are true: + //– The try block can complete normally or any catch block can complete + //normally. + //– Ifthetrystatementhasafinallyblock,thenthefinallyblockcancomplete normally. + // + //• The try block is reachable iff the try statement is reachable. + // + //• A catch block C is reachable iff both of the following are true: + //– Either the type of C's parameter is an unchecked exception type or Exception or a superclass of Exception, or some expression or throw statement in the try block is reachable and can throw a checked exception whose type is assignable to the type of C's parameter. (An expression is reachable iff the innermost statement containing it is reachable.) + //See §15.6 for normal and abrupt completion of expressions. + //– There is no earlier catch block A in the try statement such that the type of C's + //parameter is the same as or a subclass of the type of A's parameter. + //• The Block of a catch block is reachable iff the catch block is reachable. + //• If a finally block is present, it is reachable iff the try statement is reachable. + // One might expect the if statement to be handled in the following manner: + //• An if-then statement can complete normally iff at least one of the following is true: + //– The if-then statement is reachable and the condition expression is not a constant expression whose value is true. + //– The then-statement can complete normally. + // The then-statement is reachable iff the if-then statement is reachable and the + //condition expression is not a constant expression whose value is false. + //• An if-then-else statement can complete normally iff the then-statement can complete normally or the else-statement can complete normally. + // BLOCKS AND STATEMENTS Unreachable Statements 14.21 + //The then-statement is reachable iff the if-then-else statement is reachable and the condition expression is not a constant expression whose value is false. + // The else-statement is reachable iff the if-then-else statement is reachable and the condition expression is not a constant expression whose value is true. + // This approach would be consistent with the treatment of other control structures. However, in order to allow the if statement to be used conveniently for "conditional compilation" purposes, the actual rules differ. + + // FIXME + //throw new UnsupportedOperationException(); + } + + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/ExpressionHelper.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/ExpressionHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..c71c55ed1376548d48583159d7c261b8ff322542 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/ExpressionHelper.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference; + +import com.github.javaparser.ast.expr.Expression; +import com.github.javaparser.ast.expr.LambdaExpr; +import com.github.javaparser.ast.stmt.BlockStmt; +import com.github.javaparser.ast.type.UnknownType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.List; + +/** + * @author Federico Tomassetti + */ +public class ExpressionHelper { + + public static boolean isExplicitlyTyped(LambdaExpr lambdaExpr) { + return lambdaExpr.getParameters().stream().allMatch(p -> !(p.getType() instanceof UnknownType)); + } + + public static List getResultExpressions(BlockStmt blockStmt) { + throw new UnsupportedOperationException(); + } + + public static boolean isCompatibleInAssignmentContext(Expression expression, ResolvedType type, TypeSolver typeSolver) { + return type.isAssignableBy(JavaParserFacade.get(typeSolver).getType(expression, false)); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/InferenceVariable.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/InferenceVariable.java new file mode 100644 index 0000000000000000000000000000000000000000..a4527e9cd0e21aa093093ff8d1c9f45a88d7a962 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/InferenceVariable.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference; + +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; + +import java.util.LinkedList; +import java.util.List; + +/** + * Are meta-variables for types - that is, they are special names that allow abstract reasoning about types. + * To distinguish them from type variables, inference variables are represented with Greek letters, principally α. + * + * See JLS 18 + * + * @author Federico Tomassetti + */ +public class InferenceVariable implements ResolvedType { + private static int unnamedInstantiated = 0; + + private String name; + private ResolvedTypeParameterDeclaration typeParameterDeclaration; + + public static List instantiate(List typeParameterDeclarations) { + List inferenceVariables = new LinkedList<>(); + for (ResolvedTypeParameterDeclaration tp : typeParameterDeclarations) { + inferenceVariables.add(InferenceVariable.unnamed(tp)); + } + return inferenceVariables; + } + + public static InferenceVariable unnamed(ResolvedTypeParameterDeclaration typeParameterDeclaration) { + return new InferenceVariable("__unnamed__" + (unnamedInstantiated++), typeParameterDeclaration); + } + + public InferenceVariable(String name, ResolvedTypeParameterDeclaration typeParameterDeclaration) { + this.name = name; + this.typeParameterDeclaration = typeParameterDeclaration; + } + + @Override + public boolean isInferenceVariable() { + return true; + } + + @Override + public String describe() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + InferenceVariable that = (InferenceVariable) o; + + if (!name.equals(that.name)) return false; + return typeParameterDeclaration != null ? typeParameterDeclaration.equals(that.typeParameterDeclaration) + : that.typeParameterDeclaration == null; + } + + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + (typeParameterDeclaration != null ? typeParameterDeclaration.hashCode() : 0); + return result; + } + + @Override + public boolean isAssignableBy(ResolvedType other) { + if (other.equals(this)) { + return true; + } + throw new UnsupportedOperationException( + "We are unable to determine the assignability of an inference variable without knowing the bounds and" + + " constraints"); + } + + public ResolvedTypeParameterDeclaration getTypeParameterDeclaration() { + if (typeParameterDeclaration == null) { + throw new IllegalStateException("The type parameter declaration was not specified"); + } + return typeParameterDeclaration; + } + + @Override + public String toString() { + return "InferenceVariable{" + + "name='" + name + '\'' + + ", typeParameterDeclaration=" + typeParameterDeclaration + + '}'; + } + + @Override + public boolean mention(List typeParameters) { + return false; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/InferenceVariableSubstitution.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/InferenceVariableSubstitution.java new file mode 100644 index 0000000000000000000000000000000000000000..42fc05dd797c0b00bc6e4c9208431eec732e5011 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/InferenceVariableSubstitution.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference; + +import com.github.javaparser.resolution.types.ResolvedType; + +import java.util.LinkedList; +import java.util.List; + +/** + * @author Federico Tomassetti + */ +public class InferenceVariableSubstitution { + + private final static InferenceVariableSubstitution EMPTY = new InferenceVariableSubstitution(); + + private List inferenceVariables; + private List types; + + public static InferenceVariableSubstitution empty() { + return EMPTY; + } + + private InferenceVariableSubstitution() { + this.inferenceVariables = new LinkedList<>(); + this.types = new LinkedList<>(); + } + + public InferenceVariableSubstitution withPair(InferenceVariable inferenceVariable, ResolvedType type) { + InferenceVariableSubstitution newInstance = new InferenceVariableSubstitution(); + newInstance.inferenceVariables.addAll(this.inferenceVariables); + newInstance.types.addAll(this.types); + newInstance.inferenceVariables.add(inferenceVariable); + newInstance.types.add(type); + return newInstance; + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/Instantiation.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/Instantiation.java new file mode 100644 index 0000000000000000000000000000000000000000..e941c317f9fb0d6a44cf7469b6be4f9795f1c879 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/Instantiation.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference; + + +import com.github.javaparser.resolution.types.ResolvedType; + +/** + * @author Federico Tomassetti + */ +public class Instantiation { + private InferenceVariable inferenceVariable; + private ResolvedType properType; + + public Instantiation(InferenceVariable inferenceVariable, ResolvedType properType) { + this.inferenceVariable = inferenceVariable; + this.properType = properType; + } + + public InferenceVariable getInferenceVariable() { + return inferenceVariable; + } + + public ResolvedType getProperType() { + return properType; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Instantiation that = (Instantiation) o; + + if (!inferenceVariable.equals(that.inferenceVariable)) return false; + return properType.equals(that.properType); + } + + @Override + public int hashCode() { + int result = inferenceVariable.hashCode(); + result = 31 * result + properType.hashCode(); + return result; + } + + @Override + public String toString() { + return "Instantiation{" + + "inferenceVariable=" + inferenceVariable + + ", properType=" + properType + + '}'; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/InstantiationSet.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/InstantiationSet.java new file mode 100644 index 0000000000000000000000000000000000000000..6bf491037ec48837b27e905ce889675490f2f3f1 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/InstantiationSet.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference; + +import com.github.javaparser.resolution.types.ResolvedType; + +import java.util.LinkedList; +import java.util.List; + +/** + * @author Federico Tomassetti + */ +public class InstantiationSet { + + private List instantiations; + + public boolean allInferenceVariablesAreResolved(BoundSet boundSet) { + throw new UnsupportedOperationException(); + } + + public static InstantiationSet empty() { + return EMPTY; + } + + private static final InstantiationSet EMPTY = new InstantiationSet(); + + private InstantiationSet() { + instantiations = new LinkedList<>(); + } + + public InstantiationSet withInstantiation(Instantiation instantiation) { + InstantiationSet newInstance = new InstantiationSet(); + newInstance.instantiations.addAll(this.instantiations); + newInstance.instantiations.add(instantiation); + return newInstance; + } + + public boolean isEmpty() { + return instantiations.isEmpty(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + InstantiationSet that = (InstantiationSet) o; + + return instantiations.equals(that.instantiations); + } + + @Override + public int hashCode() { + return instantiations.hashCode(); + } + + @Override + public String toString() { + return "InstantiationSet{" + + "instantiations=" + instantiations + + '}'; + } + + public ResolvedType apply(ResolvedType type) { + for (Instantiation instantiation : instantiations) { + type = type.replaceTypeVariables(instantiation.getInferenceVariable().getTypeParameterDeclaration(), instantiation.getProperType()); + } + return type; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/MethodType.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/MethodType.java new file mode 100644 index 0000000000000000000000000000000000000000..8d4b26d28e2d8d40942afa5dd901499a05d7b6ed --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/MethodType.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference; + +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; + +import java.util.List; + +/** + * A MethodType is an ordered 4-tuple consisting of: + * 1. type parameters: the declarations of any type parameters of the method member. + * 2. argument types: a list of the types of the arguments to the method member. + * 3. return type: the return type of the method member. + * 4. throws clause: exception types declared in the throws clause of the method member. + * + * See JLS 8.2 + * + * @author Federico Tomassetti + */ +public class MethodType { + private List typeParameters; + private List formalArgumentTypes; + private ResolvedType returnType; + private List exceptionTypes; + + public static MethodType fromMethodUsage(MethodUsage methodUsage) { + return new MethodType(methodUsage.getDeclaration().getTypeParameters(), methodUsage.getParamTypes(), + methodUsage.returnType(), methodUsage.exceptionTypes()); + } + + public MethodType(List typeParameters, List formalArgumentTypes, + ResolvedType returnType, + List exceptionTypes) { + this.typeParameters = typeParameters; + this.formalArgumentTypes = formalArgumentTypes; + this.returnType = returnType; + this.exceptionTypes = exceptionTypes; + } + + public List getTypeParameters() { + return typeParameters; + } + + public List getFormalArgumentTypes() { + return formalArgumentTypes; + } + + public ResolvedType getReturnType() { + return returnType; + } + + public List getExceptionTypes() { + return exceptionTypes; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/ProperLowerBound.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/ProperLowerBound.java new file mode 100644 index 0000000000000000000000000000000000000000..f2551367dd5a40f4ab4e0bf8bbdf001e346ddb3e --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/ProperLowerBound.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference; + +import com.github.javaparser.resolution.types.ResolvedType; + +/** + * @author Federico Tomassetti + */ +public class ProperLowerBound { + private InferenceVariable inferenceVariable; + private ResolvedType properType; + + public ProperLowerBound(InferenceVariable inferenceVariable, ResolvedType properType) { + this.inferenceVariable = inferenceVariable; + this.properType = properType; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ProperLowerBound that = (ProperLowerBound) o; + + if (!inferenceVariable.equals(that.inferenceVariable)) return false; + return properType.equals(that.properType); + } + + @Override + public int hashCode() { + int result = inferenceVariable.hashCode(); + result = 31 * result + properType.hashCode(); + return result; + } + + @Override + public String toString() { + return "ProperLowerBound{" + + "inferenceVariable=" + inferenceVariable + + ", properType=" + properType + + '}'; + } + + public InferenceVariable getInferenceVariable() { + return inferenceVariable; + } + + public ResolvedType getProperType() { + return properType; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/ProperUpperBound.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/ProperUpperBound.java new file mode 100644 index 0000000000000000000000000000000000000000..a229f481bacef4c5afc9f8f0e6105a0132fdc94e --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/ProperUpperBound.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference; + +import com.github.javaparser.resolution.types.ResolvedType; + +/** + * @author Federico Tomassetti + */ +public class ProperUpperBound { + private InferenceVariable inferenceVariable; + private ResolvedType properType; + + public ProperUpperBound(InferenceVariable inferenceVariable, ResolvedType properType) { + this.inferenceVariable = inferenceVariable; + this.properType = properType; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ProperUpperBound that = (ProperUpperBound) o; + + if (!inferenceVariable.equals(that.inferenceVariable)) return false; + return properType.equals(that.properType); + } + + @Override + public int hashCode() { + int result = inferenceVariable.hashCode(); + result = 31 * result + properType.hashCode(); + return result; + } + + @Override + public String toString() { + return "ProperUpperBound{" + + "inferenceVariable=" + inferenceVariable + + ", properType=" + properType + + '}'; + } + + public InferenceVariable getInferenceVariable() { + return inferenceVariable; + } + + public ResolvedType getProperType() { + return properType; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/Substitution.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/Substitution.java new file mode 100644 index 0000000000000000000000000000000000000000..2f6959b4619fa1f0989b67d75fe1da12b2d11049 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/Substitution.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference; + +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; + +import java.util.LinkedList; +import java.util.List; + +/** + * @author Federico Tomassetti + */ +public class Substitution { + + private List typeParameterDeclarations; + private List types; + + private final static Substitution EMPTY = new Substitution(); + + public static Substitution empty() { + return EMPTY; + } + + public Substitution withPair(ResolvedTypeParameterDeclaration typeParameterDeclaration, ResolvedType type) { + Substitution newInstance = new Substitution(); + newInstance.typeParameterDeclarations.addAll(this.typeParameterDeclarations); + newInstance.types.addAll(this.types); + newInstance.typeParameterDeclarations.add(typeParameterDeclaration); + newInstance.types.add(type); + return newInstance; + + } + + private Substitution() { + this.typeParameterDeclarations = new LinkedList<>(); + this.types = new LinkedList<>(); + } + + public ResolvedType apply(ResolvedType originalType) { + ResolvedType result = originalType; + for (int i=0;i isProperType(it)); + } + if (type instanceof ResolvedWildcard) { + ResolvedWildcard wildcard = (ResolvedWildcard)type; + if (wildcard.isBounded()) { + return isProperType(wildcard.getBoundedType()); + } else { + return true; + } + } + if (type.isPrimitive()) { + return true; + } + if (type.isTypeVariable()) { + // FIXME I am not sure... + return false; + } + if (type.isArray()) { + return isProperType(type.asArrayType().getComponentType()); + } + throw new UnsupportedOperationException(type.toString()); + } + + /** + * see https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.3 + * @param expression + * @param t + * @return + */ + public static boolean isCompatibleInAStrictInvocationContext(Expression expression, ResolvedType t) { + throw new UnsupportedOperationException(); + } + + /** + * see https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.3 + * @param expression + * @param t + * @return + */ + public static boolean isCompatibleInALooseInvocationContext(TypeSolver typeSolver, Expression expression, ResolvedType t) { + //throw new UnsupportedOperationException("Unable to determine if " + expression + " is compatible in a loose invocation context with type " + t); + return isCompatibleInALooseInvocationContext(JavaParserFacade.get(typeSolver).getType(expression), t); + } + + /** + * see https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.3 + * @param s + * @param t + * @return + */ + public static boolean isCompatibleInALooseInvocationContext(ResolvedType s, ResolvedType t) { + // Loose invocation contexts allow a more permissive set of conversions, because they are only used for a + // particular invocation if no applicable declaration can be found using strict invocation contexts. Loose + // invocation contexts allow the use of one of the following: + // + // - an identity conversion (§5.1.1) + + if (s.equals(t)) { + return true; + } + + // - a widening primitive conversion (§5.1.2) + + if (s.isPrimitive() && t.isPrimitive() && areCompatibleThroughWideningPrimitiveConversion(s, t)) { + return true; + } + + // - a widening reference conversion (§5.1.5) + + if (s.isReferenceType() && t.isReferenceType() && areCompatibleThroughWideningReferenceConversion(s, t)) { + return true; + } + + // - a boxing conversion (§5.1.7) optionally followed by widening reference conversion + + if (s.isPrimitive() && t.isReferenceType() && + areCompatibleThroughWideningReferenceConversion(toBoxedType(s.asPrimitive()), t)) { + return true; + } + + // - an unboxing conversion (§5.1.8) optionally followed by a widening primitive conversion + + if (s.isReferenceType() && s.asReferenceType().isUnboxable() && t.isPrimitive() && + areCompatibleThroughWideningPrimitiveConversion(s.asReferenceType().toUnboxedType().get(), t)) { + return true; + } + + // If, after the conversions listed for an invocation context have been applied, the resulting type is a raw + // type (§4.8), an unchecked conversion (§5.1.9) may then be applied. + // + // A value of the null type (the null reference is the only such value) may be assigned to any reference type + if (s.isNull() && t.isReferenceType()) { + return true; + } + + //throw new UnsupportedOperationException("isCompatibleInALooseInvocationContext unable to decide on s=" + s + ", t=" + t); + // TODO FIXME + return t.isAssignableBy(s); + } + + public static ResolvedType toBoxedType(ResolvedPrimitiveType primitiveType) { + throw new UnsupportedOperationException(); + } + + // get the resolved boxed type of the specified primitive type + public static ResolvedType toBoxedType(ResolvedPrimitiveType primitiveType, TypeSolver typeSolver ) { + SymbolReference typeDeclaration = typeSolver.tryToSolveType(primitiveType.getBoxTypeQName()); + return new ReferenceTypeImpl(typeDeclaration.getCorrespondingDeclaration(), typeSolver); + } + + public static boolean areCompatibleThroughWideningReferenceConversion(ResolvedType s, ResolvedType t) { + Optional correspondingPrimitiveTypeForS = Arrays.stream(ResolvedPrimitiveType.values()).filter(pt -> pt.getBoxTypeQName().equals(s.asReferenceType().getQualifiedName())).findFirst(); + if (!correspondingPrimitiveTypeForS.isPresent()) { + return false; + } + throw new UnsupportedOperationException("areCompatibleThroughWideningReferenceConversion s="+s+", t=" + t); + } + + public static boolean areCompatibleThroughWideningPrimitiveConversion(ResolvedType s, ResolvedType t) { + if (s.isPrimitive() && t.isPrimitive()) { + return s.isAssignableBy(t); + } else { + return false; + } + } + + public static Set usedInferenceVariables(ResolvedType type) { + if (type.isInferenceVariable()) { + return new HashSet<>(Arrays.asList((InferenceVariable) type)); + } + if (type.isReferenceType()) { + Set res = new HashSet<>(); + for (ResolvedType tp : type.asReferenceType().typeParametersValues()) { + res.addAll(usedInferenceVariables(tp)); + } + return res; + } + throw new UnsupportedOperationException(type.toString()); + } + + /** + * See JLS 4.10.4. Least Upper Bound. + */ + public static ResolvedType leastUpperBound(Set types) { + if (types.isEmpty()) { + throw new IllegalArgumentException(); + } + + // The least upper bound, or "lub", of a set of reference types is a shared supertype that is more specific than + // any other shared supertype (that is, no other shared supertype is a subtype of the least upper bound). + // This type, lub(U1, ..., Uk), is determined as follows. + // + // If k = 1, then the lub is the type itself: lub(U) = U. + + if (types.size() == 1) { + return types.stream().findFirst().get(); + } + + // + //Otherwise: + // + //For each Ui (1 ≤ i ≤ k): + // + //Let ST(Ui) be the set of supertypes of Ui. + // + //Let EST(Ui), the set of erased supertypes of Ui, be: + // + //EST(Ui) = { |W| | W in ST(Ui) } where |W| is the erasure of W. + // + //The reason for computing the set of erased supertypes is to deal with situations where the set of types includes several distinct parameterizations of a generic type. + // + //For example, given List and List, simply intersecting the sets ST(List) = { List, Collection, Object } and ST(List) = { List, Collection, Object } would yield a set { Object }, and we would have lost track of the fact that the upper bound can safely be assumed to be a List. + // + //In contrast, intersecting EST(List) = { List, Collection, Object } and EST(List) = { List, Collection, Object } yields { List, Collection, Object }, which will eventually enable us to produce List. + // + //Let EC, the erased candidate set for U1 ... Uk, be the intersection of all the sets EST(Ui) (1 ≤ i ≤ k). + // + //Let MEC, the minimal erased candidate set for U1 ... Uk, be: + // + //MEC = { V | V in EC, and for all W ≠ V in EC, it is not the case that W <: V } + // + //Because we are seeking to infer more precise types, we wish to filter out any candidates that are supertypes of other candidates. This is what computing MEC accomplishes. In our running example, we had EC = { List, Collection, Object }, so MEC = { List }. The next step is to recover type arguments for the erased types in MEC. + // + //For any element G of MEC that is a generic type: + // + //Let the "relevant" parameterizations of G, Relevant(G), be: + // + //Relevant(G) = { V | 1 ≤ i ≤ k: V in ST(Ui) and V = G<...> } + // + //In our running example, the only generic element of MEC is List, and Relevant(List) = { List, List }. We will now seek to find a type argument for List that contains (§4.5.1) both String and Object. + // + //This is done by means of the least containing parameterization (lcp) operation defined below. The first line defines lcp() on a set, such as Relevant(List), as an operation on a list of the elements of the set. The next line defines the operation on such lists, as a pairwise reduction on the elements of the list. The third line is the definition of lcp() on pairs of parameterized types, which in turn relies on the notion of least containing type argument (lcta). lcta() is defined for all possible cases. + // + //Let the "candidate" parameterization of G, Candidate(G), be the most specific parameterization of the generic type G that contains all the relevant parameterizations of G: + // + //Candidate(G) = lcp(Relevant(G)) + // + //where lcp(), the least containing invocation, is: + // + //lcp(S) = lcp(e1, ..., en) where ei (1 ≤ i ≤ n) in S + // + //lcp(e1, ..., en) = lcp(lcp(e1, e2), e3, ..., en) + // + //lcp(G, G) = G + // + //lcp(G) = G + // + //and where lcta(), the least containing type argument, is: (assuming U and V are types) + // + //lcta(U, V) = U if U = V, otherwise ? extends lub(U, V) + // + //lcta(U, ? extends V) = ? extends lub(U, V) + // + //lcta(U, ? super V) = ? super glb(U, V) + // + //lcta(? extends U, ? extends V) = ? extends lub(U, V) + // + //lcta(? extends U, ? super V) = U if U = V, otherwise ? + // + //lcta(? super U, ? super V) = ? super glb(U, V) + // + //lcta(U) = ? if U's upper bound is Object, otherwise ? extends lub(U,Object) + // + //and where glb() is as defined in §5.1.10. + // + //Let lub(U1 ... Uk) be: + // + //Best(W1) & ... & Best(Wr) + // + //where Wi (1 ≤ i ≤ r) are the elements of MEC, the minimal erased candidate set of U1 ... Uk; + // + //and where, if any of these elements are generic, we use the candidate parameterization (so as to recover type arguments): + // + //Best(X) = Candidate(X) if X is generic; X otherwise. + // + //Strictly speaking, this lub() function only approximates a least upper bound. Formally, there may exist some other type T such that all of U1 ... Uk are subtypes of T and T is a subtype of lub(U1, ..., Uk). However, a compiler for the Java programming language must implement lub() as specified above. + // + //It is possible that the lub() function yields an infinite type. This is permissible, and a compiler for the Java programming language must recognize such situations and represent them appropriately using cyclic data structures. + // + //The possibility of an infinite type stems from the recursive calls to lub(). Readers familiar with recursive types should note that an infinite type is not the same as a recursive type + throw new UnsupportedOperationException(); + } + + /** + * See JLS 15.27.3. Type of a Lambda Expression + * @return + */ + public static Pair groundTargetTypeOfLambda(LambdaExpr lambdaExpr, ResolvedType T, TypeSolver typeSolver) { + // The ground target type is derived from T as follows: + // + boolean used18_5_3 = false; + + boolean wildcardParameterized = T.asReferenceType().typeParametersValues().stream() + .anyMatch(tp -> tp.isWildcard()); + if (wildcardParameterized) { + // - If T is a wildcard-parameterized functional interface type and the lambda expression is explicitly typed, + // then the ground target type is inferred as described in §18.5.3. + + if (ExpressionHelper.isExplicitlyTyped(lambdaExpr)) { + used18_5_3 = true; + throw new UnsupportedOperationException(); + } + + // - If T is a wildcard-parameterized functional interface type and the lambda expression is implicitly typed, + // then the ground target type is the non-wildcard parameterization (§9.9) of T. + + else { + return new Pair<>(nonWildcardParameterizationOf(T.asReferenceType(), typeSolver), used18_5_3); + } + } + + // - Otherwise, the ground target type is T. + return new Pair<>(T, used18_5_3); + } + + /** + * See JLS 9.9 + */ + private static ResolvedReferenceType nonWildcardParameterizationOf(ResolvedReferenceType originalType, TypeSolver typeSolver) { + ResolvedReferenceTypeDeclaration originalTypeDeclaration = originalType.getTypeDeclaration().orElseThrow(() -> new RuntimeException("TypeDeclaration unexpectedly empty.")); + + List TIs = new LinkedList<>(); + List AIs = originalType.typeParametersValues(); + List TPs = originalTypeDeclaration.getTypeParameters(); + + // Let P1...Pn be the type parameters of I with corresponding bounds B1...Bn. For all i (1 ≤ i ≤ n), + // Ti is derived according to the form of Ai: + + ResolvedReferenceType object = new ReferenceTypeImpl(typeSolver.getSolvedJavaLangObject(), typeSolver); + + for (int i=0;i(Arrays.asList(Ui, Bi))); + } + + // - If Ai is a lower-bounded wildcard ? super Li, then Ti = Li. + + else if (Ai.isWildcard() && Ai.asWildcard().isLowerBounded()) { + Ti = Ai.asWildcard().getBoundedType(); + } + + else { + throw new RuntimeException("This should not happen"); + } + } + + TIs.add(Ti); + } + + return new ReferenceTypeImpl(originalTypeDeclaration, TIs, typeSolver); + } + + public static MethodType getFunctionType(ResolvedType type) { + Optional mu = FunctionalInterfaceLogic.getFunctionalMethod(type); + if (mu.isPresent()) { + return MethodType.fromMethodUsage(mu.get()); + } else { + throw new IllegalArgumentException(); + } + } + + /** + * See JLS 5.1.10. Capture Conversion. + */ + public static ResolvedType glb(Set types) { + if (types.isEmpty()) { + throw new IllegalArgumentException(); + } + if (types.size() == 1) { + return types.iterator().next(); + } + return new ResolvedIntersectionType(types); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/TypeInference.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/TypeInference.java new file mode 100644 index 0000000000000000000000000000000000000000..ff916612d10a7089ff94651968a32b3147d44040 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/TypeInference.java @@ -0,0 +1,738 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference; + +import com.github.javaparser.ast.expr.*; +import com.github.javaparser.ast.type.UnknownType; +import com.github.javaparser.resolution.MethodUsage; +import com.github.javaparser.resolution.declarations.ResolvedInterfaceDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.symbolsolver.resolution.typeinference.bounds.SubtypeOfBound; +import com.github.javaparser.symbolsolver.resolution.typeinference.bounds.ThrowsBound; +import com.github.javaparser.symbolsolver.resolution.typeinference.constraintformulas.ExpressionCompatibleWithType; + +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; + +/** + * The API exposed by the TypeInference subsystem. + * + * @author Federico Tomassetti + */ +public class TypeInference { + + private final ResolvedType object; + private TypeSolver typeSolver; + + public TypeInference(TypeSolver typeSolver) { + if (typeSolver == null) { + throw new NullPointerException(); + } + this.typeSolver = typeSolver; + this.object = new ReferenceTypeImpl(typeSolver.getSolvedJavaLangObject(), typeSolver); + } + + /// + /// Public static methods + /// + + public static MethodUsage toMethodUsage(MethodCallExpr call, ResolvedMethodDeclaration methodDeclaration, TypeSolver typeSolver) { + TypeInference typeInference = new TypeInference(typeSolver); + Optional instantiationSetOpt = typeInference.instantiationInference(call, methodDeclaration); + if (instantiationSetOpt.isPresent()) { + return instantiationSetToMethodUsage(methodDeclaration, instantiationSetOpt.get()); + } else { + throw new IllegalArgumentException(); + } + } + + /// + /// Public instance methods + /// + + public Optional instantiationInference(MethodCallExpr methodCallExpr, ResolvedMethodDeclaration methodDeclaration) { + return instantiationInference(methodCallExpr.getArguments(), methodDeclaration); + } + + public Optional instantiationInference(List argumentExpressions, ResolvedMethodDeclaration methodDeclaration) { +// if (methodCallExpr.getTypeArguments().isPresent()) { +// throw new IllegalArgumentException("Type inference unnecessary as type arguments have been specified"); +// } + + // Given a method invocation that provides no explicit type arguments, the process to determine whether a + // potentially applicable generic method m is applicable is as follows: + + // - Where P1, ..., Pp (p ≥ 1) are the type parameters of m, let α1, ..., αp be inference variables, and + // let θ be the substitution [P1:=α1, ..., Pp:=αp]. + + List Ps = methodDeclaration.getTypeParameters(); + List alphas = InferenceVariable.instantiate(Ps); + Substitution theta = Substitution.empty(); + for (int i=0;i Fs = formalParameterTypes(methodDeclaration); + List es = argumentExpressions; + + Optional C = Optional.empty(); + + // - To test for applicability by strict invocation: + + if (!C.isPresent()) { + C = testForApplicabilityByStrictInvocation(Fs, es, theta); + } + + // - To test for applicability by loose invocation: + + if (!C.isPresent()) { + C = testForApplicabilityByLooseInvocation(Fs, es, theta); + } + + // - To test for applicability by variable arity invocation: + + if (!C.isPresent()) { + C = testForApplicabilityByVariableArityInvocation(Fs, es, theta); + } + + if (!C.isPresent()) { + return Optional.empty(); + } + + // - C is reduced (§18.2) and the resulting bounds are incorporated with B1 to produce a new bound set, B2. + + BoundSet resultingBounds = C.get().reduce(typeSolver); + BoundSet B2 = B1.incorporate(resultingBounds, typeSolver); + + // - Finally, the method m is applicable if B2 does not contain the bound false and resolution of all the + // inference variables in B2 succeeds (§18.4). + + if (B2.containsFalse()) { + return Optional.empty(); + } + + Optional instantiation = B2.performResolution(alphas, typeSolver); + return instantiation; + } + + /** + * Determine whether a potentially applicable generic method m is applicable for a method invocation that + * provides no explicit type arguments. + */ + public boolean invocationApplicabilityInference(MethodCallExpr methodCallExpr, ResolvedMethodDeclaration methodDeclaration) { + if (!methodCallExpr.getNameAsString().equals(methodDeclaration.getName())) { + throw new IllegalArgumentException(); + } + Optional partial = instantiationInference(methodCallExpr, methodDeclaration); + if (!partial.isPresent()) { + return false; + } + int nActualParams = methodCallExpr.getArguments().size(); + int nFormalParams = methodDeclaration.getNumberOfParams(); + if (nActualParams != nFormalParams) { + if (methodDeclaration.hasVariadicParameter()) { + if (nActualParams < (nFormalParams - 1)) { + return false; + } + } else { + return false; + } + } + //MethodUsage methodUsage = instantiationSetToMethodUsage(methodDeclaration, partial.get()); +// for (int i=0;i= nFormalParams ? nFormalParams - 1 : i; +// Type formalType = methodDeclaration.getParam(formalIndex).getType(); +// Type actualType = JavaParserFacade.get(typeSolver).getType(methodCallExpr.getArgument(i)); +// //if (!formalType.isAssignableBy(actualType)) { +// // return false; +// //} +// } + return true; + } + + public BoundSet invocationTypeInferenceBoundsSetB3() { + // Given a method invocation that provides no explicit type arguments, and a corresponding most specific + // applicable generic method m, the process to infer the invocation type (§15.12.2.6) of the chosen method is + // as follows: + // + // - Let θ be the substitution [P1:=α1, ..., Pp:=αp] defined in §18.5.1 to replace the type parameters of m with inference variables. + // + // - Let B2 be the bound set produced by reduction in order to demonstrate that m is applicable in §18.5.1. (While it was necessary in §18.5.1 to demonstrate that the inference variables in B2 could be resolved, in order to establish applicability, the instantiations produced by this resolution step are not considered part of B2.) + // + // - If the invocation is not a poly expression, let the bound set B3 be the same as B2. + // + // If the invocation is a poly expression, let the bound set B3 be derived from B2 as follows. Let R be the + // return type of m, let T be the invocation's target type, and then: + // + // - If unchecked conversion was necessary for the method to be applicable during constraint set reduction + // in §18.5.1, the constraint formula ‹|R| → T› is reduced and incorporated with B2. + // + // - Otherwise, if R θ is a parameterized type, G, and one of A1, ..., An is a wildcard, then, + // for fresh inference variables β1, ..., βn, the constraint formula ‹G<β1, ..., βn> → T› is reduced and + // incorporated, along with the bound G<β1, ..., βn> = capture(G), with B2. + // + // - Otherwise, if R θ is an inference variable α, and one of the following is true: + // + // - T is a reference type, but is not a wildcard-parameterized type, and either i) B2 contains a bound of + // one of the forms α = S or S <: α, where S is a wildcard-parameterized type, or ii) B2 contains two + // bounds of the forms S1 <: α and S2 <: α, where S1 and S2 have supertypes that are two different + // parameterizations of the same generic class or interface. + // + // - T is a parameterization of a generic class or interface, G, and B2 contains a bound of one of the + // forms α = S or S <: α, where there exists no type of the form G<...> that is a supertype of S, but the + // raw type |G<...>| is a supertype of S. + // + // - T is a primitive type, and one of the primitive wrapper classes mentioned in §5.1.7 is an + // instantiation, upper bound, or lower bound for α in B2. + // + // then α is resolved in B2, and where the capture of the resulting instantiation of α is U, the constraint + // formula ‹U → T› is reduced and incorporated with B2. + // + // - Otherwise, the constraint formula ‹R θ → T› is reduced and incorporated with B2. + throw new UnsupportedOperationException(); + } + + public void invocationTypeInference() { + BoundSet B3 = invocationTypeInferenceBoundsSetB3(); + // + //A set of constraint formulas, C, is constructed as follows. + // + // Let e1, ..., ek be the actual argument expressions of the invocation. If m is applicable by strict or loose invocation, let F1, ..., Fk be the formal parameter types of m; if m is applicable by variable arity invocation, let F1, ..., Fk the first k variable arity parameter types of m (§15.12.2.4). Then: + // + //For all i (1 ≤ i ≤ k), if ei is not pertinent to applicability, C contains ‹ei → Fi θ›. + // + //For all i (1 ≤ i ≤ k), additional constraints may be included, depending on the form of ei: + // + //If ei is a LambdaExpression, C contains ‹LambdaExpression →throws Fi θ›. + // + //In addition, the lambda body is searched for additional constraints: + // + //For a block lambda body, the search is applied recursively to each result expression. + // + //For a poly class instance creation expression (§15.9) or a poly method invocation expression (§15.12), C contains all the constraint formulas that would appear in the set C generated by §18.5.2 when inferring the poly expression's invocation type. + // + //For a parenthesized expression, the search is applied recursively to the contained expression. + // + //For a conditional expression, the search is applied recursively to the second and third operands. + // + //For a lambda expression, the search is applied recursively to the lambda body. + // + //If ei is a MethodReference, C contains ‹MethodReference →throws Fi θ›. + // + //If ei is a poly class instance creation expression (§15.9) or a poly method invocation expression (§15.12), C contains all the constraint formulas that would appear in the set C generated by §18.5.2 when inferring the poly expression's invocation type. + // + //If ei is a parenthesized expression, these rules are applied recursively to the contained expression. + // + //If ei is a conditional expression, these rules are applied recursively to the second and third operands. + // + //While C is not empty, the following process is repeated, starting with the bound set B3 and accumulating new bounds into a "current" bound set, ultimately producing a new bound set, B4: + // + //A subset of constraints is selected in C, satisfying the property that, for each constraint, no input variable can influence an output variable of another constraint in C. The terms input variable and output variable are defined below. An inference variable α can influence an inference variable β if α depends on the resolution of β (§18.4), or vice versa; or if there exists a third inference variable γ such that α can influence γ and γ can influence β. + // + //If this subset is empty, then there is a cycle (or cycles) in the graph of dependencies between constraints. In this case, all constraints are considered that participate in a dependency cycle (or cycles) and do not depend on any constraints outside of the cycle (or cycles). A single constraint is selected from the considered constraints, as follows: + // + //If any of the considered constraints have the form ‹Expression → T›, then the selected constraint is the considered constraint of this form that contains the expression to the left (§3.5) of the expression of every other considered constraint of this form. + // + // If no considered constraint has the form ‹Expression → T›, then the selected constraint is the considered constraint that contains the expression to the left of the expression of every other considered constraint. + // + // The selected constraint(s) are removed from C. + // + // The input variables α1, ..., αm of all the selected constraint(s) are resolved. + // + // Where T1, ..., Tm are the instantiations of α1, ..., αm, the substitution [α1:=T1, ..., αm:=Tm] is applied to every constraint. + // + // The constraint(s) resulting from substitution are reduced and incorporated with the current bound set. + // + //Finally, if B4 does not contain the bound false, the inference variables in B4 are resolved. + // + //If resolution succeeds with instantiations T1, ..., Tp for inference variables α1, ..., αp, let θ' be the substitution [P1:=T1, ..., Pp:=Tp]. Then: + // + //If unchecked conversion was necessary for the method to be applicable during constraint set reduction in §18.5.1, then the parameter types of the invocation type of m are obtained by applying θ' to the parameter types of m's type, and the return type and thrown types of the invocation type of m are given by the erasure of the return type and thrown types of m's type. + // + //If unchecked conversion was not necessary for the method to be applicable, then the invocation type of m is obtained by applying θ' to the type of m. + // + //If B4 contains the bound false, or if resolution fails, then a compile-time error occurs. + // + //Invocation type inference may require carefully sequencing the reduction of constraint formulas of the forms ‹Expression → T›, ‹LambdaExpression →throws T›, and ‹MethodReference →throws T›. To facilitate this sequencing, the input variables of these constraints are defined as follows: + // + //For ‹LambdaExpression → T›: + // + //If T is an inference variable, it is the (only) input variable. + // + // If T is a functional interface type, and a function type can be derived from T (§15.27.3), then the input variables include i) if the lambda expression is implicitly typed, the inference variables mentioned by the function type's parameter types; and ii) if the function type's return type, R, is not void, then for each result expression e in the lambda body (or for the body itself if it is an expression), the input variables of ‹e → R›. + // + //Otherwise, there are no input variables. + // + //For ‹LambdaExpression →throws T›: + // + //If T is an inference variable, it is the (only) input variable. + // + // If T is a functional interface type, and a function type can be derived, as described in §15.27.3, the input variables include i) if the lambda expression is implicitly typed, the inference variables mentioned by the function type's parameter types; and ii) the inference variables mentioned by the function type's return type. + // + // Otherwise, there are no input variables. + // + // For ‹MethodReference → T›: + // + //If T is an inference variable, it is the (only) input variable. + // + // If T is a functional interface type with a function type, and if the method reference is inexact (§15.13.1), the input variables are the inference variables mentioned by the function type's parameter types. + // + //Otherwise, there are no input variables. + // + //For ‹MethodReference →throws T›: + // + //If T is an inference variable, it is the (only) input variable. + // + // If T is a functional interface type with a function type, and if the method reference is inexact (§15.13.1), the input variables are the inference variables mentioned by the function type's parameter types and the function type's return type. + // + // Otherwise, there are no input variables. + // + // For ‹Expression → T›, if Expression is a parenthesized expression: + // + //Where the contained expression of Expression is Expression', the input variables are the input variables of ‹Expression' → T›. + // + //For ‹ConditionalExpression → T›: + // + //Where the conditional expression has the form e1 ? e2 : e3, the input variables are the input variables of ‹e2 → T› and ‹e3 → T›. + // + //For all other constraint formulas, there are no input variables. + // + //The output variables of these constraints are all inference variables mentioned by the type on the right-hand side of the constraint, T, that are not input variables. + + throw new UnsupportedOperationException(); + } + + public void functionalInterfaceParameterizationInference(LambdaExpr lambdaExpr, + ResolvedInterfaceDeclaration interfaceDeclaration) { + // Where a lambda expression with explicit parameter types P1, ..., Pn targets a functional interface + // type F with at least one wildcard type argument, then a parameterization of F may be derived + // as the ground target type of the lambda expression as follows. + + int n = lambdaExpr.getParameters().size(); + + if (interfaceDeclaration.getTypeParameters().isEmpty()) { + throw new IllegalArgumentException("Functional Interface without type arguments"); + } + + // Let Q1, ..., Qk be the parameter types of the function type of the type F<α1, ..., αm>, + // where α1, ..., αm are fresh inference variables. + + int k = interfaceDeclaration.getTypeParameters().size(); + List alphas = InferenceVariable.instantiate(interfaceDeclaration.getTypeParameters()); + + TypeInferenceCache.recordInferenceVariables(typeSolver, lambdaExpr, alphas); + + // If n ≠ k, no valid parameterization exists. + + if (n != k) { + throw new IllegalArgumentException("No valida parameterization can exist has n=" + " and k=" + k); + } + + // Otherwise, a set of constraint formulas is formed with, for + // all i (1 ≤ i ≤ n), ‹Pi = Qi›. This constraint formula set is reduced to form the bound set B. + + ConstraintFormulaSet constraintFormulaSet = ConstraintFormulaSet.empty(); + for (int i=0; i, is constructed as follows, for 1 ≤ i ≤ m: + // + // - If B contains an instantiation (§18.1.3) for αi, T, then A'i = T. + // + // - Otherwise, A'i = Ai. + // + // If F is not a well-formed type (that is, the type arguments are not within their bounds), or if F is not a subtype of F, no valid parameterization exists. Otherwise, the inferred parameterization is either F, if all the type arguments are types, or the non-wildcard parameterization (§9.9) of F, if one or more type arguments are still wildcards. + + throw new UnsupportedOperationException(); + } + + /** + * Return if m2 is more specific than m1 + * @param methodCall + * @param m1 + * @param m2 + */ + public boolean moreSpecificMethodInference(MethodCallExpr methodCall, ResolvedMethodDeclaration m1, ResolvedMethodDeclaration m2) { + // When testing that one applicable method is more specific than another (§15.12.2.5), where the second method + // is generic, it is necessary to test whether some instantiation of the second method's type parameters can be + // inferred to make the first method more specific than the second. + + if (!m2.isGeneric()) { + throw new IllegalArgumentException("M2 is not generic (m2: " + m2 + ")"); + } + + // Let m1 be the first method and m2 be the second method. Where m2 has type parameters P1, ..., Pp, + // let α1, ..., αp be inference variables, and let θ be the substitution [P1:=α1, ..., Pp:=αp]. + // + // Let e1, ..., ek be the argument expressions of the corresponding invocation. Then: + // + // - If m1 and m2 are applicable by strict or loose invocation (§15.12.2.2, §15.12.2.3), then let S1, ..., Sk be the formal parameter types of m1, and let T1, ..., Tk be the result of θ applied to the formal parameter types of m2. + // + // - If m1 and m2 are applicable by variable arity invocation (§15.12.2.4), then let S1, ..., Sk be the first k variable arity parameter types of m1, and let T1, ..., Tk be the result of θ applied to the first k variable arity parameter types of m2. + // + // Note that no substitution is applied to S1, ..., Sk; even if m1 is generic, the type parameters of m1 are treated as type variables, not inference variables. + // + // The process to determine if m1 is more specific than m2 is as follows: + // + // - First, an initial bound set, B, is constructed from the declared bounds of P1, ..., Pp, as specified in §18.1.3. + // + // - Second, for all i (1 ≤ i ≤ k), a set of constraint formulas or bounds is generated. + // + // If Ti is a proper type, the result is true if Si is more specific than Ti for ei (§15.12.2.5), and false otherwise. (Note that Si is always a proper type.) + // + // Otherwise, if Ti is not a functional interface type, the constraint formula ‹Si <: Ti› is generated. + // + // Otherwise, Ti is a parameterization of a functional interface, I. It must be determined whether Si satisfies the following five conditions: + // + // 1. Si is a functional interface type. + // + // 2. Si is not a superinterface of I, nor a parameterization of a superinterface of I. + // + // 3. Si is not a subinterface of I, nor a parameterization of a subinterface of I. + // + // 4. If Si is an intersection type, at least one element of the intersection is not a superinterface of I, nor a parameterization of a superinterface of I. + // + // 5. If Si is an intersection type, no element of the intersection is a subinterface of I, nor a parameterization of a subinterface of I. + // + // If all five conditions are true, then the following constraint formulas or bounds are generated (where U1 ... Uk and R1 are the parameter types and return type of the function type of the capture of Si, and V1 ... Vk and R2 are the parameter types and return type of the function type of Ti): + // + // - If ei is an explicitly typed lambda expression: + // + // - For all j (1 ≤ j ≤ k), ‹Uj = Vj›. + // + // - If R2 is void, true. + // + // - Otherwise, if R1 and R2 are functional interface types, and neither interface is a subinterface of the other, and ei has at least one result expression, then these rules are applied recursively to R1 and R2, for each result expression in ei. + // + // - Otherwise, if R1 is a primitive type and R2 is not, and ei has at least one result expression, and each result expression of ei is a standalone expression (§15.2) of a primitive type, true. + // + // - Otherwise, if R2 is a primitive type and R1 is not, and ei has at least one result expression, and each result expression of ei is either a standalone expression of a reference type or a poly expression, true. + // + // - Otherwise, ‹R1 <: R2›. + // + // - If ei is an exact method reference: + // + // - For all j (1 ≤ j ≤ k), ‹Uj = Vj›. + // + // - If R2 is void, true. + // + // - Otherwise, if R1 is a primitive type and R2 is not, and the compile-time declaration for ei has a primitive return type, true. + // + // - Otherwise if R2 is a primitive type and R1 is not, and the compile-time declaration for ei has a reference return type, true. + // + // - Otherwise, ‹R1 <: R2›. + // + // - If ei is a parenthesized expression, these rules are applied recursively to the contained expression. + // + // - If ei is a conditional expression, these rules are applied recursively to each of the second and third operands. + // + // - Otherwise, false. + // + // If the five constraints on Si are not satisfied, the constraint formula ‹Si <: Ti› is generated instead. + // + // - Third, if m2 is applicable by variable arity invocation and has k+1 parameters, then where Sk+1 is the k+1'th variable arity parameter type of m1 and Tk+1 is the result of θ applied to the k+1'th variable arity parameter type of m2, the constraint ‹Sk+1 <: Tk+1› is generated. + // + // - Fourth, the generated bounds and constraint formulas are reduced and incorporated with B to produce a bound set B'. + // + // If B' does not contain the bound false, and resolution of all the inference variables in B' succeeds, then m1 is more specific than m2. + // + // Otherwise, m1 is not more specific than m2. + + throw new UnsupportedOperationException(); + } + + + /// + /// Private static methods + /// + + private static MethodUsage instantiationSetToMethodUsage(ResolvedMethodDeclaration methodDeclaration, InstantiationSet instantiationSet) { + if (instantiationSet.isEmpty()) { + return new MethodUsage(methodDeclaration); + } + List paramTypes = new LinkedList<>(); + for (int i=0;i typeParameterDeclarations, List inferenceVariables) { + if (typeParameterDeclarations.size() != inferenceVariables.size()) { + throw new IllegalArgumentException(); + } + + // When inference begins, a bound set is typically generated from a list of + // type parameter declarations P1, ..., Pp and associated inference variables α1, ..., αp. + // Such a bound set is constructed as follows. For each l (1 ≤ l ≤ p): + + BoundSet boundSet = BoundSet.empty(); + + for (int l=0;l formalParameterTypes(ResolvedMethodDeclaration methodDeclaration) { + List types = new LinkedList<>(); + for (int i=0;i p.getType() instanceof UnknownType); + } + + private boolean isInexact(MethodReferenceExpr methodReferenceExpr) { + throw new UnsupportedOperationException(); + } + + private boolean isPertinentToApplicability(Expression argument) { + // An argument expression is considered pertinent to applicability for a potentially applicable method m + // unless it has one of the following forms: + // + // - An implicitly typed lambda expression (§15.27.1). + + if (argument instanceof LambdaExpr) { + LambdaExpr lambdaExpr = (LambdaExpr)argument; + if (isImplicitlyTyped(lambdaExpr)) { + return false; + } + } + + // - An inexact method reference expression (§15.13.1). + + if (argument instanceof MethodReferenceExpr) { + MethodReferenceExpr methodReferenceExpr = (MethodReferenceExpr)argument; + if (isInexact(methodReferenceExpr)) { + return false; + } + } + + // - If m is a generic method and the method invocation does not provide explicit type arguments, an + // explicitly typed lambda expression or an exact method reference expression for which the + // corresponding target type (as derived from the signature of m) is a type parameter of m. + + if (argument instanceof LambdaExpr) { + throw new UnsupportedOperationException(); + } + + if (argument instanceof MethodReferenceExpr) { + throw new UnsupportedOperationException(); + } + + // - An explicitly typed lambda expression whose body is an expression that is not pertinent to applicability. + + if (argument instanceof LambdaExpr) { + throw new UnsupportedOperationException(); + } + + // - An explicitly typed lambda expression whose body is a block, where at least one result expression is not + // pertinent to applicability. + + if (argument instanceof LambdaExpr) { + throw new UnsupportedOperationException(); + } + + // - A parenthesized expression (§15.8.5) whose contained expression is not pertinent to applicability. + + if (argument instanceof EnclosedExpr) { + EnclosedExpr enclosedExpr = (EnclosedExpr)argument; + return isPertinentToApplicability(enclosedExpr.getInner()); + } + + // - A conditional expression (§15.25) whose second or third operand is not pertinent to applicability. + + if (argument instanceof ConditionalExpr) { + ConditionalExpr conditionalExpr = (ConditionalExpr)argument; + return isPertinentToApplicability(conditionalExpr.getThenExpr()) && + isPertinentToApplicability(conditionalExpr.getElseExpr()); + } + + return true; + } + + private Optional testForApplicabilityByStrictInvocation(List Fs, List es, + Substitution theta) { + int n = Fs.size(); + int k = es.size(); + + // If k ≠ n, or if there exists an i (1 ≤ i ≤ n) such that ei is pertinent to applicability (§15.12.2.2) + // and either: + // i) ei is a standalone expression of a primitive type but Fi is a reference type, or + // ii) Fi is a primitive type but ei is not a standalone expression of a primitive type; + if (k != n) { + return Optional.empty(); + } + for (int i=0;i testForApplicabilityByLooseInvocation(List Fs, List es, + Substitution theta) { + int n = Fs.size(); + int k = es.size(); + + // If k ≠ n, the method is not applicable and there is no need to proceed with inference. + + if (k != n) { + return Optional.empty(); + } + + // Otherwise, C includes, for all i (1 ≤ i ≤ k) where ei is pertinent to applicability, ‹ei → Fi θ›. + return Optional.of(constraintSetFromArgumentsSubstitution(Fs, es, theta, k)); + } + + private ConstraintFormulaSet constraintSetFromArgumentsSubstitution(List Fs, List es, Substitution theta, int k) { + ConstraintFormulaSet constraintFormulaSet = ConstraintFormulaSet.empty(); + for (int i=0;i testForApplicabilityByVariableArityInvocation(List Fs, List es, + Substitution theta) { + int k = es.size(); + + // Let F'1, ..., F'k be the first k variable arity parameter types of m (§15.12.2.4). C includes, + // for all i (1 ≤ i ≤ k) where ei is pertinent to applicability, ‹ei → F'i θ›. + + List FsFirst = new LinkedList<>(); + for (int i=0;i>> typeForLambdaParameters = new HashMap<>(); + private static Map>> inferenceVariables = new HashMap<>(); + + public static void addRecord(TypeSolver typeSolver, LambdaExpr lambdaExpr, String paramName, ResolvedType type) { + if (!typeForLambdaParameters.containsKey(typeSolver)) { + typeForLambdaParameters.put(typeSolver, new IdentityHashMap<>()); + } + if (!typeForLambdaParameters.get(typeSolver).containsKey(lambdaExpr)) { + typeForLambdaParameters.get(typeSolver).put(lambdaExpr, new HashMap<>()); + } + typeForLambdaParameters.get(typeSolver).get(lambdaExpr).put(paramName, type); + } + + public static Optional retrieve(TypeSolver typeSolver, LambdaExpr lambdaExpr, String paramName) { + if (!typeForLambdaParameters.containsKey(typeSolver)) { + return Optional.empty(); + } + if (!typeForLambdaParameters.get(typeSolver).containsKey(lambdaExpr)) { + return Optional.empty(); + } + if (!typeForLambdaParameters.get(typeSolver).get(lambdaExpr).containsKey(paramName)) { + return Optional.empty(); + } + return Optional.of(typeForLambdaParameters.get(typeSolver).get(lambdaExpr).get(paramName)); + } + + public static void recordInferenceVariables(TypeSolver typeSolver, LambdaExpr lambdaExpr, List _inferenceVariables) { + if (!inferenceVariables.containsKey(typeSolver)) { + inferenceVariables.put(typeSolver, new IdentityHashMap<>()); + } + inferenceVariables.get(typeSolver).put(lambdaExpr, _inferenceVariables); + } + + public static Optional> retrieveInferenceVariables(TypeSolver typeSolver, LambdaExpr lambdaExpr) { + if (!inferenceVariables.containsKey(typeSolver)) { + return Optional.empty(); + } + if (!inferenceVariables.get(typeSolver).containsKey(lambdaExpr)) { + return Optional.empty(); + } + return Optional.of(inferenceVariables.get(typeSolver).get(lambdaExpr)); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/bounds/CapturesBound.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/bounds/CapturesBound.java new file mode 100644 index 0000000000000000000000000000000000000000..eef7d06f3c9e22917f3aee398789959d82030437 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/bounds/CapturesBound.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference.bounds; + +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.resolution.typeinference.Bound; +import com.github.javaparser.symbolsolver.resolution.typeinference.InferenceVariable; +import com.github.javaparser.symbolsolver.resolution.typeinference.InferenceVariableSubstitution; + +import java.util.List; +import java.util.Set; + +/** + * Capture(G<A1, ..., An>): The variables α1, ..., αn represent the result of capture conversion (§5.1.10) + * applied to G<A1, ..., An> (where A1, ..., An may be types or wildcards and may mention inference variables). + * + * @author Federico Tomassetti + */ +public class CapturesBound extends Bound { + private List inferenceVariables; + private List typesOrWildcards; + + public CapturesBound(List inferenceVariables, List typesOrWildcards) { + this.inferenceVariables = inferenceVariables; + this.typesOrWildcards = typesOrWildcards; + } + + @Override + public boolean isSatisfied(InferenceVariableSubstitution inferenceVariableSubstitution) { + throw new UnsupportedOperationException(); + } + + @Override + public Set usedInferenceVariables() { + throw new UnsupportedOperationException(); + } + + public List getInferenceVariables() { + return inferenceVariables; + } + + public List getTypesOrWildcards() { + return typesOrWildcards; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + CapturesBound that = (CapturesBound) o; + + if (!inferenceVariables.equals(that.inferenceVariables)) return false; + return typesOrWildcards.equals(that.typesOrWildcards); + } + + @Override + public int hashCode() { + int result = inferenceVariables.hashCode(); + result = 31 * result + typesOrWildcards.hashCode(); + return result; + } + + @Override + public String toString() { + return "CapturesBound{" + + "inferenceVariables=" + inferenceVariables + + ", typesOrWildcards=" + typesOrWildcards + + '}'; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/bounds/FalseBound.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/bounds/FalseBound.java new file mode 100644 index 0000000000000000000000000000000000000000..1221ae11cba68367e645e2fdda98ca2ac36db645 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/bounds/FalseBound.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference.bounds; + +import com.github.javaparser.symbolsolver.resolution.typeinference.Bound; +import com.github.javaparser.symbolsolver.resolution.typeinference.InferenceVariable; +import com.github.javaparser.symbolsolver.resolution.typeinference.InferenceVariableSubstitution; + +import java.util.Collections; +import java.util.Set; + +/** + * No valid choice of inference variables exists. + * + * @author Federico Tomassetti + */ +public class FalseBound extends Bound { + + private static FalseBound INSTANCE = new FalseBound(); + + private FalseBound() { + + } + + public static FalseBound getInstance() { + return INSTANCE; + } + + @Override + public String toString() { + return "FalseBound{}"; + } + + @Override + public boolean isSatisfied(InferenceVariableSubstitution inferenceVariableSubstitution) { + return false; + } + + @Override + public Set usedInferenceVariables() { + return Collections.emptySet(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/bounds/SameAsBound.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/bounds/SameAsBound.java new file mode 100644 index 0000000000000000000000000000000000000000..7874d4957de1951acb88faec81de49b0c703059c --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/bounds/SameAsBound.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference.bounds; + +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.resolution.typeinference.*; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import static com.github.javaparser.symbolsolver.resolution.typeinference.TypeHelper.isProperType; + +/** + * S = T, where at least one of S or T is an inference variable: S is the same as T. + * + * @author Federico Tomassetti + */ +public class SameAsBound extends Bound { + private ResolvedType s; + private ResolvedType t; + + public SameAsBound(ResolvedType s, ResolvedType t) { + if (!s.isInferenceVariable() && !t.isInferenceVariable()) { + throw new IllegalArgumentException("One of S or T should be an inference variable"); + } + this.s = s; + this.t = t; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SameAsBound that = (SameAsBound) o; + + if (!s.equals(that.s)) return false; + return t.equals(that.t); + } + + @Override + public String toString() { + return "SameAsBound{" + + "s=" + s + + ", t=" + t + + '}'; + } + + @Override + public int hashCode() { + int result = s.hashCode(); + result = 31 * result + t.hashCode(); + return result; + } + + @Override + public Set usedInferenceVariables() { + Set variables = new HashSet<>(); + variables.addAll(TypeHelper.usedInferenceVariables(s)); + variables.addAll(TypeHelper.usedInferenceVariables(t)); + return variables; + } + + public ResolvedType getS() { + return s; + } + + public ResolvedType getT() { + return t; + } + + @Override + public boolean isADependency() { + return !isAnInstantiation().isPresent(); + } + + @Override + public Optional isAnInstantiation() { + if (s.isInferenceVariable() && isProperType(t)) { + return Optional.of(new Instantiation((InferenceVariable) s, t)); + } + if (isProperType(s) && t.isInferenceVariable()) { + return Optional.of(new Instantiation((InferenceVariable) t, s)); + } + return Optional.empty(); + } + + @Override + public boolean isSatisfied(InferenceVariableSubstitution inferenceVariableSubstitution) { + throw new UnsupportedOperationException(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/bounds/SubtypeOfBound.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/bounds/SubtypeOfBound.java new file mode 100644 index 0000000000000000000000000000000000000000..fa3cfd18c03e3a81ece974522e27107d25f82888 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/bounds/SubtypeOfBound.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference.bounds; + +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.resolution.typeinference.*; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import static com.github.javaparser.symbolsolver.resolution.typeinference.TypeHelper.isProperType; + +/** + * S <: T, where at least one of S or T is an inference variable: S is a subtype of T + * + * @author Federico Tomassetti + */ +public class SubtypeOfBound extends Bound { + private ResolvedType s; + private ResolvedType t; + + public SubtypeOfBound(ResolvedType s, ResolvedType t) { + if (!s.isInferenceVariable() && !t.isInferenceVariable()) { + throw new IllegalArgumentException("One of S or T should be an inference variable"); + } + this.s = s; + this.t = t; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SubtypeOfBound that = (SubtypeOfBound) o; + + if (!s.equals(that.s)) return false; + return t.equals(that.t); + } + + @Override + public String toString() { + return "SubtypeOfBound{" + + "s=" + s + + ", t=" + t + + '}'; + } + + @Override + public int hashCode() { + int result = s.hashCode(); + result = 31 * result + t.hashCode(); + return result; + } + + public ResolvedType getS() { + return s; + } + + @Override + public Set usedInferenceVariables() { + Set variables = new HashSet<>(); + variables.addAll(TypeHelper.usedInferenceVariables(s)); + variables.addAll(TypeHelper.usedInferenceVariables(t)); + return variables; + } + + public ResolvedType getT() { + return t; + } + + @Override + public Optional isProperUpperBound() { + if (s.isInferenceVariable() && isProperType(t)) { + return Optional.of(new ProperUpperBound((InferenceVariable) s, t)); + } + return Optional.empty(); + } + + @Override + public Optional isProperLowerBound() { + if (isProperType(s) && t.isInferenceVariable()) { + return Optional.of(new ProperLowerBound((InferenceVariable) t, s)); + } + return Optional.empty(); + } + + @Override + public boolean isADependency() { + return !isProperLowerBound().isPresent() && !isProperUpperBound().isPresent(); + } + + @Override + public boolean isSatisfied(InferenceVariableSubstitution inferenceVariableSubstitution) { + throw new UnsupportedOperationException(); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/bounds/ThrowsBound.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/bounds/ThrowsBound.java new file mode 100644 index 0000000000000000000000000000000000000000..88fce701779fed8bd4d671d79268721f00977e1e --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/bounds/ThrowsBound.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference.bounds; + +import com.github.javaparser.symbolsolver.resolution.typeinference.Bound; +import com.github.javaparser.symbolsolver.resolution.typeinference.InferenceVariable; +import com.github.javaparser.symbolsolver.resolution.typeinference.InferenceVariableSubstitution; + +import java.util.HashSet; +import java.util.Set; + +/** + * The inference variable α appears in a throws clause. + * + * A bound of the form throws α is purely informational: it directs resolution to optimize the instantiation of α so + * that, if possible, it is not a checked exception type. + * + * @author Federico Tomassetti + */ +public class ThrowsBound extends Bound { + private InferenceVariable inferenceVariable; + + public ThrowsBound(InferenceVariable inferenceVariable) { + this.inferenceVariable = inferenceVariable; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ThrowsBound that = (ThrowsBound) o; + + return inferenceVariable.equals(that.inferenceVariable); + } + + @Override + public String toString() { + return "ThrowsBound{" + + "inferenceVariable=" + inferenceVariable + + '}'; + } + + @Override + public int hashCode() { + return inferenceVariable.hashCode(); + } + + @Override + public Set usedInferenceVariables() { + Set variables = new HashSet<>(); + variables.add(inferenceVariable); + return variables; + } + + @Override + public boolean isSatisfied(InferenceVariableSubstitution inferenceVariableSubstitution) { + throw new UnsupportedOperationException(); + } + + public boolean isThrowsBoundOn(InferenceVariable inferenceVariable) { + return inferenceVariable.equals(this.inferenceVariable); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/constraintformulas/ExpressionCompatibleWithType.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/constraintformulas/ExpressionCompatibleWithType.java new file mode 100644 index 0000000000000000000000000000000000000000..876e4aac8bcc0de39a0ca9a87144d235a00562ac --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/constraintformulas/ExpressionCompatibleWithType.java @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference.constraintformulas; + +import com.github.javaparser.ast.expr.*; +import com.github.javaparser.ast.stmt.BlockStmt; +import com.github.javaparser.ast.stmt.ExpressionStmt; +import com.github.javaparser.ast.stmt.ReturnStmt; +import com.github.javaparser.ast.stmt.Statement; +import com.github.javaparser.ast.type.UnknownType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.resolution.types.ResolvedTypeVariable; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.logic.FunctionalInterfaceLogic; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.resolution.typeinference.*; +import com.github.javaparser.utils.Pair; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import static com.github.javaparser.symbolsolver.resolution.typeinference.TypeHelper.isCompatibleInALooseInvocationContext; +import static com.github.javaparser.symbolsolver.resolution.typeinference.TypeHelper.isProperType; +import static java.util.stream.Collectors.toList; + +/** + * An expression is compatible in a loose invocation context with type T + * + * @author Federico Tomassetti + */ +public class ExpressionCompatibleWithType extends ConstraintFormula { + private TypeSolver typeSolver; + private Expression expression; + private ResolvedType T; + + public ExpressionCompatibleWithType(TypeSolver typeSolver, Expression expression, ResolvedType T) { + this.typeSolver = typeSolver; + this.expression = expression; + this.T = T; + } + + @Override + public ReductionResult reduce(BoundSet currentBoundSet) { + // If T is a proper type, the constraint reduces to true if the expression is compatible in a loose + // invocation context with T (§5.3), and false otherwise. + + if (isProperType(T)) { + if (isCompatibleInALooseInvocationContext(typeSolver, expression, T)) { + return ReductionResult.trueResult(); + } else { + return ReductionResult.falseResult(); + } + } + + // Otherwise, if the expression is a standalone expression (§15.2) of type S, the constraint reduces + // to ‹S → T›. + + if (expression.isStandaloneExpression()) { + ResolvedType s = JavaParserFacade.get(typeSolver).getType(expression, false); + return ReductionResult.empty().withConstraint(new TypeCompatibleWithType(typeSolver, s, T)); + } + + // Otherwise, the expression is a poly expression (§15.2). The result depends on the form of the expression: + + if (expression.isPolyExpression()) { + + // - If the expression is a parenthesized expression of the form ( Expression' ), the constraint reduces + // to ‹Expression' → T›. + + if (expression instanceof EnclosedExpr) { + EnclosedExpr enclosedExpr = (EnclosedExpr)expression; + return ReductionResult.oneConstraint(new ExpressionCompatibleWithType(typeSolver, enclosedExpr.getInner(), T)); + } + + // - If the expression is a class instance creation expression or a method invocation expression, the + // constraint reduces to the bound set B3 which would be used to determine the expression's invocation + // type when targeting T, as defined in §18.5.2. (For a class instance creation expression, the + // corresponding "method" used for inference is defined in §15.9.3). + // + // This bound set may contain new inference variables, as well as dependencies between these new + // variables and the inference variables in T. + + if (expression instanceof ObjectCreationExpr) { + BoundSet B3 = new TypeInference(typeSolver).invocationTypeInferenceBoundsSetB3(); + return ReductionResult.bounds(B3); + } + + if (expression instanceof MethodCallExpr) { + throw new UnsupportedOperationException(); + } + + // - If the expression is a conditional expression of the form e1 ? e2 : e3, the constraint reduces to two + // constraint formulas, ‹e2 → T› and ‹e3 → T›. + + if (expression instanceof ConditionalExpr) { + ConditionalExpr conditionalExpr = (ConditionalExpr)expression; + return ReductionResult.withConstraints( + new ExpressionCompatibleWithType(typeSolver, conditionalExpr.getThenExpr(), T), + new ExpressionCompatibleWithType(typeSolver, conditionalExpr.getElseExpr(), T)); + } + + // - If the expression is a lambda expression or a method reference expression, the result is specified + // below. + + // A constraint formula of the form ‹LambdaExpression → T›, where T mentions at least one inference variable, is reduced as follows: + + if (expression instanceof LambdaExpr) { + LambdaExpr lambdaExpr = (LambdaExpr)expression; + + // - If T is not a functional interface type (§9.8), the constraint reduces to false. + + if (!FunctionalInterfaceLogic.isFunctionalInterfaceType(T)) { + return ReductionResult.falseResult(); + } + + // - Otherwise, let T' be the ground target type derived from T, as specified in §15.27.3. If §18.5.3 + // is used to derive a functional interface type which is parameterized, then the test that + // F is a subtype of F is not performed (instead, it is asserted with a + // constraint formula below). Let the target function type for the lambda expression be the + // function type of T'. Then: + + Pair result = TypeHelper.groundTargetTypeOfLambda(lambdaExpr, T, typeSolver); + ResolvedType TFirst = result.a; + MethodType targetFunctionType = TypeHelper.getFunctionType(TFirst); + targetFunctionType = replaceTypeVariablesWithInferenceVariables(targetFunctionType); + if (result.b) { + throw new UnsupportedOperationException(); + } + + // - If no valid function type can be found, the constraint reduces to false. + // + // Federico: THIS SHOULD NOT HAPPEN, IN CASE WE WILL THROW AN EXCEPTION + // + // - Otherwise, the congruence of LambdaExpression with the target function type is asserted as + // follows: + // + // - If the number of lambda parameters differs from the number of parameter types of the function + // type, the constraint reduces to false. + + if (targetFunctionType.getFormalArgumentTypes().size() != lambdaExpr.getParameters().size()) { + return ReductionResult.falseResult(); + } + + // - If the lambda expression is implicitly typed and one or more of the function type's parameter + // types is not a proper type, the constraint reduces to false. + // + // This condition never arises in practice, due to the handling of implicitly typed lambda + // expressions in §18.5.1 and the substitution applied to the target type in §18.5.2. + + // - If the function type's result is void and the lambda body is neither a statement expression + // nor a void-compatible block, the constraint reduces to false. + + if (targetFunctionType.getReturnType().isVoid()) { + throw new UnsupportedOperationException(); + } + + // - If the function type's result is not void and the lambda body is a block that is not + // value-compatible, the constraint reduces to false. + + if (!targetFunctionType.getReturnType().isVoid() && lambdaExpr.getBody() instanceof BlockStmt + && !isValueCompatibleBlock(lambdaExpr.getBody())) { + return ReductionResult.falseResult(); + } + + // - Otherwise, the constraint reduces to all of the following constraint formulas: + List constraints = new LinkedList<>(); + + // - If the lambda parameters have explicitly declared types F1, ..., Fn and the function type + // has parameter types G1, ..., Gn, then i) for all i (1 ≤ i ≤ n), ‹Fi = Gi›, and ii) ‹T' <: T›. + + boolean hasExplicitlyDeclaredTypes = lambdaExpr.getParameters().stream().anyMatch(p -> !(p.getType() instanceof UnknownType)); + if (hasExplicitlyDeclaredTypes) { + throw new UnsupportedOperationException(); + } + + // - If the function type's return type is a (non-void) type R, assume the lambda's parameter + // types are the same as the function type's parameter types. Then: + + if (!targetFunctionType.getReturnType().isVoid()) { + + ResolvedType R = targetFunctionType.getReturnType(); + + if (TypeHelper.isProperType(R)) { + + // - If R is a proper type, and if the lambda body or some result expression in the lambda body + // is not compatible in an assignment context with R, then false. + + if (lambdaExpr.getBody() instanceof BlockStmt) { + List resultExpressions = ExpressionHelper.getResultExpressions((BlockStmt)lambdaExpr.getBody()); + for (Expression e : resultExpressions) { + if (!ExpressionHelper.isCompatibleInAssignmentContext(e, R, typeSolver)) { + return ReductionResult.falseResult(); + } + } + } else { + Expression e = ((ExpressionStmt)lambdaExpr.getBody()).getExpression(); + if (!ExpressionHelper.isCompatibleInAssignmentContext(e, R, typeSolver)) { + return ReductionResult.falseResult(); + } + } + } else { + // - Otherwise, if R is not a proper type, then where the lambda body has the form Expression, + // the constraint ‹Expression → R›; or where the lambda body is a block with result + // expressions e1, ..., em, for all i (1 ≤ i ≤ m), ‹ei → R›. + + if (lambdaExpr.getBody() instanceof BlockStmt) { + getAllReturnExpressions((BlockStmt)lambdaExpr.getBody()).forEach(e -> constraints.add(new ExpressionCompatibleWithType(typeSolver, e, R))); + } else { + // FEDERICO: Added - Start + for (int i=0;i getAllReturnExpressions(BlockStmt blockStmt) { + return blockStmt.findAll(ReturnStmt.class).stream() + .filter(r -> r.getExpression().isPresent()) + .map(r -> r.getExpression().get()) + .collect(toList()); + } + + private boolean isValueCompatibleBlock(Statement statement) { + // A block lambda body is value-compatible if it cannot complete normally (§14.21) and every return statement + // in the block has the form return Expression;. + + if (statement instanceof BlockStmt) { + if (!ControlFlowLogic.getInstance().canCompleteNormally(statement)) { + return true; + } + List returnStmts = statement.findAll(ReturnStmt.class); + return returnStmts.stream().allMatch(r -> r.getExpression().isPresent()); + } + return false; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ExpressionCompatibleWithType that = (ExpressionCompatibleWithType) o; + + if (!typeSolver.equals(that.typeSolver)) return false; + if (!expression.equals(that.expression)) return false; + return T.equals(that.T); + } + + @Override + public int hashCode() { + int result = typeSolver.hashCode(); + result = 31 * result + expression.hashCode(); + result = 31 * result + T.hashCode(); + return result; + } + + @Override + public String toString() { + return "ExpressionCompatibleWithType{" + + "typeSolver=" + typeSolver + + ", expression=" + expression + + ", T=" + T + + '}'; + } + + private MethodType replaceTypeVariablesWithInferenceVariables(MethodType methodType) { + // Find all type variable + Map correspondences = new HashMap<>(); + List newFormalArgumentTypes = new LinkedList<>(); + for (ResolvedType formalArg : methodType.getFormalArgumentTypes()) { + newFormalArgumentTypes.add(replaceTypeVariablesWithInferenceVariables(formalArg, correspondences)); + } + ResolvedType newReturnType = replaceTypeVariablesWithInferenceVariables(methodType.getReturnType(), correspondences); + return new MethodType(methodType.getTypeParameters(), newFormalArgumentTypes, newReturnType, methodType.getExceptionTypes()); + } + + private ResolvedType replaceTypeVariablesWithInferenceVariables(ResolvedType originalType, Map correspondences) { + if (originalType.isTypeVariable()) { + if (!correspondences.containsKey(originalType.asTypeVariable())) { + correspondences.put(originalType.asTypeVariable(), InferenceVariable.unnamed(originalType.asTypeVariable().asTypeParameter())); + } + return correspondences.get(originalType.asTypeVariable()); + } + if (originalType.isPrimitive()) { + return originalType; + } + throw new UnsupportedOperationException(originalType.toString()); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/constraintformulas/LambdaThrowsCompatibleWithType.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/constraintformulas/LambdaThrowsCompatibleWithType.java new file mode 100644 index 0000000000000000000000000000000000000000..1a3dadf80e0d66891fa1c4845a3bc67bf05b43d9 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/constraintformulas/LambdaThrowsCompatibleWithType.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference.constraintformulas; + +import com.github.javaparser.ast.expr.LambdaExpr; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.resolution.typeinference.BoundSet; +import com.github.javaparser.symbolsolver.resolution.typeinference.ConstraintFormula; + +/** + * The checked exceptions thrown by the body of the LambdaExpression are declared by the throws clause of the + * function type derived from T. + * + * @author Federico Tomassetti + */ +public class LambdaThrowsCompatibleWithType extends ConstraintFormula { + private LambdaExpr lambdaExpression; + private ResolvedType T; + + @Override + public ReductionResult reduce(BoundSet currentBoundSet) { + // A constraint formula of the form ‹LambdaExpression →throws T› is reduced as follows: + // + // - If T is not a functional interface type (§9.8), the constraint reduces to false. + // + // - Otherwise, let the target function type for the lambda expression be determined as specified in §15.27.3. If no valid function type can be found, the constraint reduces to false. + // + // - Otherwise, if the lambda expression is implicitly typed, and one or more of the function type's parameter types is not a proper type, the constraint reduces to false. + // + // This condition never arises in practice, due to the substitution applied to the target type in §18.5.2. + // + // - Otherwise, if the function type's return type is neither void nor a proper type, the constraint reduces to false. + // + // This condition never arises in practice, due to the substitution applied to the target type in §18.5.2. + // + // - Otherwise, let E1, ..., En be the types in the function type's throws clause that are not proper types. If the lambda expression is implicitly typed, let its parameter types be the function type's parameter types. If the lambda body is a poly expression or a block containing a poly result expression, let the targeted return type be the function type's return type. Let X1, ..., Xm be the checked exception types that the lambda body can throw (§11.2). Then there are two cases: + // + // - If n = 0 (the function type's throws clause consists only of proper types), then if there exists some i (1 ≤ i ≤ m) such that Xi is not a subtype of any proper type in the throws clause, the constraint reduces to false; otherwise, the constraint reduces to true. + // + // - If n > 0, the constraint reduces to a set of subtyping constraints: for all i (1 ≤ i ≤ m), if Xi is not a subtype of any proper type in the throws clause, then the constraints include, for all j (1 ≤ j ≤ n), ‹Xi <: Ej›. In addition, for all j (1 ≤ j ≤ n), the constraint reduces to the bound throws Ej. + throw new UnsupportedOperationException(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + LambdaThrowsCompatibleWithType that = (LambdaThrowsCompatibleWithType) o; + + if (!lambdaExpression.equals(that.lambdaExpression)) return false; + return T.equals(that.T); + } + + @Override + public int hashCode() { + int result = lambdaExpression.hashCode(); + result = 31 * result + T.hashCode(); + return result; + } + + @Override + public String toString() { + return "LambdaThrowsCompatibleWithType{" + + "lambdaExpression=" + lambdaExpression + + ", T=" + T + + '}'; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/constraintformulas/MethodReferenceThrowsCompatibleWithType.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/constraintformulas/MethodReferenceThrowsCompatibleWithType.java new file mode 100644 index 0000000000000000000000000000000000000000..caae0adc011934d20b715bab15e64b5de8bc4f7b --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/constraintformulas/MethodReferenceThrowsCompatibleWithType.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference.constraintformulas; + +import com.github.javaparser.ast.expr.MethodReferenceExpr; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.resolution.typeinference.BoundSet; +import com.github.javaparser.symbolsolver.resolution.typeinference.ConstraintFormula; + +/** + * The checked exceptions thrown by the referenced method are declared by the throws clause of the function type + * derived from T. + * + * @author Federico Tomassetti + */ +public class MethodReferenceThrowsCompatibleWithType extends ConstraintFormula { + private MethodReferenceExpr methodReference; + private ResolvedType T; + + @Override + public ReductionResult reduce(BoundSet currentBoundSet) { + // A constraint formula of the form ‹MethodReference →throws T› is reduced as follows: + // + // - If T is not a functional interface type, or if T is a functional interface type but does not have a function type (§9.9), the constraint reduces to false. + // + // - Otherwise, let the target function type for the method reference expression be the function type of T. If the method reference is inexact (§15.13.1) and one or more of the function type's parameter types is not a proper type, the constraint reduces to false. + // + // - Otherwise, if the method reference is inexact and the function type's result is neither void nor a proper type, the constraint reduces to false. + // + // - Otherwise, let E1, ..., En be the types in the function type's throws clause that are not proper types. Let X1, ..., Xm be the checked exceptions in the throws clause of the invocation type of the method reference's compile-time declaration (§15.13.2) (as derived from the function type's parameter types and return type). Then there are two cases: + // + // - If n = 0 (the function type's throws clause consists only of proper types), then if there exists some i (1 ≤ i ≤ m) such that Xi is not a subtype of any proper type in the throws clause, the constraint reduces to false; otherwise, the constraint reduces to true. + // + // - If n > 0, the constraint reduces to a set of subtyping constraints: for all i (1 ≤ i ≤ m), if Xi is not a subtype of any proper type in the throws clause, then the constraints include, for all j (1 ≤ j ≤ n), ‹Xi <: Ej›. In addition, for all j (1 ≤ j ≤ n), the constraint reduces to the bound throws Ej. + + throw new UnsupportedOperationException(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + MethodReferenceThrowsCompatibleWithType that = (MethodReferenceThrowsCompatibleWithType) o; + + if (!methodReference.equals(that.methodReference)) return false; + return T.equals(that.T); + } + + @Override + public int hashCode() { + int result = methodReference.hashCode(); + result = 31 * result + T.hashCode(); + return result; + } + + @Override + public String toString() { + return "MethodReferenceThrowsCompatibleWithType{" + + "methodReference=" + methodReference + + ", T=" + T + + '}'; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/constraintformulas/TypeCompatibleWithType.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/constraintformulas/TypeCompatibleWithType.java new file mode 100644 index 0000000000000000000000000000000000000000..f2f428c45ae6a2a2feb0749002c7f267b12b401c --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/constraintformulas/TypeCompatibleWithType.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference.constraintformulas; + +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.symbolsolver.resolution.typeinference.BoundSet; +import com.github.javaparser.symbolsolver.resolution.typeinference.ConstraintFormula; +import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver; + +import static com.github.javaparser.symbolsolver.resolution.typeinference.TypeHelper.isCompatibleInALooseInvocationContext; +import static com.github.javaparser.symbolsolver.resolution.typeinference.TypeHelper.isProperType; + +/** + * A type S is compatible in a loose invocation context with type T + * + * @author Federico Tomassetti + */ +public class TypeCompatibleWithType extends ConstraintFormula { + private ResolvedType s; + private ResolvedType t; + private TypeSolver typeSolver; + + public TypeCompatibleWithType(TypeSolver typeSolver, ResolvedType s, ResolvedType t) { + this.typeSolver = typeSolver; + this.s = s; + this.t = t; + } + + @Override + public ReductionResult reduce(BoundSet currentBoundSet) { + // A constraint formula of the form ‹S → T› is reduced as follows: + // + // 1. If S and T are proper types, the constraint reduces to true if S is compatible in a loose invocation context with T (§5.3), and false otherwise. + + if (isProperType(s) && isProperType(t)) { + if (isCompatibleInALooseInvocationContext(s, t)) { + return ReductionResult.trueResult(); + } else { + return ReductionResult.falseResult(); + } + } + + // 2. Otherwise, if S is a primitive type, let S' be the result of applying boxing conversion (§5.1.7) to S. Then the constraint reduces to ‹S' → T›. + + if (s.isPrimitive()) { + ReflectionTypeSolver typeSolver = new ReflectionTypeSolver(); + ResolvedType sFirst = new ReferenceTypeImpl(typeSolver.solveType(s.asPrimitive().getBoxTypeQName()), typeSolver); + return ReductionResult.oneConstraint(new TypeCompatibleWithType(typeSolver, sFirst, t)); + } + + // 3. Otherwise, if T is a primitive type, let T' be the result of applying boxing conversion (§5.1.7) to T. Then the constraint reduces to ‹S = T'›. + + if (t.isPrimitive()) { + ReflectionTypeSolver typeSolver = new ReflectionTypeSolver(); + ResolvedType tFirst = new ReferenceTypeImpl(typeSolver.solveType(t.asPrimitive().getBoxTypeQName()), typeSolver); + return ReductionResult.oneConstraint(new TypeSameAsType(s, tFirst)); + } + + // The fourth and fifth cases are implicit uses of unchecked conversion (§5.1.9). These, along with any use of + // unchecked conversion in the first case, may result in compile-time unchecked warnings, and may influence a + // method's invocation type (§15.12.2.6). + + // 4. Otherwise, if T is a parameterized type of the form G, and there exists no type of the + // form G<...> that is a supertype of S, but the raw type G is a supertype of S, then the constraint reduces + // to true. + + if (t.isReferenceType() && t.asReferenceType().getTypeDeclaration().isPresent() && !t.asReferenceType().getTypeDeclaration().get().getTypeParameters().isEmpty()) { + // FIXME I really cannot understand what the specification means... + + // there exists a type of the form G<...> that is a supertype of S? + boolean condition1 = t.isAssignableBy(s); + + // the raw type G is a supertype of S + ResolvedType G = t.asReferenceType().toRawType(); + boolean condition2 = G.isAssignableBy(s); + + if (!condition1 && condition2) { + return ReductionResult.trueResult(); + } + + //throw new UnsupportedOperationException(); + } + + // 5. Otherwise, if T is an array type of the form G[]k, and there exists no type of the form + // G<...>[]k that is a supertype of S, but the raw type G[]k is a supertype of S, then the constraint + // reduces to true. (The notation []k indicates an array type of k dimensions.) + + if (t.isArray()) { + throw new UnsupportedOperationException(); + } + + // 6. Otherwise, the constraint reduces to ‹S <: T› + + return ReductionResult.empty().withConstraint(new TypeSubtypeOfType(typeSolver, s, t)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TypeCompatibleWithType that = (TypeCompatibleWithType) o; + + if (!s.equals(that.s)) return false; + return t.equals(that.t); + } + + @Override + public int hashCode() { + int result = s.hashCode(); + result = 31 * result + t.hashCode(); + return result; + } + + @Override + public String toString() { + return "TypeCompatibleWithType{" + + "s=" + s + + ", t=" + t + + '}'; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/constraintformulas/TypeContainedByType.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/constraintformulas/TypeContainedByType.java new file mode 100644 index 0000000000000000000000000000000000000000..af6db7566511e8aac899324132e0e241f335c0fb --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/constraintformulas/TypeContainedByType.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference.constraintformulas; + +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.resolution.typeinference.BoundSet; +import com.github.javaparser.symbolsolver.resolution.typeinference.ConstraintFormula; + +import static com.github.javaparser.symbolsolver.resolution.typeinference.TypeHelper.isProperType; + +/** + * A type argument S is contained by a type argument T + * + * @author Federico Tomassetti + */ +public class TypeContainedByType extends ConstraintFormula { + private ResolvedType S; + private ResolvedType T; + + @Override + public ReductionResult reduce(BoundSet currentBoundSet) { + // A constraint formula of the form ‹S <= T›, where S and T are type arguments (§4.5.1), is reduced as follows: + // + // - If T is a type: + + if (isProperType(T) && !T.isWildcard()) { + + // - If S is a type, the constraint reduces to ‹S = T›. + // + // - If S is a wildcard, the constraint reduces to false. + + throw new UnsupportedOperationException(); + } + + // - If T is a wildcard of the form ?, the constraint reduces to true. + + if (T.isWildcard() && !T.asWildcard().isBounded()) { + return ReductionResult.trueResult(); + } + + // - If T is a wildcard of the form ? extends T': + + if (T.isWildcard() && T.asWildcard().isExtends()) { + + // - If S is a type, the constraint reduces to ‹S <: T'›. + // + // - If S is a wildcard of the form ?, the constraint reduces to ‹Object <: T'›. + // + // - If S is a wildcard of the form ? extends S', the constraint reduces to ‹S' <: T'›. + // + // - If S is a wildcard of the form ? super S', the constraint reduces to ‹Object = T'›. + + throw new UnsupportedOperationException(); + } + + // - If T is a wildcard of the form ? super T': + + if (T.isWildcard() && T.asWildcard().isSuper()) { + + // - If S is a type, the constraint reduces to ‹T' <: S›. + // + // - If S is a wildcard of the form ? super S', the constraint reduces to ‹T' <: S'›. + // + // - Otherwise, the constraint reduces to false. + + throw new UnsupportedOperationException(); + } + + throw new UnsupportedOperationException(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TypeContainedByType that = (TypeContainedByType) o; + + if (!S.equals(that.S)) return false; + return T.equals(that.T); + } + + @Override + public int hashCode() { + int result = S.hashCode(); + result = 31 * result + T.hashCode(); + return result; + } + + @Override + public String toString() { + return "TypeContainedByType{" + + "S=" + S + + ", T=" + T + + '}'; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/constraintformulas/TypeSameAsType.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/constraintformulas/TypeSameAsType.java new file mode 100644 index 0000000000000000000000000000000000000000..2db4f566b4c9906bb37b9c021b4fc525d2b9e244 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/constraintformulas/TypeSameAsType.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference.constraintformulas; + +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.resolution.typeinference.BoundSet; +import com.github.javaparser.symbolsolver.resolution.typeinference.ConstraintFormula; +import com.github.javaparser.symbolsolver.resolution.typeinference.bounds.SameAsBound; + +import java.util.List; + +import static com.github.javaparser.symbolsolver.resolution.typeinference.TypeHelper.isProperType; + +/** + * A type S is the same as a type T (§4.3.4), or a type argument S is the same as type argument T + * + * @author Federico Tomassetti + */ +public class TypeSameAsType extends ConstraintFormula { + private ResolvedType S; + private ResolvedType T; + + public TypeSameAsType(ResolvedType s, ResolvedType t) { + S = s; + T = t; + } + + @Override + public ReductionResult reduce(BoundSet currentBoundSet) { + // A constraint formula of the form ‹S = T›, where S and T are types, is reduced as follows: + + if (!S.isWildcard() && !T.isWildcard()) { + + // - If S and T are proper types, the constraint reduces to true if S is the same as T (§4.3.4), and false + // otherwise. + + if (isProperType(S) && isProperType(T)) { + if (S.equals(T)) { + return ReductionResult.trueResult(); + } else { + return ReductionResult.falseResult(); + } + } + + // - Otherwise, if S or T is the null type, the constraint reduces to false. + + if (S.isNull() || T.isNull()) { + return ReductionResult.falseResult(); + } + + // - Otherwise, if S is an inference variable, α, and T is not a primitive type, the constraint reduces to the + // bound α = T. + + if (S.isInferenceVariable() && !T.isPrimitive()) { + return ReductionResult.oneBound(new SameAsBound(S, T)); + } + + // - Otherwise, if T is an inference variable, α, and S is not a primitive type, the constraint reduces to the + // bound S = α. + + if (T.isInferenceVariable() && !S.isPrimitive()) { + return ReductionResult.oneBound(new SameAsBound(S, T)); + } + + // - Otherwise, if S and T are class or interface types with the same erasure, where S has + // type arguments B1, ..., Bn and T has type arguments A1, ..., An, the constraint reduces to the following + // new constraints: for all i (1 ≤ i ≤ n), ‹Bi = Ai›. + + if (S.isReferenceType() && T.isReferenceType() + && S.asReferenceType().toRawType().equals(T.asReferenceType().toRawType())) { + ReductionResult res = ReductionResult.empty(); + List Bs = S.asReferenceType().typeParametersValues(); + List As = T.asReferenceType().typeParametersValues(); + for (int i = 0; i < Bs.size(); i++) { + res = res.withConstraint(new TypeSameAsType(Bs.get(i), As.get(i))); + } + return res; + } + + // - Otherwise, if S and T are array types, S'[] and T'[], the constraint reduces to ‹S' = T'›. + + if (S.isArray() && T.isArray()) { + return ReductionResult.oneConstraint(new TypeSameAsType( + S.asArrayType().getComponentType(), + T.asArrayType().getComponentType())); + } + + // - Otherwise, the constraint reduces to false. + + return ReductionResult.falseResult(); + } + + // Note that we do not address intersection types above, because it is impossible for reduction to encounter an + // intersection type that is not a proper type. + + // A constraint formula of the form ‹S = T›, where S and T are type arguments (§4.5.1), is reduced as follows: + // + // - If S and T are types, the constraint is reduced as described above. + // + // - If S has the form ? and T has the form ?, the constraint reduces to true. + // + // - If S has the form ? and T has the form ? extends T', the constraint reduces to ‹Object = T'›. + // + // - If S has the form ? extends S' and T has the form ?, the constraint reduces to ‹S' = Object›. + // + // - If S has the form ? extends S' and T has the form ? extends T', the constraint reduces to ‹S' = T'›. + // + // - If S has the form ? super S' and T has the form ? super T', the constraint reduces to ‹S' = T'›. + // + // - Otherwise, the constraint reduces to false. + + + throw new UnsupportedOperationException(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TypeSameAsType that = (TypeSameAsType) o; + + if (!S.equals(that.S)) return false; + return T.equals(that.T); + } + + @Override + public int hashCode() { + int result = S.hashCode(); + result = 31 * result + T.hashCode(); + return result; + } + + @Override + public String toString() { + return "TypeSameAsType{" + + "S=" + S + + ", T=" + T + + '}'; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/constraintformulas/TypeSubtypeOfType.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/constraintformulas/TypeSubtypeOfType.java new file mode 100644 index 0000000000000000000000000000000000000000..16d2afe038f251dccfe6372de1159203bcbd3235 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typeinference/constraintformulas/TypeSubtypeOfType.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typeinference.constraintformulas; + +import com.github.javaparser.resolution.types.ResolvedIntersectionType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.model.typesystem.NullType; +import com.github.javaparser.symbolsolver.resolution.typeinference.BoundSet; +import com.github.javaparser.symbolsolver.resolution.typeinference.ConstraintFormula; +import com.github.javaparser.symbolsolver.resolution.typeinference.bounds.SubtypeOfBound; + +import static com.github.javaparser.symbolsolver.resolution.typeinference.TypeHelper.isProperType; + +/** + * A reference type S is a subtype of a reference type T + * + * @author Federico Tomassetti + */ +public class TypeSubtypeOfType extends ConstraintFormula { + private ResolvedType S; + private ResolvedType T; + private TypeSolver typeSolver; + + public TypeSubtypeOfType(TypeSolver typeSolver, ResolvedType S, ResolvedType T) { + this.typeSolver = typeSolver; + this.S = S; + this.T = T; + } + + @Override + public ReductionResult reduce(BoundSet currentBoundSet) { + // A constraint formula of the form ‹S <: T› is reduced as follows: + // + // - If S and T are proper types, the constraint reduces to true if S is a subtype of T (§4.10), and false otherwise. + + if (isProperType(S) && isProperType(T)) { + if (T.isAssignableBy(S)) { + return ReductionResult.trueResult(); + } else { + return ReductionResult.falseResult(); + } + } + + // - Otherwise, if S is the null type, the constraint reduces to true. + + if (S instanceof NullType) { + return ReductionResult.trueResult(); + } + + // - Otherwise, if T is the null type, the constraint reduces to false. + + if (T instanceof NullType) { + return ReductionResult.falseResult(); + } + + // - Otherwise, if S is an inference variable, α, the constraint reduces to the bound α <: T. + + if (S.isInferenceVariable()) { + return ReductionResult.oneBound(new SubtypeOfBound(S, T)); + } + + // - Otherwise, if T is an inference variable, α, the constraint reduces to the bound S <: α. + + if (T.isInferenceVariable()) { + return ReductionResult.oneBound(new SubtypeOfBound(S, T)); + } + + // FEDERICO - Added start + //if (T.isTypeVariable()) { + // return ReductionResult.oneBound(new SubtypeOfBound(S, T)); + //} + // FEDERICO - Added end + + // - Otherwise, the constraint is reduced according to the form of T: + // + // - If T is a parameterized class or interface type, or an inner class type of a parameterized class or interface type (directly or indirectly), let A1, ..., An be the type arguments of T. Among the supertypes of S, a corresponding class or interface type is identified, with type arguments B1, ..., Bn. If no such type exists, the constraint reduces to false. Otherwise, the constraint reduces to the following new constraints: for all i (1 ≤ i ≤ n), ‹Bi <= Ai›. + // + // - If T is any other class or interface type, then the constraint reduces to true if T is among the supertypes of S, and false otherwise. + // + // - If T is an array type, T'[], then among the supertypes of S that are array types, a most specific type is identified, S'[] (this may be S itself). If no such array type exists, the constraint reduces to false. Otherwise: + // + // - If neither S' nor T' is a primitive type, the constraint reduces to ‹S' <: T'›. + // + // - Otherwise, the constraint reduces to true if S' and T' are the same primitive type, and false otherwise. + // + // - If T is a type variable, there are three cases: + + if (T.isTypeVariable()) { + + // - If S is an intersection type of which T is an element, the constraint reduces to true. + + if (S instanceof ResolvedIntersectionType) { + throw new UnsupportedOperationException(); + } + + // - Otherwise, if T has a lower bound, B, the constraint reduces to ‹S <: B›. + + if (T.asTypeVariable().asTypeParameter().hasLowerBound()) { + return ReductionResult.oneConstraint(new TypeSubtypeOfType(typeSolver, S, T.asTypeVariable().asTypeParameter().getLowerBound())); + } + + // - Otherwise, the constraint reduces to false. + + return ReductionResult.falseResult(); + } + + // + // - If T is an intersection type, I1 & ... & In, the constraint reduces to the following new constraints: for all i (1 ≤ i ≤ n), ‹S <: Ii›. + // + + throw new UnsupportedOperationException("S = "+ S + ", T = " + T); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TypeSubtypeOfType that = (TypeSubtypeOfType) o; + + if (!S.equals(that.S)) return false; + return T.equals(that.T); + } + + @Override + public int hashCode() { + int result = S.hashCode(); + result = 31 * result + T.hashCode(); + return result; + } + + @Override + public String toString() { + return "TypeSubtypeOfType{" + + "S=" + S + + ", T=" + T + + '}'; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typesolvers/AarTypeSolver.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typesolvers/AarTypeSolver.java new file mode 100644 index 0000000000000000000000000000000000000000..ab18891f014064e9e563bb181738d7b963b0f74f --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typesolvers/AarTypeSolver.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typesolvers; + +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +/** + * Will let the symbol solver look inside an Android aar file while solving types. + * (It will look inside the contained classes.jar) + * + * @author Federico Tomassetti + */ +public class AarTypeSolver implements TypeSolver { + + private JarTypeSolver delegate; + + public AarTypeSolver(String aarFile) throws IOException { + this(new File(aarFile)); + } + + public AarTypeSolver(Path aarFile) throws IOException { + this(aarFile.toFile()); + } + + public AarTypeSolver(File aarFile) throws IOException { + JarFile jarFile = new JarFile(aarFile); + ZipEntry classesJarEntry = jarFile.getEntry("classes.jar"); + if (classesJarEntry == null) { + throw new IllegalArgumentException(String.format("The given file (%s) is malformed: entry classes.jar was not found", aarFile.getAbsolutePath())); + } + delegate = new JarTypeSolver(jarFile.getInputStream(classesJarEntry)); + } + + @Override + public TypeSolver getParent() { + return delegate.getParent(); + } + + @Override + public void setParent(TypeSolver parent) { + if (parent == this) + throw new IllegalStateException("The parent of this TypeSolver cannot be itself."); + + delegate.setParent(parent); + } + + @Override + public SymbolReference tryToSolveType(String name) { + return delegate.tryToSolveType(name); + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typesolvers/ClassLoaderTypeSolver.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typesolvers/ClassLoaderTypeSolver.java new file mode 100644 index 0000000000000000000000000000000000000000..c94cbfb5159b479059fff87ff42d74efe1fd200b --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typesolvers/ClassLoaderTypeSolver.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typesolvers; + +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionFactory; + +import java.util.Objects; +import java.util.Optional; + +/** + * This TypeSolver wraps a ClassLoader. It can solve all types that the given ClassLoader can load. + * This is intended to be used with custom classloaders. To support typical cases based on reflection + * just use the ReflectionTypeSolver + * + * @author Federico Tomassetti + */ +public class ClassLoaderTypeSolver implements TypeSolver { + + private TypeSolver parent; + private ClassLoader classLoader; + + public ClassLoaderTypeSolver(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + @Override + public TypeSolver getParent() { + return parent; + } + + @Override + public void setParent(TypeSolver parent) { + Objects.requireNonNull(parent); + if (this.parent != null) { + throw new IllegalStateException("This TypeSolver already has a parent."); + } + if (parent == this) { + throw new IllegalStateException("The parent of this TypeSolver cannot be itself."); + } + this.parent = parent; + } + + protected boolean filterName(String name) { + return true; + } + + @Override + public SymbolReference tryToSolveType(String name) { + if (filterName(name)) { + try { + // Some implementations could return null when the class was loaded through the bootstrap classloader + // see https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#getClassLoader-- + if (classLoader == null) { + throw new RuntimeException( + "The ClassLoaderTypeSolver has been probably loaded through the bootstrap class loader. This usage is not supported by the JavaSymbolSolver"); + } + + Class clazz = classLoader.loadClass(name); + return SymbolReference.solved(ReflectionFactory.typeDeclarationFor(clazz, getRoot())); + } catch (NoClassDefFoundError e) { + // We can safely ignore this one because it is triggered when there are package names which are almost the + // same as class name, with the exclusion of the case. + // For example: + // java.lang.NoClassDefFoundError: com/github/javaparser/printer/ConcreteSyntaxModel + // (wrong name: com/github/javaparser/printer/concretesyntaxmodel) + // note that this exception seems to be thrown only on certain platform (mac yes, linux no) + return SymbolReference.unsolved(ResolvedReferenceTypeDeclaration.class); + } catch (ClassNotFoundException e) { + // it could be an inner class + int lastDot = name.lastIndexOf('.'); + if (lastDot == -1) { + return SymbolReference.unsolved(ResolvedReferenceTypeDeclaration.class); + } else { + String parentName = name.substring(0, lastDot); + String childName = name.substring(lastDot + 1); + SymbolReference parent = tryToSolveType(parentName); + if (parent.isSolved()) { + Optional innerClass = parent.getCorrespondingDeclaration() + .internalTypes() + .stream().filter(it -> it.getName().equals(childName)).findFirst(); + return innerClass.map(SymbolReference::solved) + .orElseGet(() -> SymbolReference.unsolved(ResolvedReferenceTypeDeclaration.class)); + } else { + return SymbolReference.unsolved(ResolvedReferenceTypeDeclaration.class); + } + } + } + } else { + return SymbolReference.unsolved(ResolvedReferenceTypeDeclaration.class); + } + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typesolvers/CombinedTypeSolver.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typesolvers/CombinedTypeSolver.java new file mode 100644 index 0000000000000000000000000000000000000000..dc79f15ff74045bce50de3757ba68f918af4ca77 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typesolvers/CombinedTypeSolver.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typesolvers; + +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.symbolsolver.cache.Cache; +import com.github.javaparser.symbolsolver.cache.NoCache; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.*; +import java.util.function.Predicate; + +/** + * A container for type solvers. All solving is done by the contained type solvers. + * This helps you when an API asks for a single type solver, but you need several. + * + * @author Federico Tomassetti + */ +public class CombinedTypeSolver implements TypeSolver { + + private final Cache> typeCache; + + private TypeSolver parent; + private List elements = new ArrayList<>(); + + /** + * A predicate which determines what to do if an exception is raised during the parsing process. + * If it returns {@code true} the exception will be ignored, and solving will continue using the next solver in line. + * If it returns {@code false} the exception will be thrown, stopping the solving process. + * + * Main use case for this is to circumvent bugs or missing functionality in some type solvers. + * If for example solver A has a bug resulting in a {@link NullPointerException}, you could use a {@link ExceptionHandlers#getTypeBasedWhitelist(Class...) whitelist} to ignore that type of exception. + * A secondary solver would then be able to step in when such an error occurs. + * + * @see #CombinedTypeSolver(Predicate, TypeSolver...) + * @see #setExceptionHandler(Predicate) + */ + private Predicate exceptionHandler; + + public CombinedTypeSolver(TypeSolver... elements) { + this(Arrays.asList(elements)); + } + + public CombinedTypeSolver(Predicate exceptionHandler, TypeSolver... elements) { + this(exceptionHandler, Arrays.asList(elements)); + } + + public CombinedTypeSolver(Iterable elements) { + this(ExceptionHandlers.IGNORE_NONE, elements); + } + + /** @see #exceptionHandler */ + public CombinedTypeSolver(Predicate exceptionHandler, Iterable elements) { + this(exceptionHandler, elements, NoCache.create()); + } + + /** + * Create a new instance of {@link CombinedTypeSolver} with a custom symbol cache. + * + * @param exceptionHandler How exception should be handled. + * @param elements The list of elements to include by default. + * @param typeCache The cache to be used to store symbols. + * + * @see #exceptionHandler + */ + public CombinedTypeSolver(Predicate exceptionHandler, + Iterable elements, + Cache> typeCache) { + Objects.requireNonNull(typeCache, "The typeCache can't be null."); + + setExceptionHandler(exceptionHandler); + this.typeCache = typeCache; + + for (TypeSolver el : elements) { + add(el, false); + } + } + + /** @see #exceptionHandler */ + public void setExceptionHandler(Predicate exceptionHandler) { + this.exceptionHandler = exceptionHandler; + } + + @Override + public TypeSolver getParent() { + return parent; + } + + @Override + public void setParent(TypeSolver parent) { + Objects.requireNonNull(parent); + if (this.parent != null) { + throw new IllegalStateException("This TypeSolver already has a parent."); + } + if (parent == this) { + throw new IllegalStateException("The parent of this TypeSolver cannot be itself."); + } + this.parent = parent; + } + + /** + * Append a type solver to the current solver. + * + * @param typeSolver The type solver to be appended. + * @param resetCache If should reset the cache when the solver is inserted. + */ + public void add(TypeSolver typeSolver, boolean resetCache) { + Objects.requireNonNull(typeSolver, "The type solver can't be null"); + + this.elements.add(typeSolver); + typeSolver.setParent(this); + + // Check if the cache should be reset after inserting + if (resetCache) { + typeCache.removeAll(); + } + } + + /** + * Append a type solver to the current solver. + *
+ * By default the cached values will be removed. + * + * @param typeSolver The type solver to be appended. + */ + public void add(TypeSolver typeSolver) { + add(typeSolver, true); + } + + @Override + public SymbolReference tryToSolveType(String name) { + Optional> cachedSymbol = typeCache.get(name); + if (cachedSymbol.isPresent()) { + return cachedSymbol.get(); + } + + // If the symbol is not cached + for (TypeSolver ts : elements) { + try { + SymbolReference res = ts.tryToSolveType(name); + if (res.isSolved()) { + typeCache.put(name, res); + return res; + } + } catch (Exception e) { + if (!exceptionHandler.test(e)) { // we shouldn't ignore this exception + throw e; + } + } + } + + // When unable to solve, cache the value with unsolved symbol + SymbolReference unsolvedSymbol = SymbolReference.unsolved(ResolvedReferenceTypeDeclaration.class); + typeCache.put(name, unsolvedSymbol); + return unsolvedSymbol; + } + + @Override + public ResolvedReferenceTypeDeclaration solveType(String name) throws UnsolvedSymbolException { + SymbolReference res = tryToSolveType(name); + if (res.isSolved()) { + return res.getCorrespondingDeclaration(); + } else { + throw new UnsolvedSymbolException(name); + } + } + + /** + * Provides some convenience exception handler implementations + * @see CombinedTypeSolver#setExceptionHandler(Predicate) + */ + public static class ExceptionHandlers { + + /** Doesn't ignore any exceptions (default) */ + public static final Predicate IGNORE_NONE = e -> false; + + /** Ignores all exceptions */ + public static final Predicate IGNORE_ALL = e -> true; + + /** + * Ignores any exception that is {@link Class#isAssignableFrom(Class) assignable from} + * {@link UnsupportedOperationException}. + * + * @see #getTypeBasedWhitelist(Class...) + */ + public static final Predicate IGNORE_UNSUPPORTED_OPERATION = getTypeBasedWhitelist( + UnsupportedOperationException.class); + + /** + * Ignores any exception that is {@link Class#isAssignableFrom(Class) assignable from} + * {@link UnsolvedSymbolException}. + * + * @see #getTypeBasedWhitelist(Class...) + */ + public static final Predicate IGNORE_UNSOLVED_SYMBOL = getTypeBasedWhitelist( + UnsolvedSymbolException.class); + + /** + * Ignores any exception that is {@link Class#isAssignableFrom(Class) assignable from} either + * {@link UnsolvedSymbolException} or {@link UnsupportedOperationException}. + * + * @see #IGNORE_UNSOLVED_SYMBOL + * @see #IGNORE_UNSUPPORTED_OPERATION + * @see #getTypeBasedWhitelist(Class...) + */ + public static final Predicate IGNORE_UNSUPPORTED_AND_UNSOLVED = getTypeBasedWhitelist( + UnsupportedOperationException.class, UnsolvedSymbolException.class); + + /** + * @see CombinedTypeSolver#setExceptionHandler(Predicate) + * @see #getTypeBasedWhitelist(Class...) + * + * @return A filter that ignores an exception if none of the listed classes are + * {@link Class#isAssignableFrom(Class) assignable from} + * the thrown exception class. + */ + public static Predicate getTypeBasedBlacklist(Class... blacklist) { + return e -> { + for (Class clazz : blacklist) { + if (clazz.isAssignableFrom(e.getClass())) { + return false; + } + } + return true; + }; + } + + /** + * @see CombinedTypeSolver#setExceptionHandler(Predicate) + * @see #getTypeBasedBlacklist(Class...) + * + * @return A filter that ignores an exception if any of the listed classes are + * {@link Class#isAssignableFrom(Class) assignable from} + * the thrown exception class. + */ + public static Predicate getTypeBasedWhitelist(Class... whitelist) { + return e -> { + for (Class clazz : whitelist) { + if (clazz.isAssignableFrom(e.getClass())) { + return true; + } + } + return false; + }; + } + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typesolvers/JarTypeSolver.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typesolvers/JarTypeSolver.java new file mode 100644 index 0000000000000000000000000000000000000000..48be06a4c71b3aa137163912a1755fbecd46b84d --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typesolvers/JarTypeSolver.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typesolvers; + +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.symbolsolver.javassistmodel.JavassistFactory; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import javassist.ClassPool; +import javassist.NotFoundException; + +import java.io.*; +import java.nio.file.Path; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * Will let the symbol solver look inside a jar file while solving types. + * + * @author Federico Tomassetti + */ +public class JarTypeSolver implements TypeSolver { + + private static final String CLASS_EXTENSION = ".class"; + + /** + * @deprecated Use of this static method (previously following singleton pattern) is strongly discouraged + * and will be removed in a future version. For now, it has been modified to return a new instance to + * prevent the IllegalStateException being thrown (as reported in #2547), allowing it to be called multiple times. + */ + @Deprecated + public static JarTypeSolver getJarTypeSolver(String pathToJar) throws IOException { + return new JarTypeSolver(pathToJar); + } + + /** + * Convert the entry path into a qualified name. + * + * The entries in Jar files follows the format {@code com/github/javaparser/ASTParser$JJCalls.class} + * while in the type solver we need to work with {@code com.github.javaparser.ASTParser.JJCalls}. + * + * @param entryPath The entryPath to be converted. + * + * @return The qualified name for the entryPath. + */ + private static String convertEntryPathToClassName(String entryPath) { + if (!entryPath.endsWith(CLASS_EXTENSION)) { + throw new IllegalArgumentException(String.format("The entry path should end with %s", CLASS_EXTENSION)); + } + String className = entryPath.substring(0, entryPath.length() - CLASS_EXTENSION.length()); + className = className.replace('/', '.'); + className = className.replace('$', '.'); + return className; + } + + /** + * Convert the entry path into a qualified name to be used in {@link ClassPool}. + * + * The entries in Jar files follows the format {@code com/github/javaparser/ASTParser$JJCalls.class} + * while in the class pool we need to work with {@code com.github.javaparser.ASTParser$JJCalls}. + * + * @param entryPath The entryPath to be converted. + * + * @return The qualified name to be used in the class pool. + */ + private static String convertEntryPathToClassPoolName(String entryPath) { + if (!entryPath.endsWith(CLASS_EXTENSION)) { + throw new IllegalArgumentException(String.format("The entry path should end with %s", CLASS_EXTENSION)); + } + String className = entryPath.substring(0, entryPath.length() - CLASS_EXTENSION.length()); + return className.replace('/', '.'); + } + + private final ClassPool classPool = new ClassPool(); + private final Map knownClasses = new HashMap<>(); + + private TypeSolver parent; + + /** + * Create a {@link JarTypeSolver} from a {@link Path}. + * + * @param pathToJar The path where the jar is located. + * + * @throws IOException If an I/O exception occurs while reading the Jar. + */ + public JarTypeSolver(Path pathToJar) throws IOException { + this(pathToJar.toFile()); + } + + /** + * Create a {@link JarTypeSolver} from a {@link File}. + * + * @param pathToJar The file pointing to the jar is located. + * + * @throws IOException If an I/O exception occurs while reading the Jar. + */ + public JarTypeSolver(File pathToJar) throws IOException { + this(pathToJar.getAbsolutePath()); + } + + /** + * Create a {@link JarTypeSolver} from a path in a {@link String} format. + * + * @param pathToJar The path pointing to the jar. + * + * @throws IOException If an I/O exception occurs while reading the Jar. + */ + public JarTypeSolver(String pathToJar) throws IOException { + addPathToJar(pathToJar); + } + + /** + * Create a {@link JarTypeSolver} from a {@link InputStream}. + * + * The content will be dumped into a temporary file to be used in the type solver. + * + * @param jarInputStream The input stream to be used. + * + * @throws IOException If an I/O exception occurs while creating the temporary file. + */ + public JarTypeSolver(InputStream jarInputStream) throws IOException { + addPathToJar(dumpToTempFile(jarInputStream).getAbsolutePath()); + } + + /** + * Utility function to dump the input stream into a temporary file. + * + * This file will be deleted when the virtual machine terminates. + * + * @param inputStream The input to be dumped. + * + * @return The created file with the dumped information. + * + * @throws IOException If an I/O exception occurs while creating the temporary file. + */ + private File dumpToTempFile(InputStream inputStream) throws IOException { + File tempFile = File.createTempFile("jar_file_from_input_stream", ".jar"); + tempFile.deleteOnExit(); + + byte[] buffer = new byte[8 * 1024]; + + try (OutputStream output = new FileOutputStream(tempFile)) { + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + output.write(buffer, 0, bytesRead); + } + } finally { + inputStream.close(); + } + return tempFile; + } + + /** + * Utility method to register a new class path. + * + * @param pathToJar The path pointing to the jar file. + * + * @throws IOException If an I/O error occurs while reading the JarFile. + */ + private void addPathToJar(String pathToJar) throws IOException { + try { + classPool.appendClassPath(pathToJar); + registerKnownClassesFor(pathToJar); + } catch (NotFoundException e) { + // If JavaAssist throws a NotFoundException we should notify the user + // with a FileNotFoundException. + FileNotFoundException jarNotFound = new FileNotFoundException(e.getMessage()); + jarNotFound.initCause(e); + throw jarNotFound; + } + } + + /** + * Register the list of known classes. + * + * When we create a new {@link JarTypeSolver} we should store the list of + * solvable types. + * + * @param pathToJar The path to the jar file. + * + * @throws IOException If an I/O error occurs while reading the JarFile. + */ + private void registerKnownClassesFor(String pathToJar) throws IOException { + try (JarFile jarFile = new JarFile(pathToJar)) { + + Enumeration jarEntries = jarFile.entries(); + while (jarEntries.hasMoreElements()) { + + JarEntry entry = jarEntries.nextElement(); + // Check if the entry is a .class file + if (!entry.isDirectory() && entry.getName().endsWith(CLASS_EXTENSION)) { + String qualifiedName = convertEntryPathToClassName(entry.getName()); + String classPoolName = convertEntryPathToClassPoolName(entry.getName()); + + // If the qualified name is the same as the class pool name we don't need to duplicate store two + // different String instances. Let's reuse the same. + if (qualifiedName.equals(classPoolName)) { + knownClasses.put(qualifiedName, qualifiedName); + } else { + knownClasses.put(qualifiedName, classPoolName); + } + } + } + + } + } + + /** + * Get the set of classes that can be resolved in the current type solver. + * + * @return The set of known classes. + */ + public Set getKnownClasses() { + return knownClasses.keySet(); + } + + @Override + public TypeSolver getParent() { + return parent; + } + + @Override + public void setParent(TypeSolver parent) { + Objects.requireNonNull(parent); + if (this.parent != null) { + throw new IllegalStateException("This TypeSolver already has a parent."); + } + if (parent == this) { + throw new IllegalStateException("The parent of this TypeSolver cannot be itself."); + } + this.parent = parent; + } + + @Override + public SymbolReference tryToSolveType(String name) { + + String storedKey = knownClasses.get(name); + // If the name is not registered in the list we can safely say is not solvable here + if (storedKey == null) { + return SymbolReference.unsolved(ResolvedReferenceTypeDeclaration.class); + } + + try { + return SymbolReference.solved(JavassistFactory.toTypeDeclaration(classPool.get(storedKey), getRoot())); + } catch (NotFoundException e) { + // The names in stored key should always be resolved. + // But if for some reason this happen, the user is notified. + throw new IllegalStateException(String.format( + "Unable to get class with name %s from class pool." + + "This was not suppose to happen, please report at https://github.com/javaparser/javaparser/issues", + storedKey)); + } + } + + @Override + public ResolvedReferenceTypeDeclaration solveType(String name) throws UnsolvedSymbolException { + SymbolReference ref = tryToSolveType(name); + if (ref.isSolved()) { + return ref.getCorrespondingDeclaration(); + } else { + throw new UnsolvedSymbolException(name); + } + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typesolvers/JavaParserTypeSolver.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typesolvers/JavaParserTypeSolver.java new file mode 100644 index 0000000000000000000000000000000000000000..78b840e345b4ba463627d48da4526e22682bbf2d --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typesolvers/JavaParserTypeSolver.java @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typesolvers; + +import com.github.javaparser.JavaParser; +import com.github.javaparser.ParserConfiguration; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.symbolsolver.cache.Cache; +import com.github.javaparser.symbolsolver.cache.GuavaCache; +import com.github.javaparser.symbolsolver.javaparser.Navigator; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; +import com.github.javaparser.symbolsolver.utils.FileUtils; +import com.google.common.cache.CacheBuilder; + +import java.io.File; +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import static com.github.javaparser.ParseStart.COMPILATION_UNIT; +import static com.github.javaparser.ParserConfiguration.LanguageLevel.BLEEDING_EDGE; +import static com.github.javaparser.Providers.provider; + +/** + * Defines a directory containing source code that should be used for solving symbols. + * The directory must correspond to the root package of the files within. + * + * @author Federico Tomassetti + */ +public class JavaParserTypeSolver implements TypeSolver { + + private final Path srcDir; + private final JavaParser javaParser; + + private TypeSolver parent; + + private final Cache> parsedFiles; + private final Cache> parsedDirectories; + private final Cache> foundTypes; + private static final int CACHE_SIZE_UNSET = -1; + + public JavaParserTypeSolver(File srcDir) { + this(srcDir.toPath()); + } + + public JavaParserTypeSolver(String srcDir) { + this(new File(srcDir)); + } + + public JavaParserTypeSolver(Path srcDir) { + this(srcDir, new ParserConfiguration().setLanguageLevel(BLEEDING_EDGE)); + } + + public JavaParserTypeSolver(File srcDir, ParserConfiguration parserConfiguration) { + this(srcDir.toPath(), parserConfiguration); + } + + public JavaParserTypeSolver(String srcDir, ParserConfiguration parserConfiguration) { + this(new File(srcDir), parserConfiguration); + } + + public JavaParserTypeSolver(Path srcDir, ParserConfiguration parserConfiguration) { + this(srcDir, parserConfiguration, CACHE_SIZE_UNSET); + } + + private Cache BuildCache(long cacheSizeLimit) { + CacheBuilder cacheBuilder = CacheBuilder.newBuilder().softValues(); + if (cacheSizeLimit != CACHE_SIZE_UNSET) { + cacheBuilder.maximumSize(cacheSizeLimit); + } + return new GuavaCache<>(cacheBuilder.build()); + } + + /** + * @param srcDir is the source code directory for the type solver. + * @param parserConfiguration is the configuration the solver should use when inspecting source code files. + * @param cacheSizeLimit is an optional size limit to the internal caches used by this solver. + * Be advised that setting the size too low might lead to noticeable performance degradation. + * However, using a size limit is advised when solving symbols in large code sources. In such cases, internal caches might consume large amounts of heap space. + */ + public JavaParserTypeSolver(Path srcDir, ParserConfiguration parserConfiguration, long cacheSizeLimit) { + if (!Files.exists(srcDir) || !Files.isDirectory(srcDir)) { + throw new IllegalStateException("SrcDir does not exist or is not a directory: " + srcDir); + } + this.srcDir = srcDir; + javaParser = new JavaParser(parserConfiguration); + parsedFiles = BuildCache(cacheSizeLimit); + parsedDirectories = BuildCache(cacheSizeLimit); + foundTypes = BuildCache(cacheSizeLimit); + } + + /** + * Create a {@link JavaParserTypeSolver} with a custom cache system. + * + * @param srcDir The source code directory for the type solver. + * @param javaParser The {@link JavaParser} to be used when parsing .java files. + * @param parsedFilesCache The cache to be used to store {@link CompilationUnit} that is associated with + * a file. + * @param parsedDirectoriesCache The cache to store the list of {@link CompilationUnit} in a given directory. + * @param foundTypesCache The cache that associated a qualified name to its {@link SymbolReference}. + */ + public JavaParserTypeSolver(Path srcDir, + JavaParser javaParser, + Cache> parsedFilesCache, + Cache> parsedDirectoriesCache, + Cache> foundTypesCache) { + Objects.requireNonNull(srcDir, "The srcDir can't be null."); + Objects.requireNonNull(javaParser, "The javaParser can't be null."); + Objects.requireNonNull(parsedFilesCache, "The parsedFilesCache can't be null."); + Objects.requireNonNull(parsedDirectoriesCache, "The parsedDirectoriesCache can't be null."); + Objects.requireNonNull(foundTypesCache, "The foundTypesCache can't be null."); + + if (!Files.exists(srcDir) || !Files.isDirectory(srcDir)) { + throw new IllegalStateException("SrcDir does not exist or is not a directory: " + srcDir); + } + + this.srcDir = srcDir; + this.javaParser = javaParser; + this.parsedFiles = parsedFilesCache; + this.parsedDirectories = parsedDirectoriesCache; + this.foundTypes = foundTypesCache; + } + + @Override + public String toString() { + return "JavaParserTypeSolver{" + + "srcDir=" + srcDir + + ", parent=" + parent + + '}'; + } + + @Override + public TypeSolver getParent() { + return parent; + } + + @Override + public void setParent(TypeSolver parent) { + Objects.requireNonNull(parent); + if (this.parent != null) { + throw new IllegalStateException("This TypeSolver already has a parent."); + } + if (parent == this) { + throw new IllegalStateException("The parent of this TypeSolver cannot be itself."); + } + this.parent = parent; + } + + private Optional parse(Path srcFile) { + try { + Optional> cachedParsedFile = parsedFiles.get(srcFile.toAbsolutePath()); + // If the value is already cached + if (cachedParsedFile.isPresent()) { + return cachedParsedFile.get(); + } + + // Otherwise load it + if (!Files.exists(srcFile) || !Files.isRegularFile(srcFile)) { + parsedFiles.put(srcFile.toAbsolutePath(), Optional.empty()); + return Optional.empty(); + } + + // JavaParser only allow one parse at time. + synchronized (javaParser) { + Optional compilationUnit = javaParser.parse(COMPILATION_UNIT, provider(srcFile)) + .getResult() + .map(cu -> cu.setStorage(srcFile)); + parsedFiles.put(srcFile.toAbsolutePath(), compilationUnit); + return compilationUnit; + } + } catch (IOException e) { + throw new RuntimeException("Issue while parsing while type solving: " + srcFile.toAbsolutePath(), e); + } + } + + /** + * Note that this parse only files directly contained in this directory. + * It does not traverse recursively all children directory. + */ + private List parseDirectory(Path srcDirectory) { + return parseDirectory(srcDirectory, false); + } + + private List parseDirectoryRecursively(Path srcDirectory) { + return parseDirectory(srcDirectory, true); + } + + private List parseDirectory(Path srcDirectory, boolean recursively) { + try { + Optional> cachedValue = parsedDirectories.get(srcDirectory.toAbsolutePath()); + if (cachedValue.isPresent()) { + return cachedValue.get(); + } + + // If not cached, we need to load it + List units = new ArrayList<>(); + if (Files.exists(srcDirectory)) { + try (DirectoryStream srcDirectoryStream = Files.newDirectoryStream(srcDirectory)) { + srcDirectoryStream + .forEach(file -> { + if (file.getFileName().toString().toLowerCase().endsWith(".java")) { + parse(file).ifPresent(units::add); + } else if (recursively && file.toFile().isDirectory()) { + units.addAll(parseDirectoryRecursively(file)); + } + }); + } + } + parsedDirectories.put(srcDirectory.toAbsolutePath(), units); + return units; + } catch (IOException e) { + throw new RuntimeException("Unable to parse directory due to an exception. Directory:" + srcDirectory.toAbsolutePath(), e); + } + + } + + @Override + public SymbolReference tryToSolveType(String name) { + Optional> cachedValue = foundTypes.get(name); + if (cachedValue.isPresent()) { + return cachedValue.get(); + } + + // Otherwise load it + SymbolReference result = tryToSolveTypeUncached(name); + foundTypes.put(name, result); + return result; + } + + private SymbolReference tryToSolveTypeUncached(String name) { + String[] nameElements = name.split("\\."); + + for (int i = nameElements.length; i > 0; i--) { + StringBuilder filePath = new StringBuilder(srcDir.toAbsolutePath().toString()); + for (int j = 0; j < i; j++) { + filePath.append(File.separator) + .append(nameElements[j]); + } + filePath.append(".java"); + + StringBuilder typeName = new StringBuilder(); + for (int j = i - 1; j < nameElements.length; j++) { + if (j != i - 1) { + typeName.append("."); + } + typeName.append(nameElements[j]); + } + + String dirToParse = null; + // As an optimization we first try to look in the canonical position where we expect to find the file + if (FileUtils.isValidPath(filePath.toString())) { + Path srcFile = Paths.get(filePath.toString()); + Optional compilationUnit = parse(srcFile); + if (compilationUnit.isPresent()) { + Optional> astTypeDeclaration = Navigator + .findType(compilationUnit.get(), typeName.toString()); + if (astTypeDeclaration.isPresent()) { + return SymbolReference + .solved(JavaParserFacade.get(this).getTypeDeclaration(astTypeDeclaration.get())); + } + } + dirToParse = srcFile.getParent().normalize().toString(); + } else { + dirToParse = FileUtils.getParentPath(filePath.toString()); + } + + // If this is not possible we parse all files + // We try just in the same package, for classes defined in a file not named as the class itself + if (FileUtils.isValidPath(dirToParse)) { + List compilationUnits = parseDirectory(Paths.get(dirToParse)); + for (CompilationUnit compilationUnit : compilationUnits) { + Optional> astTypeDeclaration = Navigator + .findType(compilationUnit, typeName.toString()); + if (astTypeDeclaration.isPresent()) { + return SymbolReference + .solved(JavaParserFacade.get(this).getTypeDeclaration(astTypeDeclaration.get())); + } + } + } + } + + return SymbolReference.unsolved(ResolvedReferenceTypeDeclaration.class); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typesolvers/MemoryTypeSolver.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typesolvers/MemoryTypeSolver.java new file mode 100644 index 0000000000000000000000000000000000000000..ae996c63ca49bee2e7872f5911709ceed0a4aceb --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typesolvers/MemoryTypeSolver.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typesolvers; + +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * A TypeSolver which only consider the TypeDeclarations provided to it. + * + * @author Federico Tomassetti + */ +public class MemoryTypeSolver implements TypeSolver { + + private TypeSolver parent; + private Map declarationMap = new HashMap<>(); + + @Override + public String toString() { + return "MemoryTypeSolver{" + + "parent=" + parent + + ", declarationMap=" + declarationMap + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof MemoryTypeSolver)) return false; + + MemoryTypeSolver that = (MemoryTypeSolver) o; + + if (parent != null ? !parent.equals(that.parent) : that.parent != null) return false; + return !(declarationMap != null ? !declarationMap.equals(that.declarationMap) : that.declarationMap != null); + + } + + @Override + public int hashCode() { + int result = parent != null ? parent.hashCode() : 0; + result = 31 * result + (declarationMap != null ? declarationMap.hashCode() : 0); + return result; + } + + @Override + public TypeSolver getParent() { + return parent; + } + + @Override + public void setParent(TypeSolver parent) { + Objects.requireNonNull(parent); + if (this.parent != null) { + throw new IllegalStateException("This TypeSolver already has a parent."); + } + if (parent == this) { + throw new IllegalStateException("The parent of this TypeSolver cannot be itself."); + } + this.parent = parent; + } + + public void addDeclaration(String name, ResolvedReferenceTypeDeclaration typeDeclaration) { + this.declarationMap.put(name, typeDeclaration); + } + + @Override + public SymbolReference tryToSolveType(String name) { + if (declarationMap.containsKey(name)) { + return SymbolReference.solved(declarationMap.get(name)); + } else { + return SymbolReference.unsolved(ResolvedReferenceTypeDeclaration.class); + } + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typesolvers/ReflectionTypeSolver.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typesolvers/ReflectionTypeSolver.java new file mode 100644 index 0000000000000000000000000000000000000000..124e931e10817ab63d6d232487a92a2ec77db7b2 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/typesolvers/ReflectionTypeSolver.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.resolution.typesolvers; + +/** + * Uses reflection to resolve types. + * Classes on the classpath used to run your application will be found. + * No source code is available for the resolved types. + * + * @author Federico Tomassetti + */ +public class ReflectionTypeSolver extends ClassLoaderTypeSolver { + + private final boolean jreOnly; + + /** + * @param jreOnly if true, will only resolve types from the java or javax packages. + * This is an easy way to say "I need a JRE to solve classes, and the one that is currently running is fine." + * If false, will resolve any kind of type. + */ + public ReflectionTypeSolver(boolean jreOnly) { + super(ReflectionTypeSolver.class.getClassLoader()); + this.jreOnly = jreOnly; + } + + /** + * Resolves classes from the JRE that is currently running. + * (It calls the other constructor with "true".) + */ + public ReflectionTypeSolver() { + this(true); + } + + @Override + protected boolean filterName(String name) { + return !jreOnly || (name.startsWith("java.") || name.startsWith("javax.")); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/utils/FileUtils.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/utils/FileUtils.java new file mode 100755 index 0000000000000000000000000000000000000000..d3a6b7d8626c6f2fca29965460602cfd43d2c7b0 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/utils/FileUtils.java @@ -0,0 +1,26 @@ +package com.github.javaparser.symbolsolver.utils; + +import com.github.javaparser.utils.Utils; + +import java.io.File; + +public class FileUtils { + + /* + * returns true if the filename exists otherwise return false + */ + public static boolean isValidPath(String filename) { + File file = new File(filename); + return file.exists(); + } + + /* + * returns the parent path from the filename as string + */ + public static String getParentPath(String filename) { + Utils.assertNotNull(filename); + int lastIndex = filename.lastIndexOf(File.separator); + return filename.substring(0, lastIndex); + } + +} diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/utils/SymbolSolverCollectionStrategy.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/utils/SymbolSolverCollectionStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..b634853d6a068de48d09f04b811d023ab55b4be2 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/utils/SymbolSolverCollectionStrategy.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2015-2016 Federico Tomassetti + * Copyright (C) 2017-2020 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver.utils; + +import com.github.javaparser.ParserConfiguration; +import com.github.javaparser.symbolsolver.JavaSymbolSolver; +import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver; +import com.github.javaparser.symbolsolver.resolution.typesolvers.JarTypeSolver; +import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver; +import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver; +import com.github.javaparser.utils.CollectionStrategy; +import com.github.javaparser.utils.Log; +import com.github.javaparser.utils.ProjectRoot; + +import java.io.IOException; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; + +import static java.nio.file.FileVisitResult.CONTINUE; +import static java.nio.file.FileVisitResult.SKIP_SUBTREE; + +/** + * {@link CollectionStrategy} which collects all SourceRoots and initialises the TypeSolver and + * returns the SourceRoots configured with the TypeSolver in a ProjectRoot object. + */ +public class SymbolSolverCollectionStrategy implements CollectionStrategy { + + private final ParserConfiguration parserConfiguration; + private final CombinedTypeSolver typeSolver = new CombinedTypeSolver(new ReflectionTypeSolver(false)); + + public SymbolSolverCollectionStrategy() { + this(new ParserConfiguration()); + } + + public SymbolSolverCollectionStrategy(ParserConfiguration parserConfiguration) { + // Allow the symbol resolver to be set via the given parser configuration + this.parserConfiguration = parserConfiguration; + if (!parserConfiguration.getSymbolResolver().isPresent()) { + this.parserConfiguration.setSymbolResolver(new JavaSymbolSolver(typeSolver)); + } + } + + @Override + public ParserConfiguration getParserConfiguration() { + return parserConfiguration; + } + + @Override + public ProjectRoot collect(Path path) { + ProjectRoot projectRoot = new ProjectRoot(path, parserConfiguration); + try { + Files.walkFileTree(path, new SimpleFileVisitor() { + private Path current_root; + private final PathMatcher javaMatcher = getPathMatcher("glob:**.java"); + private final PathMatcher jarMatcher = getPathMatcher("glob:**.jar"); + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (javaMatcher.matches(file)) { + if (current_root == null || !file.startsWith(current_root)) { + current_root = getRoot(file).orElse(null); + } + } else if (jarMatcher.matches(file)) { + typeSolver.add(new JarTypeSolver(file.toString())); + } + return CONTINUE; + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + if (Files.isHidden(dir)) { + return SKIP_SUBTREE; + } + return CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException { + if (current_root != null && Files.isSameFile(dir, current_root)) { + projectRoot.addSourceRoot(dir); + typeSolver.add(new JavaParserTypeSolver(current_root.toFile(), parserConfiguration)); + current_root = null; + } + return CONTINUE; + } + }); + } catch (IOException e) { + Log.error(e, "Unable to walk %s", () -> path); + } + return projectRoot; + } +} diff --git a/javaparser-symbol-solver-core/src/main/java/module-info.java b/javaparser-symbol-solver-core/src/main/java/module-info.java new file mode 100644 index 0000000000000000000000000000000000000000..055bb084c552ef211a9e09bfbde5f5526cef0037 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/module-info.java @@ -0,0 +1,10 @@ +module com.github.javaparser.symbolsolver { + requires com.github.javaparser.core; + requires com.google.common; + requires javassist; + exports com.github.javaparser.symbolsolver; + exports com.github.javaparser.symbolsolver.javaparsermodel.declarations; + exports com.github.javaparser.symbolsolver.model.resolution; + exports com.github.javaparser.symbolsolver.resolution.typesolvers; + exports com.github.javaparser.symbolsolver.model.typesystem; +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index df7cf4cbaffe0de37964ef3a58271649dbf02a43..046631177fe78d289346694ca6f5ae0be7a7eb94 100644 --- a/pom.xml +++ b/pom.xml @@ -39,5 +39,7 @@ sdg-core sdg-cli + javaparser-symbol-solver-core + sdg-bench diff --git a/sdg-bench/pom.xml b/sdg-bench/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..eb2e36e603f56523cb989053d188f59d89a4398f --- /dev/null +++ b/sdg-bench/pom.xml @@ -0,0 +1,61 @@ + + + + sdg + es.upv.mist.slicing + 1.3.0 + + 4.0.0 + + sdg-bench + + + 11 + 11 + + + + + es.upv.mist.slicing + sdg-core + ${project.version} + + + com.github.javaparser + javaparser-core + 3.23.1 + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.3.0 + + + + true + es.upv.mist.slicing.benchmark.BenchSC + + + + jar-with-dependencies + + + + + assemble-all + package + + single + + + + + + + \ No newline at end of file diff --git a/sdg-bench/src/main/java/es/upv/mist/slicing/benchmark/BenchSC.java b/sdg-bench/src/main/java/es/upv/mist/slicing/benchmark/BenchSC.java new file mode 100644 index 0000000000000000000000000000000000000000..3bb38745ca510365fa493a798676a84cc873fec5 --- /dev/null +++ b/sdg-bench/src/main/java/es/upv/mist/slicing/benchmark/BenchSC.java @@ -0,0 +1,253 @@ +package es.upv.mist.slicing.benchmark; + +import com.github.javaparser.ParseException; +import com.github.javaparser.Problem; +import com.github.javaparser.StaticJavaParser; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.stmt.ReturnStmt; +import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver; +import es.upv.mist.slicing.arcs.pdg.StructuralArc; +import es.upv.mist.slicing.graphs.augmented.ASDG; +import es.upv.mist.slicing.graphs.augmented.PSDG; +import es.upv.mist.slicing.graphs.exceptionsensitive.AllenSDG; +import es.upv.mist.slicing.graphs.exceptionsensitive.ESSDG; +import es.upv.mist.slicing.graphs.jsysdg.JSysDG; +import es.upv.mist.slicing.graphs.jsysdg.OriginalJSysDG; +import es.upv.mist.slicing.graphs.sdg.SDG; +import es.upv.mist.slicing.nodes.GraphNode; +import es.upv.mist.slicing.nodes.SyntheticNode; +import es.upv.mist.slicing.slicing.OriginalJSysDGSlicingAlgorithm; +import es.upv.mist.slicing.slicing.SlicingCriterion; +import es.upv.mist.slicing.utils.NodeHashSet; +import es.upv.mist.slicing.utils.StaticTypeSolver; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class BenchSC { + protected static final int BUILD_TIMES = 0, SLICE_TIMES = 1, SLICE_SIZES = 2, EXIT = 3; + protected final String[] dirIncludeSet = System.getProperty("sInclude", "").split(":"); + protected String graphType; + + public void benchmark() { + // Obtain parameters + String baselineGraph = System.getProperty("sGraphBaseline"); + String benchGraph = System.getProperty("sGraphBench"); + int minIter = Integer.parseInt(System.getProperty("sMinIter", "100")); + String outputPrefix = System.getProperty("sOutputPrefix", "result"); + + // Files + File buildBaseTime = new File(outputPrefix + "buildBaseTime.out"); + File buildBenchTime = new File(outputPrefix + "buildBenchTime.out"); + File nodeCount = new File(outputPrefix + "nodesBaseline.out"); + File sliceBaseTime = new File(outputPrefix + "sliceBaseTime.out"); + File sliceBenchTime = new File(outputPrefix + "sliceBenchTime.out"); + + // Configure JavaParser + StaticJavaParser.getConfiguration().setAttributeComments(false); + StaticTypeSolver.addTypeSolverJRE(); + for (String directory : dirIncludeSet) + StaticTypeSolver.addTypeSolver(new JavaParserTypeSolver(directory)); + + while (true) { + switch (selectOption()) { + case BUILD_TIMES: + graphType = baselineGraph; + timedRun(this::buildGraph, minIter, buildBaseTime); + graphType = benchGraph; + timedRun(this::buildGraph, minIter, buildBenchTime); + break; + case SLICE_SIZES: + try (PrintWriter pw = new PrintWriter(nodeCount)) { + graphType = "JSysDG"; + SDG baseSDG = buildGraph(); + pw.println("# File format: for each SDG type, the total number of nodes and then the number of "); + pw.println(baseSDG.vertexSet().size()); + Collection baseCriteria = findSCs(baseSDG); + System.out.printf("There are %d return object SCs", findReturnObjectSCs(baseSDG).size()); + System.out.printf("There are %d real nodes SCs", findRealSCs(baseSDG).size()); + System.exit(0); + for (SlicingCriterion sc : baseCriteria) { + int baseNodes = new OriginalJSysDGSlicingAlgorithm((JSysDG) baseSDG).traverse(sc.findNode(baseSDG)).getGraphNodes().size(); + int benchNodes = baseSDG.slice(sc).getGraphNodes().size(); + pw.printf("\"%s\",%d,%d\n", sc, baseNodes, benchNodes); + } + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + break; + case SLICE_TIMES: + try { + graphType = baselineGraph; + SDG sdg1 = buildGraph(); + try (PrintWriter pw = new PrintWriter(sliceBaseTime)) { + pw.println("# SC id, SC time sequence"); + for (SlicingCriterion sc : findSCs(sdg1)) + timedRun(() -> sdg1.slice(sc), minIter, pw); + } + graphType = benchGraph; + SDG sdg2 = buildGraph(); + try (PrintWriter pw = new PrintWriter(sliceBenchTime)) { + pw.println("# SC id, SC time sequence"); + for (SlicingCriterion sc : findSCs(sdg2)) + timedRun(() -> sdg2.slice(sc), minIter, pw); + } + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + break; + case EXIT: + return; + } + } + } + + protected int selectOption() { + Scanner in = new Scanner(System.in); + System.out.println("Select an option:"); + System.out.println("\t[0]: Time the building of the graphs"); + System.out.println("\t[1]: Time the slicing of the graphs"); + System.out.println("\t[2]: Number of nodes per slice"); + System.out.println("\t[3]: Exit"); + System.out.print("> "); + return in.nextInt(); + } + + protected SDG buildGraph() { + try { + // Build the SDG + Set units = new NodeHashSet<>(); + List problems = new LinkedList<>(); + for (File file : (Iterable) findAllJavaFiles(dirIncludeSet)::iterator) + parse(file, units, problems); + if (!problems.isEmpty()) { + for (Problem p : problems) + System.out.println(" * " + p.getVerboseMessage()); + throw new ParseException("Some problems were found while parsing files or folders"); + } + + SDG sdg = createGraph(graphType); + sdg.build(new NodeList<>(units)); + return sdg; + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + + private void parse(File file, Set units, List problems) { + try { + units.add(StaticJavaParser.parse(file)); + } catch (FileNotFoundException e) { + problems.add(new Problem(e.getLocalizedMessage(), null, e)); + } + } + + protected Stream findAllJavaFiles(String[] files) { + Stream.Builder builder = Stream.builder(); + for (String fileName : files) { + File file = new File(fileName); + if (file.isDirectory()) + findAllJavaFiles(file, builder); + else + builder.accept(file); + } + return builder.build(); + } + + protected void findAllJavaFiles(File directory, Stream.Builder builder) { + File[] files = directory.listFiles(); + if (files == null) + return; + for (File f : files) { + if (f.isDirectory()) + findAllJavaFiles(f, builder); + else if (f.getName().endsWith(".java")) + builder.accept(f); + } + } + + protected SDG createGraph(String graphName) { + switch (graphName) { + case "SDG": return new SDG(); + case "ASDG": return new ASDG(); + case "PSDG": return new PSDG(); + case "ESSDG": return new ESSDG(); + case "AllenSDG": return new AllenSDG(); + case "JSysDG": return new JSysDG(); + case "OriginalJSysDG": return new OriginalJSysDG(); + default: + throw new IllegalArgumentException(); + } + } + + protected long[] timedRun(Runnable runnable, int iterations) { + long[] times = new long[iterations]; + long t1, t2; + for (int i = -1; i < iterations; i++) { + t1 = System.nanoTime(); + runnable.run(); + t2 = System.nanoTime(); + if (i >= 0) + times[i] = t2 - t1; // Times stored in nanoseconds + } + return times; + } + + protected void timedRun(Runnable runnable, int minIter, File file) { + long[] data = timedRun(runnable, minIter); + try (PrintWriter pw = new PrintWriter(file)) { + for (long d : data) + pw.println(d); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + } + + protected void timedRun(Runnable runnable, int minIter, PrintWriter pw) { + long[] data = timedRun(runnable, minIter); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < data.length; i++) { + if (i > 0) + builder.append(','); + builder.append(data[i]); + } + pw.println(builder); + } + + protected Collection findSCs(SDG sdg) { + return findReturnObjectSCs(sdg); + } + + protected Collection findRealSCs(SDG sdg) { + return sdg.vertexSet().stream() + .filter(Predicate.not(SyntheticNode.class::isInstance)) + .filter(Predicate.not(GraphNode::isImplicitInstruction)) + .sorted() + .map(n -> (SlicingCriterion) graph -> Set.of(n)) + .collect(Collectors.toList()); + } + + protected Collection findReturnObjectSCs(SDG sdg) { + return sdg.vertexSet().stream() + .filter(gn -> gn.getAstNode() instanceof ReturnStmt) + .flatMap(gn -> sdg.outgoingEdgesOf(gn).stream() + .filter(StructuralArc.class::isInstance) + .map(sdg::getEdgeTarget)) + .filter(gn -> sdg.outgoingEdgesOf(gn).stream().anyMatch(StructuralArc.class::isInstance)) + .map(n -> new SlicingCriterion() { + public Set> findNode(SDG sdg) { return Set.of(n); } + public String toString() { return n.getLongLabel(); } + }) + .collect(Collectors.toList()); + } + + public static void main(String... args) { + new BenchSC().benchmark(); + } +} diff --git a/sdg-cli/src/main/java/es/upv/mist/slicing/cli/Slicer.java b/sdg-cli/src/main/java/es/upv/mist/slicing/cli/Slicer.java index 984e0b9b9b2aafc2a9a3ed5c1e9451a9bd5e67cf..c2b96f1f9634924b5d771afa28c45f6ee3de1dc2 100644 --- a/sdg-cli/src/main/java/es/upv/mist/slicing/cli/Slicer.java +++ b/sdg-cli/src/main/java/es/upv/mist/slicing/cli/Slicer.java @@ -1,5 +1,6 @@ package es.upv.mist.slicing.cli; +import com.github.javaparser.Problem; import com.github.javaparser.StaticJavaParser; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.NodeList; @@ -21,9 +22,9 @@ import org.apache.commons.cli.*; import java.io.File; import java.io.FileNotFoundException; import java.io.PrintWriter; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; @@ -172,24 +173,25 @@ public class Slicer { public void slice() throws ParseException { // Configure JavaParser + StaticJavaParser.getConfiguration().setAttributeComments(false); + Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.INFO, "Configuring JavaParser"); StaticTypeSolver.addTypeSolverJRE(); for (File directory : dirIncludeSet) StaticTypeSolver.addTypeSolver(new JavaParserTypeSolver(directory)); - StaticJavaParser.getConfiguration().setAttributeComments(false); // Build the SDG + Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.INFO, "Parsing files"); Set units = new NodeHashSet<>(); - try { - for (File file : dirIncludeSet) { - if (file.isDirectory()) - for (File f : (Iterable) findAllJavaFiles(file)::iterator) - units.add(StaticJavaParser.parse(f)); - else - units.add(StaticJavaParser.parse(file)); - } - units.add(StaticJavaParser.parse(scFile)); - } catch (FileNotFoundException e) { - throw new ParseException(e.getMessage()); + List problems = new LinkedList<>(); + boolean scFileFound = false; + for (File file : (Iterable) findAllJavaFiles(dirIncludeSet)::iterator) + scFileFound |= parse(file, units, problems); + if (!scFileFound) + parse(scFile, units, problems); + if (!problems.isEmpty()) { + for (Problem p : problems) + System.out.println(" * " + p.getVerboseMessage()); + throw new ParseException("Some problems were found while parsing files or folders"); } SDG sdg; @@ -202,16 +204,20 @@ public class Slicer { default: throw new IllegalArgumentException("Unknown type of graph. Available graphs are SDG, ASDG, PSDG, ESSDG"); } + Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.INFO, "Building the SDG"); sdg.build(new NodeList<>(units)); // Slice the SDG + Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.INFO, "Searching for criterion and slicing"); SlicingCriterion sc = new FileLineSlicingCriterion(scFile, scLine, scVar); Slice slice = sdg.slice(sc); // Convert the slice to code and output the result to `outputDir` + Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.INFO, "Printing slice to files"); for (CompilationUnit cu : slice.toAst()) { if (cu.getStorage().isEmpty()) throw new IllegalStateException("A synthetic CompilationUnit was discovered, with no file associated to it."); + Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.INFO, "Printing slice for " + cu.getStorage().get().getFileName()); String packagePath = cu.getPackageDeclaration().map(NodeWithName::getNameAsString).orElse("").replace(".", "/"); File packageDir = new File(outputDir, packagePath); packageDir.mkdirs(); @@ -225,6 +231,25 @@ public class Slicer { } } + private boolean parse(File file, Set units, List problems) { + try { + units.add(StaticJavaParser.parse(file)); + } catch (FileNotFoundException e) { + problems.add(new Problem(e.getLocalizedMessage(), null, e)); + } + return Objects.equals(file.getAbsoluteFile(), scFile.getAbsoluteFile()); + } + + protected Stream findAllJavaFiles(Collection files) { + Stream.Builder builder = Stream.builder(); + for (File file : files) + if (file.isDirectory()) + findAllJavaFiles(file, builder); + else + builder.accept(file); + return builder.build(); + } + protected Stream findAllJavaFiles(File directory) { Stream.Builder builder = Stream.builder(); findAllJavaFiles(directory, builder); @@ -239,7 +264,7 @@ public class Slicer { if (f.isDirectory()) findAllJavaFiles(f, builder); else if (f.getName().endsWith(".java")) - builder.add(f); + builder.accept(f); } } diff --git a/sdg-core/pom.xml b/sdg-core/pom.xml index 570d2ca6697124fd5b72cc478b6ab948d7aeba49..68114d8fb86410f6c067a49fe71db33eb6a7fd0f 100644 --- a/sdg-core/pom.xml +++ b/sdg-core/pom.xml @@ -35,12 +35,12 @@ com.github.javaparser javaparser-core - 3.22.1 + 3.23.1 com.github.javaparser javaparser-symbol-solver-core - 3.22.1 + 3.23.2 org.jgrapht diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/arcs/pdg/FlowDependencyArc.java b/sdg-core/src/main/java/es/upv/mist/slicing/arcs/pdg/FlowDependencyArc.java index e56b5ae43111318c86c77ebd7dae351e6869403b..126d8860855f344a313b36d0537eff6b9001b246 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/arcs/pdg/FlowDependencyArc.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/arcs/pdg/FlowDependencyArc.java @@ -1,6 +1,7 @@ package es.upv.mist.slicing.arcs.pdg; import es.upv.mist.slicing.arcs.Arc; +import es.upv.mist.slicing.utils.Utils; /** Represents a data dependency in an object-oriented SDG or PDG. */ public class FlowDependencyArc extends Arc { @@ -12,4 +13,8 @@ public class FlowDependencyArc extends Arc { super(variable); } + public FlowDependencyArc(String[] member) { + super(Utils.arrayJoin(member, ".")); + } + } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/BackwardDataFlowAnalysis.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/BackwardDataFlowAnalysis.java index 0f64648de3f1877dc539160347815506bbf8b2f5..5f1984f5211403c72ee3945d2126b2208b5716cf 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/BackwardDataFlowAnalysis.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/BackwardDataFlowAnalysis.java @@ -37,7 +37,7 @@ public abstract class BackwardDataFlowAnalysis { Set mayAffectVertex = graph.outgoingEdgesOf(vertex).stream() .map(graph::getEdgeTarget).collect(Collectors.toCollection(ASTUtils::newIdentityHashSet)); D newValue = compute(vertex, mayAffectVertex); - if (!Objects.equals(vertexDataMap.get(vertex), newValue)) { + if (!dataMatch(vertexDataMap.get(vertex), newValue)) { vertexDataMap.put(vertex, newValue); graph.incomingEdgesOf(vertex).stream().map(graph::getEdgeSource).forEach(newWorkList::add); } @@ -47,6 +47,11 @@ public abstract class BackwardDataFlowAnalysis { built = true; } + /** Checks whether the computed value has changed or not. */ + protected boolean dataMatch(D oldData, D newData) { + return Objects.equals(oldData, newData); + } + /** Compute a new value for a given vertex, given a set of nodes that might affect its value. */ protected abstract D compute(V vertex, Set predecessors); diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/CallGraph.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/CallGraph.java index 442d0a1cdacdf2e5791e37ed295048e264d552e9..d9e0cf8241a51539b614c64eda0c884d8a51d6d9 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/CallGraph.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/CallGraph.java @@ -7,6 +7,7 @@ import com.github.javaparser.ast.body.*; import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.expr.ObjectCreationExpr; +import com.github.javaparser.ast.nodeTypes.NodeWithSimpleName; import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; import com.github.javaparser.resolution.Resolvable; @@ -89,8 +90,7 @@ public class CallGraph extends DirectedPseudograph declaration) { return vertexSet().stream() - .filter(v -> v.declaration == declaration || - ASTUtils.equalsWithRange(v.declaration, declaration)) + .filter(v -> v.matches(declaration)) .findFirst().orElseThrow(); } @@ -130,17 +130,28 @@ public class CallGraph extends DirectedPseudograph source, CallableDeclaration target, Resolvable call) { + return false; // TODO: handle static blocks + } + /** Find the calls to methods and constructors (edges) in the given list of compilation units. */ protected void buildEdges(NodeList arg) { arg.accept(new VoidVisitorAdapter() { - private final Deque classStack = new LinkedList<>(); + private final Deque> typeStack = new LinkedList<>(); private final Deque> declStack = new LinkedList<>(); @Override public void visit(ClassOrInterfaceDeclaration n, Void arg) { - classStack.push(n); + typeStack.push(n); + super.visit(n, arg); + typeStack.pop(); + } + + @Override + public void visit(EnumDeclaration n, Void arg) { + typeStack.push(n); super.visit(n, arg); - classStack.pop(); + typeStack.pop(); } // ============ Method declarations =========== @@ -189,7 +200,7 @@ public class CallGraph extends DirectedPseudograph scope = call.getScope(); // Determine the type of the call's scope - Set dynamicTypes; + Set> dynamicTypes; if (scope.isEmpty()) { // a) No scope: any class the method is in, or any outer class if the class is not static. // Early exit: it is easier to find the methods that override the @@ -199,7 +210,7 @@ public class CallGraph extends DirectedPseudograph decl, Resolvable call) { - addEdge(declStack.peek(), decl, call); + if (declStack.isEmpty() && typeStack.isEmpty()) + throw new IllegalStateException("Trying to link call with empty declaration stack! " + decl.getDeclarationAsString() + " : " + call.toString()); + if (declStack.isEmpty()) + addEdge(typeStack.peek(), decl, call); + else + addEdge(declStack.peek(), decl, call); } // Other structures @Override public void visit(FieldDeclaration n, Void arg) { if (declStack.isEmpty() && !n.isStatic()) { - for (ConstructorDeclaration cd : classStack.peek().getConstructors()) { + for (ConstructorDeclaration cd : typeStack.peek().getConstructors()) { declStack.push(cd); super.visit(n, arg); declStack.pop(); @@ -244,7 +260,7 @@ public class CallGraph extends DirectedPseudograph node : cfgMap.get(declaration).vertexSet()) if (node.containsCall(n)) return node; - throw new NodeNotFoundException("call " + n + " could not be located!"); + throw new NodeNotFoundException("call " + n + " could not be located! cfg was " + cfgMap.get(declaration).rootNode.getLongLabel() + " and declaration was " + declaration.getDeclarationAsString()); } /** A vertex containing the declaration it represents. It only exists because @@ -264,18 +280,28 @@ public class CallGraph extends DirectedPseudograph declaration) { + if (this.declaration == declaration) + return true; + if (!this.declaration.getSignature().toString().equals(declaration.getSignature().toString())) + return false; + var t1 = this.declaration.findAncestor(NodeWithSimpleName.class).orElse(null); + var t2 = declaration.findAncestor(NodeWithSimpleName.class).orElse(null); + return t1 != null && t2 != null && t1.getNameAsString().equals(t2.getNameAsString()); + } } /** An edge containing the call it represents, and the graph node that contains it. */ @@ -285,6 +311,7 @@ public class CallGraph extends DirectedPseudograph graphNode) { assert call instanceof MethodCallExpr || call instanceof ObjectCreationExpr || call instanceof ExplicitConstructorInvocationStmt; + assert graphNode.containsCall(call); this.call = call; this.graphNode = graphNode; } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/ClassGraph.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/ClassGraph.java index 3763731b74e91efdcfc8989280b37a41b4b035e3..2c80574bbfa93c1cebfefb2c6db5bfd44eb60b11 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/ClassGraph.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/ClassGraph.java @@ -1,8 +1,13 @@ package es.upv.mist.slicing.graphs; +import com.github.javaparser.StaticJavaParser; import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.Node; import com.github.javaparser.ast.NodeList; import com.github.javaparser.ast.body.*; +import com.github.javaparser.ast.nodeTypes.NodeWithName; +import com.github.javaparser.ast.nodeTypes.NodeWithSimpleName; +import com.github.javaparser.ast.type.TypeParameter; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; import com.github.javaparser.resolution.UnsolvedSymbolException; import com.github.javaparser.resolution.declarations.ResolvedClassDeclaration; @@ -18,6 +23,8 @@ import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; +import static es.upv.mist.slicing.nodes.ObjectTree.ROOT_NODE; + public class ClassGraph extends DirectedPseudograph, ClassGraph.ClassArc> implements Buildable> { private static ClassGraph instance = null; @@ -34,7 +41,7 @@ public class ClassGraph extends DirectedPseudograph, ClassG } /** A map from the FQ class name to its corresponding vertex. Use {@code mapKey(...)} to locate the key. */ - private final Map> classDeclarationMap = new HashMap<>(); + private final Map>> classDeclarationMap = new HashMap<>(); /** A map from the field name to its corresponding vertex. Use {@code mapKey(...)} to locate the key. */ private final Map> fieldDeclarationMap = new HashMap<>(); /** A map from the method's signature to its corresponding vertex. Use {@code mapKey(...)} to locate the key. */ @@ -46,9 +53,13 @@ public class ClassGraph extends DirectedPseudograph, ClassG super(null, null, false); } + public Collection>> typeVertices() { + return classDeclarationMap.values(); + } + /** Locates the vertex that represents a given class or interface declaration. * If the vertex is not contained in the graph, {@code null} will be returned. */ - protected Vertex findClassVertex(ClassOrInterfaceDeclaration declaration) { + protected Vertex> findClassVertex(TypeDeclaration declaration) { return classDeclarationMap.get(mapKey(declaration)); } @@ -59,7 +70,7 @@ public class ClassGraph extends DirectedPseudograph, ClassG /** Set of method declarations that override the given argument. */ public Set overriddenSetOf(MethodDeclaration method) { - return subclassesStreamOf(findClassVertex(method.findAncestor(ClassOrInterfaceDeclaration.class).orElseThrow())) + return subclassesStreamOf(findClassVertex(method.findAncestor(TypeDeclaration.class).orElseThrow())) .flatMap(vertex -> outgoingEdgesOf(vertex).stream() .filter(ClassArc.Member.class::isInstance) .map(ClassGraph.this::getEdgeTarget) @@ -77,34 +88,36 @@ public class ClassGraph extends DirectedPseudograph, ClassG /** @see #findClassField(ResolvedType,String) */ @SuppressWarnings("unchecked") - public Optional findClassField(Vertex vertex, String fieldName) { + public Optional findClassField(Vertex> vertex, String fieldName) { var field = vertex.getDeclaration().getFieldByName(fieldName); if (field.isPresent()) return field; return incomingEdgesOf(vertex).stream() .filter(ClassArc.Extends.class::isInstance) .map(this::getEdgeSource) - .map(v -> (Vertex) v) + .map(v -> (Vertex>) v) .findAny() .flatMap(parent -> findClassField(parent, fieldName)); } /** Returns all child classes of the given class, including itself. */ - public Set subclassesOf(ClassOrInterfaceDeclaration clazz) { + public Set> subclassesOf(TypeDeclaration clazz) { return subclassesOf(findClassVertex(clazz)); } /** Returns all child classes of the given class, including itself. */ - public Set subclassesOf(ResolvedClassDeclaration clazz) { + public Set> subclassesOf(ResolvedClassDeclaration clazz) { return subclassesOf(classDeclarationMap.get(mapKey(clazz))); } - public Set subclassesOf(ResolvedReferenceType type) { + public Set> subclassesOf(ResolvedReferenceType type) { return subclassesOf(classDeclarationMap.get(mapKey(type))); } - /** @see #subclassesOf(ClassOrInterfaceDeclaration) */ - protected Set subclassesOf(Vertex v) { + /** @see #subclassesOf(TypeDeclaration) */ + protected Set> subclassesOf(Vertex> v) { + if (v.getDeclaration() instanceof EnumDeclaration) + return Set.of(v.getDeclaration()); return subclassesStreamOf(v) .map(Vertex::getDeclaration) .map(ClassOrInterfaceDeclaration.class::cast) @@ -112,25 +125,27 @@ public class ClassGraph extends DirectedPseudograph, ClassG } @SuppressWarnings("unchecked") - protected Stream> subclassesStreamOf(Vertex classVertex) { + protected Stream>> subclassesStreamOf(Vertex> classVertex) { return Stream.concat(Stream.of(classVertex), outgoingEdgesOf(classVertex).stream() .filter(ClassArc.Extends.class::isInstance) .map(this::getEdgeTarget) - .map(v -> (Vertex) v) + .map(v -> (Vertex>) v) .flatMap(this::subclassesStreamOf)); } // TODO: this method ignores default method implementations in interfaces, as can be overridden. /** Looks up a method in the graph, going up the class inheritance tree to locate a * matching method. If no match can be found, throws an {@link IllegalArgumentException}. */ - public MethodDeclaration findMethodByTypeAndSignature(ClassOrInterfaceDeclaration type, CallableDeclaration declaration) { + public MethodDeclaration findMethodByTypeAndSignature(TypeDeclaration type, CallableDeclaration declaration) { Vertex> v = methodDeclarationMap.get(mapKey(declaration, type)); if (v != null && v.declaration.isMethodDeclaration()) return v.declaration.asMethodDeclaration(); - Optional parentType = parentOf(type); - if (parentType.isEmpty()) - throw new IllegalArgumentException("Cannot find the given declaration: " + declaration); - return findMethodByTypeAndSignature(parentType.get(), declaration); + if (type.isClassOrInterfaceDeclaration()) { + Optional parentType = parentOf(type.asClassOrInterfaceDeclaration()); + if (parentType.isPresent()) + return findMethodByTypeAndSignature(parentType.get(), declaration); + } + throw new IllegalArgumentException("Cannot find the given declaration: " + declaration); } /** Find the parent class or interface of a given class. */ @@ -149,6 +164,11 @@ public class ClassGraph extends DirectedPseudograph, ClassG MethodDeclaration method = callableDeclaration.asMethodDeclaration(); if (method.getType().isClassOrInterfaceType()) try { + // TODO: improve. Sometimes, the cu doesn't have the symbol solver. We readd that here. + method.getType().findCompilationUnit().ifPresentOrElse(cu -> { + if (!cu.containsData(Node.SYMBOL_RESOLVER_KEY)) + cu.setData(Node.SYMBOL_RESOLVER_KEY, StaticJavaParser.getConfiguration().getSymbolResolver().orElseThrow(() -> new IllegalStateException("Symbol resolution not configured: to configure consider setting a SymbolResolver in the ParserConfiguration"))); + }, () -> { throw new IllegalStateException("The node is not inserted in a CompilationUnit"); }); return Optional.of(generateObjectTreeFor(method.getType().asClassOrInterfaceType().resolve())); } catch (UnsolvedSymbolException e) { return Optional.empty(); @@ -156,7 +176,7 @@ public class ClassGraph extends DirectedPseudograph, ClassG else return Optional.empty(); } else if (callableDeclaration.isConstructorDeclaration()) { - return Optional.of(generateObjectTreeFor(ASTUtils.getClassNode(callableDeclaration))); + return Optional.of(generateObjectTreeFor(callableDeclaration.findAncestor(TypeDeclaration.class).orElseThrow())); } else { throw new IllegalArgumentException("Invalid callable declaration type"); } @@ -164,14 +184,14 @@ public class ClassGraph extends DirectedPseudograph, ClassG public Optional generateObjectTreeForType(ResolvedType type) { if (type.isReferenceType()) { - Vertex v = classDeclarationMap.get(mapKey(type.asReferenceType())); + Vertex> v = classDeclarationMap.get(mapKey(type.asReferenceType())); if (v != null) return Optional.of(generateObjectTreeFor(v)); } return Optional.empty(); } - public ObjectTree generateObjectTreeFor(ClassOrInterfaceDeclaration declaration) { + public ObjectTree generateObjectTreeFor(TypeDeclaration declaration) { return generateObjectTreeFor(classDeclarationMap.get(mapKey(declaration))); } @@ -179,62 +199,74 @@ public class ClassGraph extends DirectedPseudograph, ClassG return generateObjectTreeFor(classDeclarationMap.get(mapKey(type))); } - protected ObjectTree generateObjectTreeFor(Vertex classVertex) { + protected ObjectTree generateObjectTreeFor(Vertex> classVertex) { if (classVertex == null) return new ObjectTree(); - return generatePolyObjectTreeFor(classVertex, new ObjectTree(), ObjectTree.ROOT_NAME, 0); + return generatePolyObjectTreeFor(classVertex, new ObjectTree(), ROOT_NODE, 0); } - protected ObjectTree generatePolyObjectTreeFor(Vertex classVertex, ObjectTree tree, String level, int depth) { + protected ObjectTree generatePolyObjectTreeFor(Vertex> classVertex, ObjectTree tree, String[] level, int depth) { if (depth >= StaticConfig.K_LIMIT) return tree; - Set types = subclassesOf(classVertex); + Set> types = subclassesOf(classVertex); if (types.isEmpty()) { generateObjectTreeFor(classVertex, tree, level, depth); } else { - for (ClassOrInterfaceDeclaration type : types) { - Vertex subclassVertex = classDeclarationMap.get(mapKey(type)); + for (TypeDeclaration type : types) { + Vertex> subclassVertex = classDeclarationMap.get(mapKey(type)); if (!findAllFieldsOf(subclassVertex).isEmpty()) { ObjectTree newType = tree.addType(ASTUtils.resolvedTypeDeclarationToResolvedType(type.resolve()), level); - generateObjectTreeFor(subclassVertex, tree, level + '.' + newType.getMemberNode().getLabel(), depth); + String[] newLevel = new String[level.length + 1]; + System.arraycopy(level, 0, newLevel, 0, level.length); + newLevel[level.length] = newType.getMemberNode().getLabel(); + generateObjectTreeFor(subclassVertex, tree, newLevel, depth + 1); } } } return tree; } - protected void generateObjectTreeFor(Vertex classVertex, ObjectTree tree, String level, int depth) { - Map> classFields = findAllFieldsOf(classVertex); + protected void generateObjectTreeFor(Vertex> classVertex, ObjectTree tree, String[] level, int depth) { + Map>> classFields = findAllFieldsOf(classVertex); for (var entry : classFields.entrySet()) { - tree.addField(level + '.' + entry.getKey()); + String[] newLevel = new String[level.length + 1]; + System.arraycopy(level, 0, newLevel, 0, level.length); + newLevel[level.length] = entry.getKey(); + tree.addField(newLevel); if (entry.getValue() != null) - generatePolyObjectTreeFor(entry.getValue(), tree, level + '.' + entry.getKey(), depth); + generatePolyObjectTreeFor(entry.getValue(), tree, newLevel, depth); } } - protected Map> findAllFieldsOf(Vertex classVertex) { - assert !classVertex.declaration.asClassOrInterfaceDeclaration().isInterface(); - ClassOrInterfaceDeclaration clazz = classVertex.getDeclaration().asClassOrInterfaceDeclaration(); - Map> fieldMap = new HashMap<>(); - while (clazz != null) { - for (FieldDeclaration field : clazz.getFields()) { + protected Map>> findAllFieldsOf(Vertex> classVertex) { + TypeDeclaration type = classVertex.getDeclaration(); + assert !type.isClassOrInterfaceDeclaration() || + !type.asClassOrInterfaceDeclaration().isInterface(); + Map>> fieldMap = new HashMap<>(); + while (type != null) { + for (FieldDeclaration field : type.getFields()) { for (VariableDeclarator var : field.getVariables()) { if (fieldMap.containsKey(var.getNameAsString())) continue; - Vertex v = null; + Vertex> v = null; if (var.getType().isClassOrInterfaceType()) { - try { - v = classDeclarationMap.get(mapKey(var.getType().asClassOrInterfaceType().resolve())); - } catch (UnsolvedSymbolException ignored) { - } + boolean isTypeParameter = false; + for (TypeParameter typeParameter : type.asClassOrInterfaceDeclaration().getTypeParameters()) + if (typeParameter.getNameAsString().equals(var.getType().asClassOrInterfaceType().getNameAsString())) + isTypeParameter = true; + if (!isTypeParameter) + try { + v = classDeclarationMap.get(mapKey(var.getType().asClassOrInterfaceType().resolve())); + } catch (UnsolvedSymbolException ignored) {} } fieldMap.put(var.getNameAsString(), v); } } - Optional parent = parentOf(clazz); - if (parent.isEmpty()) - break; - clazz = parent.get(); + if (type.isClassOrInterfaceDeclaration()) { + type = parentOf(type.asClassOrInterfaceDeclaration()).orElse(null); + } else { + type = null; + } } return fieldMap; } @@ -253,7 +285,7 @@ public class ClassGraph extends DirectedPseudograph, ClassG return built; } - protected String mapKey(ClassOrInterfaceDeclaration n) { + protected String mapKey(TypeDeclaration n) { return n.getFullyQualifiedName().orElseThrow(); } @@ -265,11 +297,11 @@ public class ClassGraph extends DirectedPseudograph, ClassG return n.getQualifiedName(); } - protected String mapKey(CallableDeclaration declaration, ClassOrInterfaceDeclaration clazz) { + protected String mapKey(CallableDeclaration declaration, TypeDeclaration clazz) { return clazz.getFullyQualifiedName().orElseThrow() + "." + declaration.getSignature(); } - protected String mapKey(FieldDeclaration declaration, ClassOrInterfaceDeclaration clazz) { + protected String mapKey(FieldDeclaration declaration, TypeDeclaration clazz) { return clazz.getFullyQualifiedName().orElseThrow() + "." + declaration; } @@ -277,7 +309,7 @@ public class ClassGraph extends DirectedPseudograph, ClassG * in the given list of compilation units. */ protected void buildVertices(NodeList arg) { arg.accept(new VoidVisitorAdapter() { - private final Deque classStack = new LinkedList<>(); + private final Deque> typeStack = new LinkedList<>(); // QUESTIONS & LACKS: // 1) Is it necessary to include something apart from class vertices? // 2) Private classes inside other classes? @@ -285,49 +317,56 @@ public class ClassGraph extends DirectedPseudograph, ClassG @Override public void visit(ClassOrInterfaceDeclaration n, Void arg) { - classStack.push(n); - addClassDeclaration(n); + typeStack.push(n); + addTypeDeclaration(n); super.visit(n, arg); - classStack.pop(); + typeStack.pop(); + } + + @Override + public void visit(EnumDeclaration n, Void arg) { + typeStack.push(n); + addTypeDeclaration(n); + super.visit(n, arg); + typeStack.pop(); } @Override public void visit(FieldDeclaration n, Void arg) { - assert classStack.peek() != null; - addFieldDeclaration(n, classStack.peek()); + assert typeStack.peek() != null; + addFieldDeclaration(n, typeStack.peek()); } @Override public void visit(MethodDeclaration n, Void arg) { - assert classStack.peek() != null; - addCallableDeclaration(n, classStack.peek()); + assert typeStack.peek() != null; + addCallableDeclaration(n, typeStack.peek()); } @Override public void visit(ConstructorDeclaration n, Void arg) { - assert classStack.peek() != null; - addCallableDeclaration(n, classStack.peek()); + assert typeStack.peek() != null; + addCallableDeclaration(n, typeStack.peek()); } }, null); } - /** Add a class declaration vertex to the class graph */ - protected void addClassDeclaration(ClassOrInterfaceDeclaration n) { - ClassGraph.Vertex v = new ClassGraph.Vertex<>(n); - // Required string to match ClassOrInterfaceType and ClassOrInterfaceDeclaration. QualifiedName Not Valid + /** Add a type declaration vertex to the class graph, to represent classes and enums. */ + protected void addTypeDeclaration(TypeDeclaration n) { + ClassGraph.Vertex> v = new ClassGraph.Vertex<>(n); classDeclarationMap.put(mapKey(n), v); addVertex(v); } /** Add a field declaration vertex to the class graph */ - protected void addFieldDeclaration(FieldDeclaration n, ClassOrInterfaceDeclaration c){ + protected void addFieldDeclaration(FieldDeclaration n, TypeDeclaration c){ ClassGraph.Vertex v = new ClassGraph.Vertex<>(n); fieldDeclarationMap.put(mapKey(n, c), v); addVertex(v); } /** Add a method/constructor declaration vertex to the class graph */ - protected void addCallableDeclaration(CallableDeclaration n, ClassOrInterfaceDeclaration c){ + protected void addCallableDeclaration(CallableDeclaration n, TypeDeclaration c){ assert n instanceof ConstructorDeclaration || n instanceof MethodDeclaration; ClassGraph.Vertex> v = new ClassGraph.Vertex<>(n); methodDeclarationMap.put(mapKey(n, c), v); @@ -338,53 +377,63 @@ public class ClassGraph extends DirectedPseudograph, ClassG * member/extends/implements relationships in the given list of compilation units. */ protected void buildEdges(NodeList arg) { arg.accept(new VoidVisitorAdapter() { - private final Deque classStack = new LinkedList<>(); + private final Deque> typeStack = new LinkedList<>(); @Override public void visit(ClassOrInterfaceDeclaration n, Void arg) { - classStack.push(n); - Vertex v = classDeclarationMap.get(mapKey(n)); + typeStack.push(n); + var v = classDeclarationMap.get(mapKey(n)); addClassEdges(v); super.visit(n, arg); - classStack.pop(); + typeStack.pop(); + } + + @Override + public void visit(EnumDeclaration n, Void arg) { + typeStack.push(n); + super.visit(n, arg); + typeStack.pop(); } @Override public void visit(FieldDeclaration n, Void arg) { - ClassOrInterfaceDeclaration clazz = classStack.peek(); - assert clazz != null; - Vertex c = classDeclarationMap.get(mapKey(clazz)); - Vertex v = fieldDeclarationMap.get(mapKey(n, clazz)); + assert !typeStack.isEmpty(); + TypeDeclaration type = typeStack.peek(); + var c = classDeclarationMap.get(mapKey(type)); + Vertex v = fieldDeclarationMap.get(mapKey(n, type)); addEdge(c, v, new ClassArc.Member()); } @Override public void visit(MethodDeclaration n, Void arg) { - ClassOrInterfaceDeclaration clazz = classStack.peek(); - assert clazz != null; - Vertex c = classDeclarationMap.get(mapKey(clazz)); - Vertex> v = methodDeclarationMap.get(mapKey(n, clazz)); + assert !typeStack.isEmpty(); + TypeDeclaration type = typeStack.peek(); + var c = classDeclarationMap.get(mapKey(type)); + Vertex> v = methodDeclarationMap.get(mapKey(n, type)); addEdge(c, v, new ClassArc.Member()); } @Override public void visit(ConstructorDeclaration n, Void arg) { - ClassOrInterfaceDeclaration clazz = classStack.peek(); - assert clazz != null; - Vertex c = classDeclarationMap.get(mapKey(clazz)); - Vertex> v = methodDeclarationMap.get(mapKey(n, clazz)); + assert !typeStack.isEmpty(); + TypeDeclaration type = typeStack.peek(); + var c = classDeclarationMap.get(mapKey(type)); + Vertex> v = methodDeclarationMap.get(mapKey(n, type)); addEdge(c, v, new ClassArc.Member()); } }, null); } - protected void addClassEdges(Vertex v) { - v.declaration.getExtendedTypes().forEach(p -> { + protected void addClassEdges(Vertex> v) { + if (v.declaration instanceof EnumDeclaration) + return; // nothing to do, it is final and cannot extend nor implement user-defined types + ClassOrInterfaceDeclaration c = (ClassOrInterfaceDeclaration) v.declaration; + c.getExtendedTypes().forEach(p -> { Vertex source = classDeclarationMap.get(mapKey(p.resolve())); if (source != null && containsVertex(v)) addEdge(source, v, new ClassArc.Extends()); }); - v.declaration.getImplementedTypes().forEach(p -> { + c.getImplementedTypes().forEach(p -> { Vertex source = classDeclarationMap.get(mapKey(p.resolve())); if (source != null && containsVertex(v)) addEdge(source, v, new ClassArc.Implements()); @@ -409,11 +458,19 @@ public class ClassGraph extends DirectedPseudograph, ClassG @Override public int hashCode() { - return Objects.hash(declaration, declaration.getRange()); + if (declaration instanceof NodeWithName) + return Objects.hash(((NodeWithName) declaration).getNameAsString()); + if (declaration instanceof NodeWithSimpleName) + return Objects.hash(((NodeWithSimpleName) declaration).getNameAsString()); + if (declaration instanceof FieldDeclaration) + return Objects.hash(String.valueOf(declaration)); + throw new IllegalStateException("Invalid vertex in graph"); } @Override public boolean equals(Object obj) { + if (obj == this) + return true; return obj instanceof CallGraph.Vertex && ASTUtils.equalsWithRangeInCU(((CallGraph.Vertex) obj).declaration, declaration); } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/ExpressionObjectTreeFinder.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/ExpressionObjectTreeFinder.java index 9ee7ad4bb8d6b5247cd84dbd590791c610690038..5a6e19fecf9382b814bdea73d8229211cf1f8bdb 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/ExpressionObjectTreeFinder.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/ExpressionObjectTreeFinder.java @@ -4,6 +4,7 @@ import com.github.javaparser.ast.body.VariableDeclarator; import com.github.javaparser.ast.expr.*; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; import com.github.javaparser.resolution.Resolvable; +import com.github.javaparser.resolution.UnsolvedSymbolException; import com.github.javaparser.resolution.declarations.ResolvedMethodLikeDeclaration; import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; import com.github.javaparser.utils.Pair; @@ -11,6 +12,7 @@ import es.upv.mist.slicing.nodes.GraphNode; import es.upv.mist.slicing.nodes.ObjectTree; import es.upv.mist.slicing.nodes.VariableAction; import es.upv.mist.slicing.utils.ASTUtils; +import es.upv.mist.slicing.utils.Logger; import java.util.LinkedList; import java.util.List; @@ -176,26 +178,34 @@ public class ExpressionObjectTreeFinder { @Override public void visit(NameExpr n, String arg) { - ResolvedValueDeclaration resolved = n.resolve(); - if (resolved.isType()) - return; - if (resolved.isField() && !resolved.asField().isStatic()) { - String newArg = n.getNameAsString() + (!arg.isEmpty() ? "." : "") + arg; - var optVa = locateVariableAction(n, va -> va.getName().matches("^.*this$")); - if (optVa.isEmpty()) - throw new IllegalStateException("Could not find USE action for var " + newArg); - list.add(new Pair<>(optVa.get(), newArg)); - } else { - var optVa = locateVariableAction(n, va -> va.getName().equals(n.getNameAsString())); - if (optVa.isEmpty()) - throw new IllegalStateException("Cannot find USE action for var " + n); - list.add(new Pair<>(optVa.get(), arg)); + try { + ResolvedValueDeclaration resolved = n.resolve(); + if (resolved.isType()) + return; + if (resolved.isField() && !resolved.asField().isStatic()) { + String newArg = n.getNameAsString() + (!arg.isEmpty() ? "." : "") + arg; + var va = locateVariableActionThis(n) + .orElseThrow(() -> new IllegalStateException("Could not find USE action for var " + newArg)); + list.add(new Pair<>(va, newArg)); + } else { + var va = locateVariableAction(n) + .orElseThrow(() -> new IllegalStateException("Cannot find USE action for var " + n)); + list.add(new Pair<>(va, arg)); + } + } catch (UnsolvedSymbolException e) { + Logger.log("Unable to resolve " + n + " in " + graphNode.toString() + ". Assuming that it's a reference type"); + if (!n.calculateResolvedType().isReferenceType()) { + throw e; // It's a type?! + } + var va = locateVariableAction(n) + .orElseThrow(() -> new IllegalStateException("Cannot find USE action for var " + n)); + list.add(new Pair<>(va, arg)); } } @Override public void visit(ThisExpr n, String arg) { - var vaOpt = locateVariableAction(n, va -> va.getName().matches("^.*this$")); + var vaOpt = locateVariableActionThis(n); if (vaOpt.isEmpty()) throw new IllegalStateException("Could not find USE(this)"); list.add(new Pair<>(vaOpt.get(), arg)); @@ -205,7 +215,32 @@ public class ExpressionObjectTreeFinder { public void visit(FieldAccessExpr n, String arg) { if (!arg.isEmpty()) arg = "." + arg; - n.getScope().accept(this, n.getNameAsString() + arg); + ResolvedValueDeclaration resolved; + try { + resolved = n.resolve(); + } catch (UnsolvedSymbolException e) { + Optional optVa = locateVariableAction(n.getScope()); + if (optVa.isPresent()) + list.add(new Pair<>(optVa.get(), n.getNameAsString() + arg)); + else if (n.getScope().isFieldAccessExpr()) + n.getScope().accept(this, n.getNameAsString() + arg); + else + throw e; + return; + } + if (resolved.isEnumConstant() || (resolved.isField() && resolved.asField().isStatic())) { + // For static fields and enum constants, we can't resolve the base type, so we skip that + var optVa = locateVariableAction(n.getScope()); + if (optVa.isPresent()) + list.add(new Pair<>(optVa.get(), n.getNameAsString() + arg)); + else if (n.getScope().isFieldAccessExpr()) + n.getScope().accept(this, n.getNameAsString() + arg); + else + throw new IllegalStateException("Could not find USE(" + n.getScope().toString() + ")"); + } else { + // Resolve the scope and accumulate the argument. + n.getScope().accept(this, n.getNameAsString() + arg); + } } @Override @@ -218,7 +253,7 @@ public class ExpressionObjectTreeFinder { visitCall(n, arg); } - protected void visitCall(Resolvable call, String arg) { + private void visitCall(Resolvable call, String arg) { if (ASTUtils.shouldVisitArgumentsForMethodCalls(call)) return; VariableAction lastUseOut = null; @@ -271,13 +306,23 @@ public class ExpressionObjectTreeFinder { @Override public void visit(PatternExpr n, String arg) {} - protected Optional locateVariableAction(Expression expression, Predicate predicate) { + private Optional locateVariableAction(Expression expression, Predicate predicate) { return graphNode.getVariableActions().stream() .filter(VariableAction::isUsage) .filter(predicate) .filter(va -> va.matches(expression)) .findAny(); } + + /** Locates a variable action that has the given expression with the given expression's text as name. */ + private Optional locateVariableAction(Expression expression) { + return locateVariableAction(expression, va -> va.getName().equals(expression.toString())); + } + + /** Locates a variable action with the given expression and whose name matches this, Class.this, Class.Class.this... */ + private Optional locateVariableActionThis(Expression expression) { + return locateVariableAction(expression, va -> va.getName().matches("^.*this$")); + } }, ""); return list; } @@ -290,11 +335,20 @@ public class ExpressionObjectTreeFinder { if (targetAction.hasObjectTree()) { boolean sourceTypesInClassGraph = sourceAction.getDynamicTypes().stream() .anyMatch(ClassGraph.getInstance()::containsType); - if (sourceTypesInClassGraph && !sourceAction.hasObjectTree()) + if (sourceTypesInClassGraph && !hasObjectTreeAt(sourceAction, sourceMember)) ObjectTree.copyTargetTreeToSource(sourceAction.getObjectTree(), targetAction.getObjectTree(), sourceMember, targetMember); sourceAction.setPDGTreeConnectionTo(targetAction, sourceMember, targetMember); } else { sourceAction.setPDGValueConnection(sourceMember); } } + + protected boolean hasObjectTreeAt(VariableAction action, String prefix) { + if (!action.hasObjectTree()) + return false; + ObjectTree ot = action.getObjectTree(); + if (!prefix.isEmpty()) + prefix = "." + prefix; + return ot.hasChildrenPoly(ObjectTree.ROOT_NAME + prefix); + } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/GraphNodeContentVisitor.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/GraphNodeContentVisitor.java index 282f73384f7cd82a3975ef9231ef0cf190d5aeb3..261448cfce62aba29bcfc9844ac4996ffde1fde5 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/GraphNodeContentVisitor.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/GraphNodeContentVisitor.java @@ -46,6 +46,11 @@ public class GraphNodeContentVisitor extends VoidVisitorAdapter { n.getParameter().accept(this, arg); } + @Override + public void visit(ClassOrInterfaceDeclaration n, A arg) { + // A node representing a class or interface declaration has no relevant elements to be visited. + } + @Override public void visit(ConstructorDeclaration n, A arg) { // A node representing a constructor declaration has no relevant elements to be visited. @@ -68,12 +73,12 @@ public class GraphNodeContentVisitor extends VoidVisitorAdapter { @Override public void visit(EnumConstantDeclaration n, A arg) { - throw new UnsupportedOperationException(); + n.getArguments().accept(this, arg); } @Override public void visit(EnumDeclaration n, A arg) { - throw new UnsupportedOperationException(); + // This node should not contain other elements } @Override diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/cfg/CFG.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/cfg/CFG.java index 347fbeab25c10151359ed5da9b3fb9e5c2759317..3516912c26636fc1a673595c609448912cd5be00 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/cfg/CFG.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/cfg/CFG.java @@ -98,14 +98,11 @@ public class CFG extends GraphWithRootNode> { stream = stream.takeWhile(va -> va != var); List list = stream.filter(var::matches).filter(filter).collect(Collectors.toList()); if (!list.isEmpty()) { - boolean found = false; - for (int i = list.size() - 1; i >= 0 && !found; i--) { + for (int i = list.size() - 1; i >= 0; i--) { result.add(list.get(i)); if (!list.get(i).isOptional()) - found = true; + return true; } - if (found) - return true; } // Not found: traverse backwards! diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/cfg/CFGBuilder.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/cfg/CFGBuilder.java index f34565ae4e7ddbc189e74c60ebe7cac7021fd302..67154709432abe59cfa3f5d4537853ab3f5c95fc 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/cfg/CFGBuilder.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/cfg/CFGBuilder.java @@ -6,6 +6,7 @@ import com.github.javaparser.ast.body.ConstructorDeclaration; import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.expr.BooleanLiteralExpr; import com.github.javaparser.ast.expr.Expression; +import com.github.javaparser.ast.expr.ObjectCreationExpr; import com.github.javaparser.ast.expr.SimpleName; import com.github.javaparser.ast.stmt.*; import com.github.javaparser.ast.visitor.VoidVisitor; @@ -53,7 +54,8 @@ public class CFGBuilder extends VoidVisitorAdapter { /** Lists of labelled continue statements, mapped according to their label. */ protected final Map>> continueMap = new HashMap<>(); /** Return statements that should be connected to the final node, if it is created at the end of the */ - protected final List> returnList = new LinkedList<>(); + // TODO: Type changed due to exception handling + protected final List> returnList = new LinkedList<>(); /** Stack of lists of hanging cases on switch statements */ protected final Deque>> switchEntriesStack = new LinkedList<>(); @@ -337,6 +339,13 @@ public class CFGBuilder extends VoidVisitorAdapter { connectTo(n); } + @Override + public void visit(ObjectCreationExpr n, Void arg) { + // Skip anonymous classes + if (n.getAnonymousClassBody().isEmpty()) + super.visit(n, arg); + } + // ====================================================================== // ============================ Declarations ============================ // ====================================================================== diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/exceptionsensitive/AllenSDG.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/exceptionsensitive/AllenSDG.java new file mode 100644 index 0000000000000000000000000000000000000000..308b44412bf1482e7788e37833f2832360cba8b8 --- /dev/null +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/exceptionsensitive/AllenSDG.java @@ -0,0 +1,12 @@ +package es.upv.mist.slicing.graphs.exceptionsensitive; + +import es.upv.mist.slicing.graphs.jsysdg.JSysDG; +import es.upv.mist.slicing.slicing.AllenSlicingAlgorithm; +import es.upv.mist.slicing.slicing.SlicingAlgorithm; + +public class AllenSDG extends JSysDG { + @Override + protected SlicingAlgorithm createSlicingAlgorithm() { + return new AllenSlicingAlgorithm(this); + } +} diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/exceptionsensitive/ESCFG.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/exceptionsensitive/ESCFG.java index e9bd74c4a464c174374c5f7768cca536b7508475..7c84f22dc40b1830344206d4b1b89109532afb9e 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/exceptionsensitive/ESCFG.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/exceptionsensitive/ESCFG.java @@ -242,17 +242,23 @@ public class ESCFG extends ACFG { @Override public void visit(MethodCallExpr n, Void arg) { + n.getArguments().accept(this, arg); + n.getScope().ifPresent(s -> s.accept(this, arg)); visitCallForExceptions(n); } @Override public void visit(ObjectCreationExpr n, Void arg) { + n.getArguments().accept(this, arg); + n.getScope().ifPresent(s -> s.accept(this, arg)); visitCallForExceptions(n); } @Override public void visit(ExplicitConstructorInvocationStmt n, Void arg) { stmtStack.push(n); + n.getExpression().ifPresent(e -> e.accept(this, arg)); + n.getArguments().accept(this, arg); connectTo(n); visitCallForExceptions(n); stmtStack.pop(); @@ -375,7 +381,14 @@ public class ESCFG extends ACFG { @Override public void visit(ReturnStmt returnStmt, Void arg) { stmtStack.push(returnStmt); - super.visit(returnStmt, arg); + GraphNode node = connectTo(returnStmt); + // This change is for exception/normal exits, in which only the normal + // exit constitutes a valid return, and thus the type of returnList must change. + returnStmt.getExpression().ifPresent(e -> e.accept(this, arg)); + returnList.addAll(hangingNodes); + // end of change w.r.t. ACFG + clearHanging(); + nonExecHangingNodes.add(node); stmtStack.pop(); } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/exceptionsensitive/ExceptionSensitiveCallConnector.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/exceptionsensitive/ExceptionSensitiveCallConnector.java index d43d57bdde9aafb537d6d91c8879fa3437d2258b..ab972332f313f413600e406a6d9124ab4d58bc4c 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/exceptionsensitive/ExceptionSensitiveCallConnector.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/exceptionsensitive/ExceptionSensitiveCallConnector.java @@ -12,6 +12,7 @@ import es.upv.mist.slicing.graphs.sdg.CallConnector; import es.upv.mist.slicing.nodes.SyntheticNode; import es.upv.mist.slicing.nodes.exceptionsensitive.*; import es.upv.mist.slicing.nodes.io.CallNode; +import es.upv.mist.slicing.utils.ASTUtils; import java.util.*; import java.util.stream.Collectors; @@ -55,11 +56,11 @@ public class ExceptionSensitiveCallConnector extends CallConnector { protected void connectNormalNodes(Set> synthNodes, Resolvable call, CallableDeclaration decl) { ReturnNode normalReturn = (ReturnNode) synthNodes.stream() .filter(NormalReturnNode.class::isInstance) - .filter(n -> n.getAstNode() == call) + .filter(n -> n.getAstNode() == call || ASTUtils.equalsWithRange(n.getAstNode(), call)) .findAny().orElseThrow(); ExitNode normalExit = (ExitNode) synthNodes.stream() .filter(NormalExitNode.class::isInstance) - .filter(n -> n.getAstNode() == decl) + .filter(n -> n.getAstNode() == decl || ASTUtils.equalsWithRange(n.getAstNode(), decl)) .findAny().orElseThrow(); ((ESSDG) sdg).addReturnArc(normalExit, normalReturn); } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysCFG.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysCFG.java index 53695e178f90b4f4c10aa19eb3f0a63a308b541b..1a6b94a5093d13b1c1ffa2661d6196b4e6d69579 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysCFG.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysCFG.java @@ -5,6 +5,7 @@ import com.github.javaparser.ast.NodeList; import com.github.javaparser.ast.body.CallableDeclaration; import com.github.javaparser.ast.body.ConstructorDeclaration; import com.github.javaparser.ast.body.FieldDeclaration; +import com.github.javaparser.ast.body.TypeDeclaration; import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.ThisExpr; import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt; @@ -64,7 +65,7 @@ public class JSysCFG extends ESCFG { /** Given a usage of an object member, find the last definitions of that member. * This method returns a list of variable actions, where the caller can find the member. */ - public List findLastDefinitionOfObjectMember(VariableAction usage, String member) { + public List findLastDefinitionOfObjectMember(VariableAction usage, String[] member) { return findLastVarActionsFrom(usage, def -> def.isDefinition() && def.hasTreeMember(member)); } @@ -110,7 +111,7 @@ public class JSysCFG extends ESCFG { /** Given an action that defines a member, locates the previous total definition that gave * it value. */ - public List findLastTotalDefinitionOf(VariableAction action, String member) { + public List findLastTotalDefinitionOf(VariableAction action, String[] member) { return findLastVarActionsFrom(action, def -> (def.isDeclaration() && def.hasTreeMember(member)) || (def.isDefinition() && def.asDefinition().isTotallyDefinedMember(member))); @@ -119,7 +120,7 @@ public class JSysCFG extends ESCFG { /** Given a definition of a given member, locate all definitions of the same object until a definition * containing the given member is found (not including that last one). If the member is found in the * given definition, it will return a list with only the given definition. */ - public List findNextObjectDefinitionsFor(VariableAction definition, String member) { + public List findNextObjectDefinitionsFor(VariableAction definition, String[] member) { if (!this.containsVertex(definition.getGraphNode())) throw new NodeNotFoundException(definition.getGraphNode(), this); if (definition.hasTreeMember(member)) @@ -134,7 +135,7 @@ public class JSysCFG extends ESCFG { * the given argument. This search stops after finding a matching action in each branch. */ protected boolean findNextVarActionsFor(Set> visited, List result, GraphNode currentNode, VariableAction var, - Predicate filter, String memberName) { + Predicate filter, String[] memberName) { // Base case if (visited.contains(currentNode)) return true; @@ -197,7 +198,7 @@ public class JSysCFG extends ESCFG { connectTo(n); // 2. Insert dynamic class code (only for super()) if (!n.isThis()) - ASTUtils.getClassInit(ASTUtils.getClassNode(rootNode.getAstNode()), false) + ASTUtils.getTypeInit(n.findAncestor(TypeDeclaration.class).orElseThrow(), false) .forEach(node -> node.accept(this, arg)); // 3. Handle exceptions super.visitCallForExceptions(n); @@ -213,7 +214,7 @@ public class JSysCFG extends ESCFG { @Override public void visit(ConstructorDeclaration n, Void arg) { // Insert call to super() if it is implicit. - if (!ASTUtils.constructorHasExplicitConstructorInvocation(n)){ + if (ASTUtils.shouldInsertExplicitConstructorInvocation(n)) { var superCall = new ExplicitConstructorInvocationStmt(null, null, false, null, new NodeList<>()); methodInsertedInstructions.add(superCall); n.getBody().addStatement(0, superCall); @@ -237,6 +238,22 @@ public class JSysCFG extends ESCFG { } } + @Override + protected void buildEnter(CallableDeclaration callableDeclaration) { + super.buildEnter(callableDeclaration); + // enums have no super(), so the implicitly inserted instructions + // must be placed after the root node + if (callableDeclaration.isConstructorDeclaration()) { + ConstructorDeclaration cd = callableDeclaration.asConstructorDeclaration(); + TypeDeclaration type = cd.findAncestor(TypeDeclaration.class).orElseThrow(); + if (!ASTUtils.shouldInsertExplicitConstructorInvocation(cd) && + type.isEnumDeclaration() && + ASTUtils.shouldInsertDynamicInitInEnum(cd)) { + ASTUtils.getTypeInit(type, false).forEach(n -> n.accept(this, null)); + } + } + } + /** * Sets the expression for all return statements contained in its argument. * @param node The AST to search for return statements. diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysCallConnector.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysCallConnector.java index 05b5f15f3abf51db1c886d5e90dceff5c4759b6e..442b128595952ed232b537dd4b33525c6eb6b3d9 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysCallConnector.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysCallConnector.java @@ -51,9 +51,13 @@ public class JSysCallConnector extends ExceptionSensitiveCallConnector { /** Whether the given formal node represents an object with an object tree. */ protected boolean formalIsObject(FormalIONode formalNode) { - return formalNode.getVariableName().equals("this") - || !formalNode.getAstNode().getParameterByName(formalNode.getVariableName()) - .orElseThrow().getType().isPrimitiveType(); + if (formalNode.getVariableName().equals("-output-")) { + return ASTUtils.declarationReturnIsObject(formalNode.getAstNode()); + } else { + return formalNode.getVariableName().equals("this") + || !formalNode.getAstNode().getParameterByName(formalNode.getVariableName()) + .orElseThrow().getType().isPrimitiveType(); + } } /** Connects the object tree from the last variable action in the source node to diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysDG.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysDG.java index 6956213f4728797d9eaa816d96cda7646b759f83..b43e9cf01c38d1bb699ae6ba1ca59c7d006e89b1 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysDG.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysDG.java @@ -3,17 +3,20 @@ package es.upv.mist.slicing.graphs.jsysdg; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.Modifier; import com.github.javaparser.ast.NodeList; -import com.github.javaparser.ast.body.CallableDeclaration; -import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; -import com.github.javaparser.ast.body.ConstructorDeclaration; +import com.github.javaparser.ast.body.*; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; +import es.upv.mist.slicing.arcs.pdg.StructuralArc; import es.upv.mist.slicing.graphs.ClassGraph; import es.upv.mist.slicing.graphs.augmented.PSDG; import es.upv.mist.slicing.graphs.cfg.CFG; import es.upv.mist.slicing.graphs.exceptionsensitive.ESSDG; import es.upv.mist.slicing.graphs.exceptionsensitive.ExceptionSensitiveCallConnector; import es.upv.mist.slicing.graphs.pdg.PDG; +import es.upv.mist.slicing.nodes.GraphNode; +import es.upv.mist.slicing.nodes.VariableAction; +import es.upv.mist.slicing.nodes.io.FormalIONode; +import es.upv.mist.slicing.nodes.oo.MemberNode; import es.upv.mist.slicing.slicing.JSysDGSlicingAlgorithm; import es.upv.mist.slicing.slicing.SlicingAlgorithm; import es.upv.mist.slicing.utils.NodeHashSet; @@ -41,15 +44,28 @@ public class JSysDG extends ESSDG { super.build(nodeList); } + @Override + protected void createClassGraph(NodeList nodeList) { + super.createClassGraph(nodeList); + insertTypeNodes(); + } + /** Create implicit constructors, and store them in a set so that they may be built with implicit nodes. */ protected void insertImplicitConstructors(NodeList nodeList) { nodeList.accept(new ModifierVisitor<>() { @Override public Visitable visit(ClassOrInterfaceDeclaration n, Object arg) { - if (n.getConstructors().isEmpty()) + if (!n.isInterface() && n.getConstructors().isEmpty()) newlyInsertedConstructors.add(n.addConstructor(Modifier.Keyword.PUBLIC)); return super.visit(n, arg); } + + @Override + public Visitable visit(EnumDeclaration n, Object arg) { + if (n.getConstructors().isEmpty()) + newlyInsertedConstructors.add(n.addConstructor()); + return super.visit(n, arg); + } }, null); } @@ -72,11 +88,67 @@ public class JSysDG extends ESSDG { @Override protected void connectCalls() { new JSysCallConnector(JSysDG.this).connectAllCalls(callGraph); + connectEnumToFormalIn(); + } + + protected void connectEnumToFormalIn() { + for (GraphNode g1 : vertexSet()) { + if (!(g1.getAstNode() instanceof EnumDeclaration)) + continue; + VariableAction a1 = g1.getLastVariableAction(); + for (GraphNode g2 : vertexSet()) { + if (g2 instanceof FormalIONode) { + FormalIONode fIn = (FormalIONode) g2; + if (fIn.isInput() && fIn.getVariableName().equals(a1.getName())) + a1.applySDGTreeConnection(JSysDG.this, g2.getLastVariableAction()); + } + } + } } @Override protected void createSummaryArcs() { new SummaryArcAnalyzer(JSysDG.this, callGraph).analyze(); } + + /** Adds type nodes (classes, interfaces, enums) to the SDG, along with their static fields. */ + protected void insertTypeNodes() { + for (ClassGraph.Vertex> cgVertex : ClassGraph.getInstance().typeVertices()) { + String kind; + if (cgVertex.getDeclaration() instanceof EnumDeclaration) { + kind = "enum"; + } else if (cgVertex.getDeclaration() instanceof ClassOrInterfaceDeclaration) { + if (((ClassOrInterfaceDeclaration) cgVertex.getDeclaration()).isInterface()) + kind = "interface"; + else + kind = "class"; + } else { + throw new IllegalStateException("Invalid kind of type node"); + } + + String typeName = cgVertex.getDeclaration().getNameAsString(); + GraphNode typeNode = addVertex(kind + " " + typeName, cgVertex.getDeclaration()); + VariableAction typeDef = new VariableAction.Definition(VariableAction.DeclarationType.TYPE, typeName, typeNode); + typeNode.addVariableAction(typeDef); + + for (FieldDeclaration fieldDecl : cgVertex.getDeclaration().getFields()) + if (fieldDecl.isStatic()) + for (VariableDeclarator vd : fieldDecl.getVariables()) + typeNode.getLastVariableAction().getObjectTree().addStaticField(vd.getNameAsString(), fieldDecl); + + // Enums have additional static fields: their entries or constants + if (cgVertex.getDeclaration() instanceof EnumDeclaration) + for (EnumConstantDeclaration ecDecl : ((EnumDeclaration) cgVertex.getDeclaration()).getEntries()) + typeDef.getObjectTree().addStaticField(ecDecl.getNameAsString(), ecDecl); + + // Copy object tree nodes to the SDG + addVertex(typeDef.getObjectTree().getMemberNode()); + addEdge(typeNode, typeDef.getObjectTree().getMemberNode(), new StructuralArc()); + for (MemberNode memberNode : typeDef.getObjectTree().nodeIterable()) { + addVertex(memberNode); + addEdge(memberNode.getParent(), memberNode, new StructuralArc()); + } + } + } } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysPDG.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysPDG.java index bb75c9a06e3b8c25480aa7c06d552cc209ed9d94..47fb133de71fc900b9e4cb8cbd242c40e0d66597 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysPDG.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/JSysPDG.java @@ -18,6 +18,7 @@ import java.util.LinkedList; import java.util.List; import static es.upv.mist.slicing.nodes.ObjectTree.ROOT_NAME; +import static es.upv.mist.slicing.nodes.ObjectTree.ROOT_NODE; public class JSysPDG extends ESPDG { public JSysPDG() { @@ -43,8 +44,8 @@ public class JSysPDG extends ESPDG { } // definicion de miembro --object-flow--> definicion de raiz - protected void addObjectFlowDependencyArc(VariableAction nextDefinitionRoot, String memberDefined, VariableAction definition) { - MemberNode defMember = definition.getObjectTree().getNodeFor(memberDefined); + protected void addObjectFlowDependencyArc(VariableAction nextDefinitionRoot, String[] memberDefined, VariableAction definition) { + MemberNode defMember = definition.getObjectTree().getNodeFor(true, memberDefined); addEdge(defMember, graphNodeOf(nextDefinitionRoot), new ObjectFlowDependencyArc()); } @@ -59,23 +60,23 @@ public class JSysPDG extends ESPDG { } // definicion de miembro --flow--> uso de miembro - protected void addFlowDependencyArc(VariableAction definition, VariableAction usage, String objMember) { - GraphNode defMember = definition.getObjectTree().getNodeFor(objMember); - GraphNode useMember = usage.getObjectTree().getNodeFor(objMember); + protected void addFlowDependencyArc(VariableAction definition, VariableAction usage, String[] objMember) { + GraphNode defMember = definition.getObjectTree().getNodeFor(true, objMember); + GraphNode useMember = usage.getObjectTree().getNodeFor(true, objMember); addEdge(defMember, useMember, new FlowDependencyArc(objMember)); } - protected void addValueDependencyArc(VariableAction usage, String member, GraphNode statement) { - addEdge(usage.getObjectTree().getNodeFor(member), statement, new FlowDependencyArc(member)); + protected void addValueDependencyArc(VariableAction usage, GraphNode statement) { + addEdge(usage.getObjectTree().getMemberNode(), statement, new FlowDependencyArc(ROOT_NAME)); } - protected void addTotalDefinitionDependencyArc(VariableAction totalDefinition, VariableAction target, String member) { - if (member.equals(ROOT_NAME)) + protected void addTotalDefinitionDependencyArc(VariableAction totalDefinition, VariableAction target, String[] member) { + if (member.length == 1 && member[0].equals(ROOT_NAME)) addEdge(graphNodeOf(totalDefinition), graphNodeOf(target), new TotalDefinitionDependenceArc()); else - addEdge(totalDefinition.getObjectTree().getNodeFor(member), - target.getObjectTree().getNodeFor(member), - new TotalDefinitionDependenceArc()); + for (MemberNode from : totalDefinition.getObjectTree().getNodesForPoly(member)) + for (MemberNode to : target.getObjectTree().getNodesForPoly(member)) + addEdge(from, to, new TotalDefinitionDependenceArc()); } protected GraphNode graphNodeOf(VariableAction action) { @@ -122,10 +123,10 @@ public class JSysPDG extends ESPDG { * non-synthetic definitions. Connects each member to its previous total definition. */ private void buildTotalDefinitionDependence(JSysCFG jSysCFG, VariableAction varAct) { if (!varAct.isPrimitive() && (varAct.isUsage() || (varAct.isDefinition() && !varAct.isSynthetic()))) { - jSysCFG.findLastTotalDefinitionOf(varAct, ROOT_NAME).forEach(totalDef -> addTotalDefinitionDependencyArc(totalDef, varAct, ROOT_NAME)); + jSysCFG.findLastTotalDefinitionOf(varAct, ROOT_NODE).forEach(totalDef -> addTotalDefinitionDependencyArc(totalDef, varAct, ROOT_NODE)); if (!varAct.hasObjectTree()) return; - for (String member : varAct.getObjectTree().nameIterable()) + for (String[] member : varAct.getObjectTree().nameAsArrayIterable()) jSysCFG.findLastTotalDefinitionOf(varAct, member).forEach(totalDef -> addTotalDefinitionDependencyArc(totalDef, varAct, member)); } } @@ -139,7 +140,7 @@ public class JSysPDG extends ESPDG { jSysCFG.findLastDefinitionOfObjectRoot(varAct).forEach(def -> addObjectFlowDependencyArc(def, varAct)); if (!varAct.hasObjectTree()) return; - for (String member : varAct.getObjectTree().nameIterable()) + for (String[] member : varAct.getObjectTree().nameAsArrayIterable()) jSysCFG.findLastDefinitionOfObjectMember(varAct, member).forEach(def -> addFlowDependencyArc(def, varAct, member)); } } @@ -152,7 +153,7 @@ public class JSysPDG extends ESPDG { // Object flow definition --> definition if (varAct.isPrimitive() || !varAct.hasObjectTree()) return; - for (String member : varAct.getObjectTree().nameIterable()) + for (String[] member : varAct.getObjectTree().nameAsArrayIterable()) jSysCFG.findNextObjectDefinitionsFor(varAct, member).forEach(def -> addObjectFlowDependencyArc(varAct, member, def)); } @@ -248,7 +249,7 @@ public class JSysPDG extends ESPDG { if (action.isDefinition() && action.hasObjectTree() && action.getName().equals(ESCFG.ACTIVE_EXCEPTION_VARIABLE)) - addValueDependencyArc(action, ROOT_NAME, node); + addValueDependencyArc(action, node); } } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/OriginalJSysDG.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/OriginalJSysDG.java new file mode 100644 index 0000000000000000000000000000000000000000..e35848060832f952ec0b684b6a77dc8713a3201c --- /dev/null +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/OriginalJSysDG.java @@ -0,0 +1,26 @@ +package es.upv.mist.slicing.graphs.jsysdg; + +import es.upv.mist.slicing.graphs.cfg.CFG; +import es.upv.mist.slicing.graphs.pdg.PDG; +import es.upv.mist.slicing.slicing.OriginalJSysDGSlicingAlgorithm; +import es.upv.mist.slicing.slicing.SlicingAlgorithm; + +public class OriginalJSysDG extends JSysDG { + @Override + protected Builder createBuilder() { + return new Builder(); + } + + @Override + protected SlicingAlgorithm createSlicingAlgorithm() { + return new OriginalJSysDGSlicingAlgorithm(this); + } + + public class Builder extends JSysDG.Builder { + @Override + protected PDG createPDG(CFG cfg) { + assert cfg instanceof JSysCFG; + return new OriginalJSysPDG((JSysCFG) cfg); + } + } +} diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/OriginalJSysPDG.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/OriginalJSysPDG.java new file mode 100644 index 0000000000000000000000000000000000000000..22ff610b565a2f73acf25550b5976eedf1c29704 --- /dev/null +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/jsysdg/OriginalJSysPDG.java @@ -0,0 +1,95 @@ +package es.upv.mist.slicing.graphs.jsysdg; + +import es.upv.mist.slicing.graphs.pdg.PDG; +import es.upv.mist.slicing.nodes.GraphNode; +import es.upv.mist.slicing.nodes.VariableAction; + +public class OriginalJSysPDG extends JSysPDG { + public OriginalJSysPDG() { + this(new JSysCFG()); + } + + public OriginalJSysPDG(JSysCFG cfg) { + super(cfg); + } + + @Override + protected PDG.Builder createBuilder() { + return new Builder(); + } + + // definicion de raiz --object-flow--> uso de raiz + @Override + protected void addObjectFlowDependencyArc(VariableAction definition, VariableAction usage) { + throw new UnsupportedOperationException(); + } + + // definicion de miembro --object-flow--> definicion de raiz + @Override + protected void addObjectFlowDependencyArc(VariableAction nextDefinitionRoot, String[] memberDefined, VariableAction definition) { + throw new UnsupportedOperationException(); + } + + @Override + protected void addTotalDefinitionDependencyArc(VariableAction totalDefinition, VariableAction target, String[] member) { + throw new UnsupportedOperationException(); + } + + @Override + public void addDataDependencyArc(VariableAction src, VariableAction tgt) { + throw new UnsupportedOperationException("Use flow or object-flow dependency"); + } + + protected class Builder extends JSysPDG.Builder { + + @Override + protected void buildDataDependency() { + addSyntheticNodesToPDG(); + applyTreeConnections(); + buildJSysDataDependency(); + valueDependencyForThrowStatements(); + } + + /** Compute flow, object flow and total definition dependence. */ + @Override + protected void buildJSysDataDependency() { + JSysCFG jSysCFG = (JSysCFG) cfg; + for (GraphNode node : vertexSet()) { + for (VariableAction varAct : node.getVariableActions()) { + // Total definition dependence + if (varAct.isUsage()) + buildUsageDependencies(jSysCFG, varAct); + else if (varAct.isDefinition()) + buildDefinitionDependencies(jSysCFG, varAct); + else if (varAct.isDeclaration()) + buildDeclarationDependencies(jSysCFG, varAct); + } + } + } + + /** Generate dependencies to usages, including flow dependency for primitives, + * object flow for object roots and flow for object members. */ + private void buildUsageDependencies(JSysCFG jSysCFG, VariableAction varAct) { + if (varAct.isPrimitive()) { + jSysCFG.findLastDefinitionOfPrimitive(varAct).forEach(def -> addFlowDependencyArc(def, varAct)); + } else if (varAct.hasObjectTree()) { + for (String[] member : varAct.getObjectTree().nameAsArrayIterable()) + jSysCFG.findLastDefinitionOfObjectMember(varAct, member).forEach(def -> addFlowDependencyArc(def, varAct, member)); + } + } + + /** Generates dec --> def flow and def --> def object flow dependencies. */ + private void buildDefinitionDependencies(JSysCFG jSysCFG, VariableAction varAct) { + // Flow declaration --> definition + if (!varAct.isSynthetic()) + jSysCFG.findDeclarationFor(varAct).ifPresent(dec -> addFlowDependencyArc(dec, varAct)); + } + + /** Generates dec --> def declaration dependencies for objects (constructors only). */ + private void buildDeclarationDependencies(JSysCFG jSysCFG, VariableAction varAct) { + if (!varAct.getName().startsWith("this.")) + return; + jSysCFG.findAllFutureObjectDefinitionsFor(varAct).forEach(def -> addDeclarationFlowDependencyArc(varAct, def)); + } + } +} diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/oo/DynamicTypeResolver.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/oo/DynamicTypeResolver.java index 779606894f598d0e32c65148ff461f5c0d52a917..97307e63c36e462d836c0450d99a997bf33d7c6c 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/oo/DynamicTypeResolver.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/oo/DynamicTypeResolver.java @@ -2,7 +2,7 @@ package es.upv.mist.slicing.graphs.oo; import com.github.javaparser.ast.Node; import com.github.javaparser.ast.body.CallableDeclaration; -import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.TypeDeclaration; import com.github.javaparser.ast.expr.CastExpr; import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.MethodCallExpr; @@ -67,6 +67,7 @@ public class DynamicTypeResolver { protected Stream resolveMethodCallExpr(MethodCallExpr methodCallExpr) { assert !methodCallExpr.calculateResolvedType().isVoid(); return callGraph.getCallTargets(methodCallExpr) + .filter(ASTUtils::hasBody) // abstract or interface methods must be skipped .map(cfgMap::get) .flatMap(cfg -> cfg.vertexSet().stream()) .filter(node -> node.getAstNode() instanceof ReturnStmt) @@ -151,7 +152,7 @@ public class DynamicTypeResolver { ResolvedClassDeclaration type = expression.calculateResolvedType().asReferenceType() .getTypeDeclaration().orElseThrow().asClass(); return classGraph.subclassesOf(type).stream() - .map(ClassOrInterfaceDeclaration::resolve) + .map(TypeDeclaration::resolve) .map(ASTUtils::resolvedTypeDeclarationToResolvedType); } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/CallConnector.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/CallConnector.java index 26d3c86fe0e6f17f6fec6dea3cedb7f68628c351..c80d51c7755c8485fc1ae695b9c806a0430cbcc0 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/CallConnector.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/CallConnector.java @@ -11,6 +11,7 @@ import es.upv.mist.slicing.nodes.io.ActualIONode; import es.upv.mist.slicing.nodes.io.CallNode; import es.upv.mist.slicing.nodes.io.FormalIONode; import es.upv.mist.slicing.nodes.io.OutputNode; +import es.upv.mist.slicing.utils.ASTUtils; /** Adds interprocedural arcs between the 'PDG components' of an SDG. * Arcs generated include {@link ParameterInOutArc parameter input/output} and @@ -35,6 +36,7 @@ public class CallConnector { protected void connectCall(CallNode callNode, CallGraph callGraph) { var callExpr = (Resolvable) callNode.getAstNode(); callGraph.getCallTargets(callExpr) + .filter(ASTUtils::hasBody) // Added so that all nodes received correspond to a valid CFG root *without body there is no CFG* .map(sdg::findNodeByASTNode) .map(opt -> opt.orElseThrow(IllegalArgumentException::new)) .forEach(node -> connectCall(callNode, node)); diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/InterproceduralActionFinder.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/InterproceduralActionFinder.java index f6b0482df67940e41d9600b2bc4b9fa96f257510..45dd83c4fe20db1ca88387a24b35d52c17b340a2 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/InterproceduralActionFinder.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/InterproceduralActionFinder.java @@ -39,7 +39,7 @@ public abstract class InterproceduralActionFinder exte /** Entry-point to the class. Performs the analysis and then saves the results to the CFG nodes. */ public void save() { if (!built) analyze(); - graph.vertexSet().forEach(this::saveDeclaration); + graph.vertexSet().forEach(this::saveDeclarationFormalNodes); } /** Obtains the StoredAction object with information on which actions have been stored. */ @@ -47,21 +47,31 @@ public abstract class InterproceduralActionFinder exte return actionStoredMap.get(vertex).get(action); } - /** Save the current set of actions associated to the given declaration. It will avoid saving - * duplicates by default, so this method may be called multiple times safely. */ - protected void saveDeclaration(CallGraph.Vertex vertex) { + /** Save the current set of actions associated with the given declaration. This method will + * only generate actual-in and actual-out nodes. It is idempotent, and won't generate duplicates. */ + protected void saveDeclarationActualNodes(CallGraph.Vertex vertex) { var actions = vertexDataMap.get(vertex); // Update stored action map actionStoredMap.computeIfAbsent(vertex, v -> new HashMap<>()); for (A a : actions) actionStoredMap.get(vertex).computeIfAbsent(a, __ -> new StoredAction()); - // FORMAL: per declaration (1) - for (A a : actions) - getStored(vertex, a).storeFormal(() -> sandBoxedHandler(vertex, a, this::handleFormalAction)); // ACTUAL: per call (n) for (CallGraph.Edge edge : graph.incomingEdgesOf(vertex)) actions.stream().sorted(new ParameterFieldSorter(edge)).forEach(a -> - getStored(vertex, a).storeActual(edge, e -> sandBoxedHandler(e, a, this::handleActualAction))); + getStored(vertex, a).storeActual(edge, a, e -> sandBoxedHandler(e, a, this::handleActualAction))); + } + + /** Save the current set of actions associated with the given declaration. This method will + * only generate formal-in and formal-out nodes. It is idempotent, and won't generate duplicates. */ + protected void saveDeclarationFormalNodes(CallGraph.Vertex vertex) { + var actions = vertexDataMap.get(vertex); + // Update stored action map + actionStoredMap.computeIfAbsent(vertex, __ -> new HashMap<>()); + for (A a : actions) + actionStoredMap.get(vertex).computeIfAbsent(a, __ -> new StoredAction()); + // 1 formal per declaration and action + for (A a : actions) + getStored(vertex, a).storeFormal(a, () -> sandBoxedHandler(vertex, a, this::handleFormalAction)); } /** A sandbox to avoid resolution errors when a variable is included that is a class name @@ -100,14 +110,18 @@ public abstract class InterproceduralActionFinder exte @Override protected Set compute(CallGraph.Vertex vertex, Set predecessors) { - saveDeclaration(vertex); - Set newValue = new HashSet<>(vertexDataMap.get(vertex)); - newValue.addAll(initialValue(vertex)); - return newValue; + saveDeclarationActualNodes(vertex); + return initialValue(vertex); } @Override protected Set initialValue(CallGraph.Vertex vertex) { + // Skip abstract vertices + + if (vertex.getDeclaration().isAbstract() || + (vertex.getDeclaration().isMethodDeclaration() && + vertex.getDeclaration().asMethodDeclaration().getBody().isEmpty())) + return new HashSet<>(); CFG cfg = cfgMap.get(vertex.getDeclaration()); assert cfg != null; Stream actionStream = cfg.vertexSet().stream() @@ -117,7 +131,9 @@ public abstract class InterproceduralActionFinder exte // We never analyze synthetic variables (all intraprocedural) .filter(Predicate.not(VariableAction::isSynthetic)) // We skip over non-root variables (for each 'x.a' action we'll find 'x' later) - .filter(VariableAction::isRootAction); + .filter(VariableAction::isRootAction) + // We skip local variables, as those can't be interprocedural + .filter(Predicate.not(VariableAction::isLocalVariable)); Stream filteredStream = mapAndFilterActionStream(actionStream, cfg); Set set = new HashSet<>(); for (Iterator it = filteredStream.iterator(); it.hasNext(); ) { @@ -136,6 +152,21 @@ public abstract class InterproceduralActionFinder exte * filter unwanted items (only if the filter is specific to that type). */ protected abstract Stream mapAndFilterActionStream(Stream stream, CFG cfg); + @Override + protected boolean dataMatch(Set oldData, Set newData) { + if (oldData == newData) + return true; + if (oldData.size() != newData.size()) + return false; + HashMap map = new HashMap<>(); + for (A a : oldData) + map.put(a.getName(), a); + for (A b : newData) + if (!VariableAction.objectTreeMatches(map.get(b.getName()), b)) + return false; + return true; + } + // =========================================================== // ========================= SUBCLASSES ====================== // =========================================================== @@ -168,27 +199,28 @@ public abstract class InterproceduralActionFinder exte * have been saved to the graph or not. */ protected static class StoredAction { /** Whether the action has been saved as actual node for each call. */ - private final Map, Boolean> actualStoredMap = new HashMap<>(); + private final Map, VariableAction> actualStoredMap = new HashMap<>(); /** Whether the action has been saved as formal node. */ - protected boolean formalStored = false; + protected VariableAction formalStored = null; private StoredAction() {} /** If this action has not yet been saved as formal node, use the argument to do so, then mark it as stored. */ - private void storeFormal(Runnable save) { - if (!formalStored) { + private void storeFormal(VariableAction action, Runnable save) { + if (formalStored == null || !VariableAction.objectTreeMatches(action, formalStored)) { save.run(); - formalStored = true; + formalStored = action; } } /** If this action has not yet been saved as actual node for the given edge, * use the consumer to do so, then mark it as stored. */ - private void storeActual(CallGraph.Edge edge, Consumer> save) { - if (!actualStoredMap.getOrDefault(edge, false)) { + private void storeActual(CallGraph.Edge edge, VariableAction action, Consumer> save) { + VariableAction storedAction = actualStoredMap.get(edge); + if (storedAction == null || !VariableAction.objectTreeMatches(storedAction, action)) { save.accept(edge); - actualStoredMap.put(edge, true); + actualStoredMap.put(edge, action); } } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/InterproceduralUsageFinder.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/InterproceduralUsageFinder.java index d3a49a18b71f9bd09df2b00f4e917fd21f620cb3..cdaccc0497890bc5df07bb3908639fcf4ed111dc 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/InterproceduralUsageFinder.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/InterproceduralUsageFinder.java @@ -29,27 +29,25 @@ public class InterproceduralUsageFinder extends InterproceduralActionFinder edge : graph.edgeSet()) { - for (ActualIONode actualIn : locateActualInNode(edge)) { - for (VariableAction va : edge.getGraphNode().getVariableActions()) { - if (va instanceof Movable && ((Movable) va).getRealNode().equals(actualIn)) { - ExpressionObjectTreeFinder finder = new ExpressionObjectTreeFinder(edge.getGraphNode()); - if (va.getName().equals("-scope-in-")) { - if (actualIn.getArgument() == null) - finder.locateAndMarkTransferenceToRoot(edge.getCall(), va); - else - finder.locateAndMarkTransferenceToRoot(actualIn.getArgument(), va); - } else if (va.getName().equals("-arg-in-")) { + protected void markTransferenceToRoot(CallGraph.Edge edge) { + for (ActualIONode actualIn : locateActualInNode(edge)) { + for (VariableAction va : edge.getGraphNode().getVariableActions()) { + if (va instanceof Movable && ((Movable) va).getRealNode().equals(actualIn)) { + ExpressionObjectTreeFinder finder = new ExpressionObjectTreeFinder(edge.getGraphNode()); + if (va.getName().equals("-scope-in-")) { + if (actualIn.getArgument() == null) + finder.locateAndMarkTransferenceToRoot(edge.getCall(), va); + else finder.locateAndMarkTransferenceToRoot(actualIn.getArgument(), va); - } + } else if (va.getName().equals("-arg-in-")) { + finder.locateAndMarkTransferenceToRoot(actualIn.getArgument(), va); } } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/SDG.java b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/SDG.java index 101838bb4bc51c00366689c4e313df2620a29545..3ae9c1a42921a01cffa53ab8ab0b4adbe7e7fa3c 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/SDG.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/graphs/sdg/SDG.java @@ -3,6 +3,7 @@ package es.upv.mist.slicing.graphs.sdg; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.NodeList; import com.github.javaparser.ast.body.CallableDeclaration; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.body.ConstructorDeclaration; import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; @@ -135,16 +136,26 @@ public class SDG extends Graph implements Sliceable, Buildable() { @Override public void visit(MethodDeclaration n, Void arg) { + boolean isInInterface = n.findAncestor(ClassOrInterfaceDeclaration.class) + .map(ClassOrInterfaceDeclaration::isInterface).orElse(false); + if (n.isAbstract() || isInInterface) + return; // Allow abstract methods CFG cfg = createCFG(); buildCFG(n, cfg); cfgMap.put(n, cfg); + super.visit(n, arg); } @Override public void visit(ConstructorDeclaration n, Void arg) { + boolean isInInterface = n.findAncestor(ClassOrInterfaceDeclaration.class) + .map(ClassOrInterfaceDeclaration::isInterface).orElse(false); + if (n.isAbstract() || isInInterface) + return; // Allow abstract methods CFG cfg = createCFG(); buildCFG(n, cfg); cfgMap.put(n, cfg); + super.visit(n, arg); } }, null); } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/GraphNode.java b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/GraphNode.java index 19f5438a820b0bac3b906ef19c023a2699e75bbd..436dfb422f110136eaf8b9af344b5839686dd7e6 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/GraphNode.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/GraphNode.java @@ -125,8 +125,10 @@ public class GraphNode implements Comparable> { /** Whether this node contains the given call AST node. */ public boolean containsCall(Resolvable call) { - return methodCalls.stream() - .anyMatch(callInMethod -> ASTUtils.equalsWithRangeInCU((Node) callInMethod, (Node) call)); + for (Resolvable c : methodCalls) + if (c == call || ASTUtils.equalsWithRange((Node) c, (Node) call)) + return true; + return false; } /** Append or prepend the given set of actions to the actions of the given call. */ diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/ObjectTree.java b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/ObjectTree.java index 1211beb11401efad696aab6598b3c879cd36c04d..f9d2bd0011d67031bd1a74fd54e52413fae3dd8e 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/ObjectTree.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/ObjectTree.java @@ -1,5 +1,6 @@ package es.upv.mist.slicing.nodes; +import com.github.javaparser.ast.Node; import com.github.javaparser.resolution.types.ResolvedType; import es.upv.mist.slicing.nodes.oo.MemberNode; import es.upv.mist.slicing.nodes.oo.PolyMemberNode; @@ -11,6 +12,8 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; +import static es.upv.mist.slicing.utils.Utils.arrayJoin; + /** * A tree data structure that mimics the tree found in an object's fields. * Each tree contains a MemberNode that represents its, including a name. @@ -24,6 +27,8 @@ import java.util.stream.Stream; public class ObjectTree implements Cloneable { /** The default name of a tree's root. */ public static final String ROOT_NAME = "-root-"; + /** The representation of the root node in member form. */ + public static final String[] ROOT_NODE = new String[] { ROOT_NAME }; /** Regex pattern to split the root from the fields of a field access expression. */ private static final Pattern FIELD_SPLIT = Pattern.compile("^(?(([_0-9A-Za-z]+\\.)*this)|(?(-?))([_0-9A-Za-z]+\\k)+)(\\.(?.+))?$"); @@ -77,17 +82,32 @@ public class ObjectTree implements Cloneable { String member = removeRoot(memberWithRoot); if (member.isEmpty()) return hasChildren(); - return hasChildrenInternal(member); + return hasChildrenInternal(member, false); + } + + /** Same as {@link #hasChildren(String)}, but considering the optional presence of polymorphic nodes. */ + public boolean hasChildrenPoly(String memberWithRoot) { + String member = removeRoot(memberWithRoot); + if (member.isEmpty()) + return hasChildren(); + return hasChildrenInternal(member, true); } - protected boolean hasChildrenInternal(String members) { + protected boolean hasChildrenInternal(String members, boolean polymorphic) { if (members.contains(".")) { int firstDot = members.indexOf('.'); String first = members.substring(0, firstDot); String rest = members.substring(firstDot + 1); - childrenMap.computeIfAbsent(first, f -> new ObjectTree(f, this)); - return childrenMap.get(first).hasChildrenInternal(rest); + if (polymorphic && !childrenMap.containsKey(first) && !childrenMap.isEmpty()) + return childrenMap.values().stream() + .filter(ot -> ot.getMemberNode() instanceof PolyMemberNode) + .anyMatch(ot -> ot.hasChildrenInternal(members, true)); + return childrenMap.containsKey(first) && childrenMap.get(first).hasChildrenInternal(rest, polymorphic); } else { + if (polymorphic && !childrenMap.containsKey(members) && !childrenMap.isEmpty()) + return childrenMap.values().stream() + .filter(ot -> ot.getMemberNode() instanceof PolyMemberNode) + .anyMatch(ot -> ot.hasChildrenInternal(members, true)); return childrenMap.get(members).hasChildren(); } } @@ -110,14 +130,14 @@ public class ObjectTree implements Cloneable { return childrenMap.computeIfAbsent(rt.describe(), n -> new ObjectTree(rt, this)); } - public ObjectTree addType(ResolvedType rt, String prefix) { - String members = removeRoot(prefix); + public ObjectTree addType(ResolvedType rt, String[] prefix) { + String[] members = removeRoot(prefix); Collection trees = findObjectTreeOfPolyMember(members); if (trees.size() > 1) throw new IllegalArgumentException("This method accepts only prefixes with all the necessary types"); for (ObjectTree tree : trees) return tree.addType(rt); - throw new IllegalArgumentException("Could not locate any tree for the given prefix " + prefix); + throw new IllegalArgumentException("Could not locate any tree for the given prefix " + arrayJoin(prefix, ".")); } /** @@ -133,6 +153,11 @@ public class ObjectTree implements Cloneable { return addNonRootField(members); } + public ObjectTree addField(String[] fieldName) { + String[] members = removeRoot(fieldName); + return addNonRootField(members, 0); + } + /** Insert a field in the current level of object tree. The field should be a variable name, * and not contain dots or be blank. */ public ObjectTree addImmediateField(String fieldName) { @@ -141,6 +166,12 @@ public class ObjectTree implements Cloneable { return childrenMap.computeIfAbsent(fieldName, f -> new ObjectTree(f, this)); } + public ObjectTree addStaticField(String fieldName, Node node) { + if (fieldName.contains(".") || fieldName.isBlank()) + throw new IllegalArgumentException("field name must not include dots or be blank!"); + return childrenMap.computeIfAbsent(fieldName, f -> new ObjectTree(new MemberNode(fieldName, node, memberNode))); + } + /** Similar to {@link #addField(String)}, but may be called at any level * and the argument must not contain the root variable. */ private ObjectTree addNonRootField(String members) { @@ -155,6 +186,15 @@ public class ObjectTree implements Cloneable { } } + private ObjectTree addNonRootField(String[] members, int index) { + assert index < members.length; + ObjectTree tree = childrenMap.computeIfAbsent(members[index], f -> new ObjectTree(f, this)); + if (members.length - 1 == index) + return tree; + else + return tree.addNonRootField(members, index + 1); + } + /** Copies the structure of another object tree into this object tree. * All elements inserted in the current tree are a copy of the argument's children and members. */ public void addAll(ObjectTree tree) { @@ -209,33 +249,77 @@ public class ObjectTree implements Cloneable { first = member; rest = ""; } - result = result.stream().flatMap(res -> { + Collection newResult = new LinkedList<>(); + for (ObjectTree res : result) { + if (!res.childrenMap.containsKey(first)) { + for (String key : res.childrenMap.keySet()) { + if (member.startsWith(key)) { + first = member.substring(0, key.length()); + try { + rest = member.substring(key.length() + 1); + } catch (StringIndexOutOfBoundsException e) { + rest = ""; + } + break; + } + } + } ObjectTree ot = res.childrenMap.get(first); if (ot == null && res.childrenMap.size() > 0) { - Collection collection = new LinkedList<>(); - for (ObjectTree child : childrenMap.values()) { + for (ObjectTree child : res.childrenMap.values()) { if (!(child.getMemberNode() instanceof PolyMemberNode) || !child.childrenMap.containsKey(first)) throw new IllegalArgumentException("Could not locate member in object tree"); - collection.add(child.childrenMap.get(first)); + newResult.add(child.childrenMap.get(first)); } - return collection.stream(); + break; } else if (ot == null) { throw new IllegalArgumentException("Could not locate member in object tree"); } else { - return Stream.of(ot); + newResult.add(ot); } - }).collect(Collectors.toList()); + } + result = newResult; member = rest; } return result; } + Collection findObjectTreeOfPolyMember(String[] member) { + Collection result = List.of(this); + for (String field : member) { + Collection newResult = new LinkedList<>(); + for (ObjectTree res : result) { + ObjectTree ot = res.childrenMap.get(field); + if (ot == null && res.childrenMap.size() > 0) { + for (ObjectTree child : res.childrenMap.values()) { + if (!(child.getMemberNode() instanceof PolyMemberNode) || !child.childrenMap.containsKey(field)) + throw new IllegalArgumentException("Could not locate member in object tree"); + newResult.add(child.childrenMap.get(field)); + } + break; + } else if (ot == null) { + throw new IllegalArgumentException("Could not locate member in object tree"); + } else { + newResult.add(ot); + } + } + result = newResult; + } + return result; + } + /** Whether this object tree contains the given member. The argument should contain the root variable name. */ public boolean hasMember(String member) { String field = removeRoot(member); return hasNonRootMember(field, false); } + public boolean hasMember(String[] member) { + if (member.length < 2) + return true; + return hasMemberIndexed(member, 1, false); + } + /** Whether this object tree contains the given member. The argument may omit typing * information (i.e., 'a.x' will find 'a.A.x', where A is a polymorphic node). */ public boolean hasPolyMember(String member) { @@ -243,6 +327,10 @@ public class ObjectTree implements Cloneable { return hasNonRootMember(field, true); } + public boolean hasPolyMember(String[] member) { + return hasMemberIndexed(member, 1, true); + } + /** Similar to hasMember, but valid at any level of the tree and the argument should not contain * the root variable's name. * @see #hasMember(String) */ @@ -265,12 +353,31 @@ public class ObjectTree implements Cloneable { } } + private boolean hasMemberIndexed(String[] member, int index, boolean polymorphic) { + String first = member[index]; + if (polymorphic && !childrenMap.containsKey(first) && !childrenMap.isEmpty()) + return childrenMap.values().stream() + .filter(ot -> ot.getMemberNode() instanceof PolyMemberNode) + .anyMatch(ot -> ot.hasMemberIndexed(member, index, polymorphic)); + if (index + 1 < member.length) + return childrenMap.containsKey(first) && childrenMap.get(first).hasMemberIndexed(member, index + 1, polymorphic); + else return childrenMap.containsKey(first); + } + + public MemberNode getRootNode() { + return memberNode; + } + /** Obtain the member node that corresponds to the given field name (with root). */ public MemberNode getNodeFor(String member) { String field = removeRoot(member); return getNodeForNonRoot(field); } + public MemberNode getNodeFor(boolean withRoot, String... members) { + return getNodeForIndex(members, withRoot ? 1 : 0); + } + /** Similar to getNodeFor, but valid at any level of the tree, and the argument must be the field only. * @see #getNodeFor(String) */ MemberNode getNodeForNonRoot(String members) { @@ -288,6 +395,16 @@ public class ObjectTree implements Cloneable { } } + MemberNode getNodeForIndex(String[] members, int index) { + if (members.length <= index) + return memberNode; + assert childrenMap.containsKey(members[index]); + if (members.length == index + 1) + return childrenMap.get(members[index]).memberNode; + else + return childrenMap.get(members[index]).getNodeForIndex(members, index + 1); + } + /** Similar to {@link #getNodeFor(String)}, but if the argument does not contain * types, it will obtain all member nodes that represent a given field (in multiple * types). For example, the argument 'a.x' may produce 'a.A.x' and 'a.B.x'; whereas @@ -298,6 +415,12 @@ public class ObjectTree implements Cloneable { .collect(Collectors.toList()); } + public Collection getNodesForPoly(String[] membersWithRoot) { + return findObjectTreeOfPolyMember(removeRoot(membersWithRoot)).stream() + .map(ObjectTree::getMemberNode) + .collect(Collectors.toList()); + } + /** @return An iterable through the names (with full prefixes) of all members of this tree, * excluding the root. */ public Iterable nameIterable() { @@ -330,6 +453,35 @@ public class ObjectTree implements Cloneable { }; } + public Iterable nameAsArrayIterable() { + return () -> new Iterator<>() { + final Iterator it = treeIterator(); + + @Override + public boolean hasNext() { + return it.hasNext(); + } + + @Override + public String[] next() { + ObjectTree element = it.next(); + List builder = new ArrayList<>(); + MemberNode node = element.memberNode; + if (node == null) + return new String[] {ROOT_NAME}; + else if (node instanceof PolyMemberNode) + return next(); + else + builder.add(node.getLabel()); + while (node.getParent() instanceof MemberNode) { + node = (MemberNode) node.getParent(); + builder.add(0, node.getLabel()); + } + return builder.toArray(new String[0]); + } + }; + } + /** @return An iterable through the nodes of all members of this tree, excluding the root. */ public Iterable nodeIterable() { return () -> new Iterator<>() { @@ -403,6 +555,19 @@ public class ObjectTree implements Cloneable { throw new IllegalArgumentException("Field should be of the form ., .this., where may not contain dots."); } + public static String[] removeRoot(String[] field) { + int newStart = 1; + for (int i = 0; i < field.length; i++) { + if (field[i].equals("this")) { + newStart = i + 1; + break; + } + } + String[] res = new String[field.length - newStart]; + System.arraycopy(field, newStart, res, 0, res.length); + return res; + } + /** * Utility method to remove the fields a string, retaining just the root. The root element or root of * the object tree should be either "-root-", a valid variable name or an optionally type-prefixed @@ -416,13 +581,27 @@ public class ObjectTree implements Cloneable { throw new IllegalArgumentException("Field should be of the form ., .this., where may not contain dots."); } + public static String[] removeFields(String[] fields) { + Pattern.compile("^(?(([_0-9A-Za-z]+\\.)*this)|(?(-?))([_0-9A-Za-z]+\\k)+)(\\.(?.+))?$"); + int length = 1; + for (int i = 0; i < fields.length; i++) { + if (fields[i].equals("this")) { + length = i + 1; + break; + } + } + String[] res = new String[length]; + System.arraycopy(fields, 0, res, 0, length); + return res; + } + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ObjectTree tree = (ObjectTree) o; return Objects.equals(getMemberName(), tree.getMemberName()) && - childrenMap.values().equals(tree.childrenMap.values()); + childrenMap.equals(tree.childrenMap); } @Override diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableAction.java b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableAction.java index fe30151e7bf3a4d674824dbfcbe8e8ba0a3504ac..c4316a074bd3aaf13d08a33286fdfbf872b04962 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableAction.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableAction.java @@ -1,12 +1,13 @@ package es.upv.mist.slicing.nodes; -import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.TypeDeclaration; import com.github.javaparser.ast.expr.*; import com.github.javaparser.resolution.Resolvable; import com.github.javaparser.resolution.UnsolvedSymbolException; import com.github.javaparser.resolution.declarations.ResolvedMethodLikeDeclaration; import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserEnumConstantDeclaration; import es.upv.mist.slicing.arcs.Arc; import es.upv.mist.slicing.arcs.pdg.DataDependencyArc; import es.upv.mist.slicing.graphs.ClassGraph; @@ -16,6 +17,7 @@ import es.upv.mist.slicing.graphs.jsysdg.JSysPDG; import es.upv.mist.slicing.graphs.pdg.PDG; import es.upv.mist.slicing.utils.ASTUtils; import es.upv.mist.slicing.utils.NodeHashSet; +import es.upv.mist.slicing.utils.Utils; import java.util.*; import java.util.stream.Collectors; @@ -30,13 +32,16 @@ public abstract class VariableAction { STATIC_FIELD, PARAMETER, LOCAL_VARIABLE, - SYNTHETIC; + SYNTHETIC, + TYPE; public static DeclarationType valueOf(ResolvedValueDeclaration resolved) { if (resolved.isType()) return STATIC_FIELD; if (resolved.isField() && resolved.asField().isStatic()) return STATIC_FIELD; + if (resolved instanceof JavaParserEnumConstantDeclaration) + return STATIC_FIELD; if (resolved.isField()) return FIELD; if (resolved.isParameter()) @@ -164,7 +169,7 @@ public abstract class VariableAction { dynamicTypes.add(staticType); if (staticType.isReferenceType() && ClassGraph.getInstance().containsType(staticType.asReferenceType())) { ClassGraph.getInstance().subclassesOf(staticType.asReferenceType()).stream() - .map(ClassOrInterfaceDeclaration::resolve) + .map(TypeDeclaration::resolve) .map(ASTUtils::resolvedTypeDeclarationToResolvedType) .forEach(dynamicTypes::add); } @@ -207,6 +212,14 @@ public abstract class VariableAction { return getObjectTree().hasMember(member); } + public boolean hasTreeMember(String[] member) { + if (member.length == 0) + return hasObjectTree(); + if (!hasObjectTree()) + return false; + return getObjectTree().hasMember(member); + } + /** Whether there is an object tree and it contains the given member. * The search will skip polymorphic nodes if they haven't been specified in the argument. */ public boolean hasPolyTreeMember(String member) { @@ -217,6 +230,14 @@ public abstract class VariableAction { return getObjectTree().hasPolyMember(member); } + public boolean hasPolyTreeMember(String[] member) { + if (member.length == 0) + return hasObjectTree(); + if (!hasObjectTree()) + return false; + return getObjectTree().hasPolyMember(member); + } + public boolean hasObjectTree() { return objectTree != null; } @@ -238,6 +259,20 @@ public abstract class VariableAction { connection.applySDG(sdg); } + public static boolean objectTreeMatches(VariableAction a, VariableAction b) { + if (a == b) + return true; + if (a == null || b == null) + return false; + boolean aHasTree = a.hasObjectTree() && a.getObjectTree().hasChildren(); + boolean bHasTree = b.hasObjectTree() && b.getObjectTree().hasChildren(); + if (aHasTree != bHasTree) + return false; + if (!aHasTree) + return true; + return a.getObjectTree().equals(b.getObjectTree()); + } + // ====================================================== // =================== ROOT ACTIONS ===================== // ====================================================== @@ -246,7 +281,7 @@ public abstract class VariableAction { assert !isRootAction(); if (this instanceof Movable) { Movable movable = (Movable) this; - return new Movable(movable.inner.getRootAction(), movable.getRealNode()); + return new Movable(movable.inner.getRootAction(), movable.getRealNode()); } VariableAction action; if (this instanceof Usage) @@ -419,7 +454,7 @@ public abstract class VariableAction { /** The value to which the variable has been defined. */ protected final Expression expression; /** The members of the object tree that are total definitions. */ - protected String totallyDefinedMember; + protected String[] totallyDefinedMember; public Definition(DeclarationType declarationType, String name, GraphNode graphNode) { this(declarationType, name, graphNode, (Expression) null); @@ -439,18 +474,18 @@ public abstract class VariableAction { this.expression = expression; } - public void setTotallyDefinedMember(String totallyDefinedMember) { + public void setTotallyDefinedMember(String[] totallyDefinedMember) { this.totallyDefinedMember = Objects.requireNonNull(totallyDefinedMember); } - public boolean isTotallyDefinedMember(String member) { + public boolean isTotallyDefinedMember(String[] member) { if (totallyDefinedMember == null) return false; - if (totallyDefinedMember.equals(member)) + if (Arrays.equals(totallyDefinedMember, member)) return true; - if (member.startsWith(totallyDefinedMember) - || ObjectTree.removeRoot(member).startsWith(ObjectTree.removeRoot(totallyDefinedMember))) - return ObjectTree.removeRoot(member).isEmpty() || hasTreeMember(member); + if (Utils.arrayPrefix(totallyDefinedMember, member) + || Utils.arrayPrefix(ObjectTree.removeRoot(member), ObjectTree.removeRoot(totallyDefinedMember))) + return ObjectTree.removeRoot(member).length == 0 || hasTreeMember(member); return false; } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableVisitor.java b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableVisitor.java index 326b7ef2033f74d533bc6df770911e8625e4f752..101b5d1dffc7c777deb4e34606b6bc55ccc35f13 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableVisitor.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/VariableVisitor.java @@ -21,16 +21,16 @@ import es.upv.mist.slicing.nodes.VariableAction.DeclarationType; import es.upv.mist.slicing.nodes.io.ActualIONode; import es.upv.mist.slicing.nodes.io.CallNode; import es.upv.mist.slicing.utils.ASTUtils; -import es.upv.mist.slicing.utils.Logger; import java.util.*; import java.util.stream.Collectors; import static es.upv.mist.slicing.graphs.cfg.CFGBuilder.VARIABLE_NAME_OUTPUT; import static es.upv.mist.slicing.graphs.exceptionsensitive.ESCFG.ACTIVE_EXCEPTION_VARIABLE; -import static es.upv.mist.slicing.nodes.ObjectTree.ROOT_NAME; +import static es.upv.mist.slicing.nodes.ObjectTree.ROOT_NODE; import static es.upv.mist.slicing.nodes.VariableAction.DeclarationType.*; import static es.upv.mist.slicing.nodes.VariableVisitor.Action.*; +import static es.upv.mist.slicing.utils.Utils.arrayJoin; /** A graph node visitor that extracts the actions performed in a given GraphNode. An initial action mode can * be set, to consider variables found a declaration, definition or usage (default). @@ -111,14 +111,13 @@ public class VariableVisitor extends GraphNodeContentVisitor node) { startVisit(node, USE); - groupActionsByRoot(node); generatePolyTrees(node); } @Override public void visit(NameExpr n, Action action) { - String realName = getRealName(n); - if (realName.equals(n.toString())) { + String[] realName = getRealName(n); + if (realName.length == 1 && realName[0].equals(n.toString())) { acceptAction(n, action); } else { VariableAction va = acceptAction(DeclarationType.valueOf(n), realName, action); @@ -155,9 +154,9 @@ public class VariableVisitor extends GraphNodeContentVisitor nodeVAs = graphNode.getVariableActions(); + VariableAction prev = nodeVAs.isEmpty() ? null : nodeVAs.get(nodeVAs.size() - 1); + String nameRoot = arrayJoin(ObjectTree.removeFields(realName), "."); + + boolean matches = prev != null && // must have a previous VA + !(prev instanceof VariableAction.CallMarker) && // CallMarkers can't be combined + !prev.isDeclaration() && // declarations are never combined + !prev.isRootAction() && // Can't combine with root actions + Objects.equals(nameRoot, prev.getName()) && // Name must match + // Type must match: DEF-DEF or USE-USE (DEC never combines) + ((prev.isDefinition() && action.isDefinition()) || + (prev.isUsage() && action.isUse())); + + if (matches) { + va = prev; + } else if (action.isDeclaration()) { + va = new VariableAction.Declaration(declarationType, nameRoot, graphNode); } else if (action.isDefinition()) { assert !definitionStack.isEmpty() || canDefBeNull; - va = new VariableAction.Definition(declarationType, realName, graphNode, definitionStack.peek()); + va = new VariableAction.Definition(declarationType, nameRoot, graphNode, definitionStack.peek()); } else if (action.isUse()) { - va = new VariableAction.Usage(declarationType, realName, graphNode); + va = new VariableAction.Usage(declarationType, nameRoot, graphNode); } else { throw new UnsupportedOperationException(); } + + if (ObjectTree.removeRoot(realName).length > 0) + va.getObjectTree().addField(realName); + va.setOptional(action.isOptional()); if (!realNodeStack.isEmpty()) { va = new VariableAction.Movable(va, realNodeStack.peek()); @@ -238,10 +258,10 @@ public class VariableVisitor extends GraphNodeContentVisitor() { @Override public void visit(NameExpr nameExpr, Void arg) { - String realName = getRealName(nameExpr); + String[] realName = getRealName(nameExpr); definitionStack.push(n.getValue()); - if (!realName.contains(".")) { + if (realName.length < 2) { VariableAction va = acceptAction(nameExpr, realName, action.or(DEFINITION)); va.asDefinition().setTotallyDefinedMember(realName); realNameWithoutRootList.add(""); } else { - String root = ObjectTree.removeFields(realName); + String[] root = ObjectTree.removeFields(realName); VariableAction va = acceptAction(DeclarationType.valueOf(nameExpr), root, action.or(DEFINITION)); va.setStaticType(ASTUtils.resolvedTypeOfCurrentClass(nameExpr)); va.getObjectTree().addField(realName); va.asDefinition().setTotallyDefinedMember(realName); va.addExpression(nameExpr); - realNameWithoutRootList.add(ObjectTree.removeRoot(realName)); + realNameWithoutRootList.add(arrayJoin(ObjectTree.removeRoot(realName), ".")); } definitionStack.pop(); } @@ -323,24 +343,35 @@ public class VariableVisitor extends GraphNodeContentVisitor tail = new ArrayList<>(); + tail.add(fieldAccessExpr.getNameAsString()); boolean traverse = true; while (traverse) { - if (scope.isFieldAccessExpr()) + if (scope.isFieldAccessExpr()) { + tail.add(scope.asFieldAccessExpr().getNameAsString()); scope = scope.asFieldAccessExpr().getScope(); - else if (scope.isEnclosedExpr()) + } else if (scope.isEnclosedExpr()) scope = scope.asEnclosedExpr().getInner(); else if (scope.isCastExpr()) scope = scope.asCastExpr().getExpression(); else traverse = false; } - if (!scope.isNameExpr() && !scope.isThisExpr()) + String[] realName, root; + if (scope.isMethodCallExpr() || scope.isObjectCreationExpr()) { + scope.accept(VariableVisitor.this, action); + tail.add(VARIABLE_NAME_OUTPUT); + realName = tail.toArray(new String[0]); + root = new String[]{VARIABLE_NAME_OUTPUT}; + } else if (scope.isNameExpr() || scope.isThisExpr()) { + realName = getRealName(fieldAccessExpr); + root = ObjectTree.removeFields(realName); + } else { throw new IllegalStateException("only valid assignments are this[.]+ =, and [.]+"); - String realName = getRealName(fieldAccessExpr); - String root = ObjectTree.removeFields(realName); + } definitionStack.push(n.getValue()); VariableAction va; - if (root.equals(scope.toString())) + if (root.length == 1 && root[0].equals(scope.toString())) va = acceptAction(scope, root, action.or(DEFINITION)); else { va = acceptAction(FIELD, root, action.or(DEFINITION)); @@ -352,7 +383,7 @@ public class VariableVisitor extends GraphNodeContentVisitor { init.accept(this, action); definitionStack.push(init); - VariableAction vaDef = acceptAction(LOCAL_VARIABLE, v.getNameAsString(), DEFINITION); + VariableAction vaDef = acceptAction(LOCAL_VARIABLE, vName, DEFINITION); vaDef.addExpression(n); vaDef.setStaticType(v.getType().resolve()); definitionStack.pop(); if (v.getType().isClassOrInterfaceType()) - vaDef.asDefinition().setTotallyDefinedMember(v.getNameAsString()); + vaDef.asDefinition().setTotallyDefinedMember(vName); v.accept(this, action); }); } @@ -416,7 +447,7 @@ public class VariableVisitor extends GraphNodeContentVisitor ASTUtils.initializerForField(n)); @@ -434,7 +465,6 @@ public class VariableVisitor extends GraphNodeContentVisitor vaList = graphNode.getVariableActions(); @@ -485,7 +516,7 @@ public class VariableVisitor extends GraphNodeContentVisitor= 5; VariableAction useOutput = vaList.get(vaList.size() - 3); assert useOutput.isUsage() && useOutput.getName().equals(VARIABLE_NAME_OUTPUT); - defThis.asDefinition().setTotallyDefinedMember("this"); + defThis.asDefinition().setTotallyDefinedMember(new String[]{ "this" }); ObjectTree.copyTargetTreeToSource(defThis.getObjectTree(), useOutput.getObjectTree(), "", ""); useOutput.setPDGTreeConnectionTo(defThis, "", ""); } @@ -507,7 +538,7 @@ public class VariableVisitor extends GraphNodeContentVisitor scope.accept(this, action), () -> { - VariableAction va = acceptAction(FIELD, "this", action); + VariableAction va = acceptAction(FIELD, new String[]{ "this" }, action); va.setStaticType(ASTUtils.resolvedTypeOfCurrentClass((MethodCallExpr) call)); }); // Generate -scope-in- action, so that InterproceduralUsageFinder does not need to do so. @@ -554,7 +585,7 @@ public class VariableVisitor extends GraphNodeContentVisitor (ObjectTree) tree.clone()).orElse(null)); - def.setTotallyDefinedMember(ROOT_NAME); + def.setTotallyDefinedMember(ROOT_NODE); var defMov = new VariableAction.Movable(def, CallNode.Return.create(call)); defMov.setStaticType(ASTUtils.getCallResolvedType(call)); graphNode.addVariableAction(defMov); @@ -579,89 +610,41 @@ public class VariableVisitor extends GraphNodeContentVisitor result = new LinkedList<>(); Expression scope = n.asFieldAccessExpr().getScope(); - StringBuilder builder = new StringBuilder(n.asFieldAccessExpr().getNameAsString()); + result.add(n.asFieldAccessExpr().getNameAsString()); while (scope instanceof FieldAccessExpr) { - builder.insert(0, '.'); - builder.insert(0, scope.asFieldAccessExpr().getNameAsString()); + result.add(0, scope.asFieldAccessExpr().getNameAsString()); scope = scope.asFieldAccessExpr().getScope(); } - builder.insert(0, '.'); - builder.insert(0, getRealName(scope)); - return builder.toString(); + String[] realName = getRealName(scope); + for (int i = realName.length - 1; i >= 0; i--) + result.add(0, realName[i]); + return result.toArray(new String[0]); } - return n.toString(); + return new String[] { n.toString() }; } /** Generate the correct prefix for a NameExpr. Only works for non-static fields. */ protected String getNamePrefix(NameExpr n) { // We only care about non-static fields ResolvedValueDeclaration resolved = n.resolve(); - if (!resolved.isField() || resolved.asField().isStatic()) - return ""; - return "this."; - } - - /** Extracts the parent elements affected by each variable action (e.g. an action on a.x affects a). - * When multiple compatible actions (same root and action) are found, only one parent element is generated. */ - protected void groupActionsByRoot(GraphNode graphNode) { - VariableAction lastRootAction = null; - for (int i = 0; i < graphNode.variableActions.size(); i++) { - VariableAction action = graphNode.variableActions.get(i); - if (action instanceof VariableAction.CallMarker || - action.isDeclaration() || action.isRootAction()) { - if (lastRootAction != null) { - graphNode.variableActions.add(i, lastRootAction); - i++; - lastRootAction = null; - } - continue; - } - if (lastRootAction == null) { - // generate our own root action - lastRootAction = action.getRootAction(); - // It can be representing a fieldAccessExpr or a fieldDeclaration - // in the first, we can use the expression to obtain the 'type.this' or 'object_name' - // in the second, the expression is null but we can extract 'type.this' from realName - } else if (!action.rootMatches(lastRootAction) - || !actionTypeMatches(action, lastRootAction)) { - // No match: add the root before the current element and update counter - graphNode.variableActions.add(i, lastRootAction); - i++; - // generate our own root action - lastRootAction = action.getRootAction(); - } - lastRootAction.getObjectTree().addField(action.getName()); - lastRootAction.copyExpressions(action); - graphNode.variableActions.remove(action); - i--; - } - // Append the last root action if there is any! - if (lastRootAction != null) - graphNode.variableActions.add(lastRootAction); - } - - /** Whether two variable actions perform the same action. */ - private static boolean actionTypeMatches(VariableAction a, VariableAction b) { - return (a.isDeclaration() && b.isDeclaration()) || - (a.isDefinition() && b.isDefinition()) || - (a.isUsage() && b.isUsage()); + return !resolved.isField() || resolved.asField().isStatic() ? null : "this"; } /** Given a graph node, modify the existing object trees in each variable action @@ -686,7 +669,7 @@ public class VariableVisitor extends GraphNodeContentVisitor types, ClassGraph classGraph) { + protected static void polyUnit(ObjectTree oldOT, ObjectTree newOT, Set types, ClassGraph classGraph) { boolean skipPolyNodes = types.stream().noneMatch(classGraph::containsType) || !oldOT.hasChildren() || oldOT.hasPoly(); if (skipPolyNodes) { // Copy as-is @@ -708,15 +691,20 @@ public class VariableVisitor extends GraphNodeContentVisitor dynamicTypesOf(ResolvedType rt, String fieldName, ClassGraph classGraph) { + protected static Set dynamicTypesOf(ResolvedType rt, String fieldName, ClassGraph classGraph) { Optional field = classGraph.findClassField(rt, fieldName); if (field.isEmpty()) return Collections.emptySet(); - ResolvedType fieldType = field.get().getVariable(0).getType().resolve(); + ResolvedType fieldType; + try { + fieldType = field.get().getVariable(0).getType().resolve(); + } catch (UnsupportedOperationException e) { + return Set.of(); + } if (!fieldType.isReferenceType() || !classGraph.containsType(fieldType)) return Set.of(fieldType); return classGraph.subclassesOf(fieldType.asReferenceType()).stream() - .map(ClassOrInterfaceDeclaration::resolve) + .map(TypeDeclaration::resolve) .map(ASTUtils::resolvedTypeDeclarationToResolvedType) .collect(Collectors.toSet()); } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/oo/MemberNode.java b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/oo/MemberNode.java index 19d9bd71c57d1ed9d3615a61fb3924987c53b45e..bda1f689227a59a42348e53753135cace6155f77 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/nodes/oo/MemberNode.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/nodes/oo/MemberNode.java @@ -13,7 +13,11 @@ public class MemberNode extends SyntheticNode { protected GraphNode parent; public MemberNode(String instruction, GraphNode parent) { - super(instruction, null, new LinkedList<>()); + this(instruction, null, parent); + } + + public MemberNode(String instruction, Node astNode, GraphNode parent) { + super(instruction, astNode, new LinkedList<>()); this.parent = parent; } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/AllenSlicingAlgorithm.java b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/AllenSlicingAlgorithm.java new file mode 100644 index 0000000000000000000000000000000000000000..291cdb6fc1c581cd044ebe832109c6cf808d8901 --- /dev/null +++ b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/AllenSlicingAlgorithm.java @@ -0,0 +1,20 @@ +package es.upv.mist.slicing.slicing; + +import es.upv.mist.slicing.arcs.Arc; +import es.upv.mist.slicing.arcs.pdg.ConditionalControlDependencyArc; +import es.upv.mist.slicing.graphs.jsysdg.JSysDG; + +public class AllenSlicingAlgorithm extends JSysDGSlicingAlgorithm { + + public AllenSlicingAlgorithm(JSysDG graph) { + super(graph); + } + + @Override + protected boolean commonIgnoreConditions(Arc arc) { + return arc instanceof ConditionalControlDependencyArc.CC1 || + arc instanceof ConditionalControlDependencyArc.CC2 || + objectFlowIgnore(arc) || + ppdgIgnore(arc); + } +} diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/OriginalJSysDGSlicingAlgorithm.java b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/OriginalJSysDGSlicingAlgorithm.java new file mode 100644 index 0000000000000000000000000000000000000000..88328b60a511938ff9d8c6334d3ded6c4715b69d --- /dev/null +++ b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/OriginalJSysDGSlicingAlgorithm.java @@ -0,0 +1,21 @@ +package es.upv.mist.slicing.slicing; + +import es.upv.mist.slicing.arcs.Arc; +import es.upv.mist.slicing.arcs.pdg.ObjectFlowDependencyArc; +import es.upv.mist.slicing.arcs.pdg.TotalDefinitionDependenceArc; +import es.upv.mist.slicing.arcs.sdg.ParameterInOutArc; +import es.upv.mist.slicing.graphs.jsysdg.JSysDG; + +public class OriginalJSysDGSlicingAlgorithm extends JSysDGSlicingAlgorithm { + public OriginalJSysDGSlicingAlgorithm(JSysDG graph) { + super(graph); + } + + @Override + protected boolean commonIgnoreConditions(Arc arc) { + return arc instanceof ParameterInOutArc.ObjectFlow || + arc instanceof ObjectFlowDependencyArc || + arc instanceof TotalDefinitionDependenceArc || + ppdgIgnore(arc) || essdgIgnore(arc); + } +} diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/SlicePruneVisitor.java b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/SlicePruneVisitor.java index 2d96a3484f4f41cf0077026ddb244a5dd85e2481..f94f015d53cd96400c7c60f30838d1b174c57575 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/slicing/SlicePruneVisitor.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/slicing/SlicePruneVisitor.java @@ -42,6 +42,20 @@ public class SlicePruneVisitor extends ModifierVisitor> { return keep || containsDeclarations ? v : null; } + // ============= Enum visitors ============= + + @Override + public Visitable visit(EnumDeclaration n, NodeHashSet arg) { + boolean keep = arg.contains(n); + Visitable v = super.visit(n, arg); + return keep ? v : null; + } + + @Override + public Visitable visit(EnumConstantDeclaration n, NodeHashSet arg) { + return arg.contains(n) ? n : null; + } + // ========== Class body visitors ========== @Override diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/utils/ASTUtils.java b/sdg-core/src/main/java/es/upv/mist/slicing/utils/ASTUtils.java index db7b1421044531f4eb69fac0a02838e5f382f1a2..55c3037bd22a0e8fb00985f7017ce41e323ce072 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/utils/ASTUtils.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/utils/ASTUtils.java @@ -97,12 +97,21 @@ public class ASTUtils { throw new IllegalArgumentException("Call wasn't of a compatible type!"); } + public static boolean hasBody(CallableDeclaration callableDeclaration) { + if (callableDeclaration instanceof MethodDeclaration) + return ((MethodDeclaration) callableDeclaration).getBody().isPresent() + && !((MethodDeclaration) callableDeclaration).getBody().get().isEmptyStmt(); + if (callableDeclaration instanceof ConstructorDeclaration) + return !((ConstructorDeclaration) callableDeclaration).getBody().isEmptyStmt(); + throw new IllegalStateException("Invalid type of callable declaration"); + } + public static BlockStmt getCallableBody(CallableDeclaration callableDeclaration) { if (callableDeclaration instanceof MethodDeclaration) return ((MethodDeclaration) callableDeclaration).getBody().orElseThrow(() -> new IllegalStateException("Graph creation is not allowed for abstract or native methods!")); if (callableDeclaration instanceof ConstructorDeclaration) return ((ConstructorDeclaration) callableDeclaration).getBody(); - throw new IllegalStateException("The method must have a body!"); + throw new IllegalStateException("Invalid type of callable declaration!"); } /** Compute the resolved type that is returned from a given method call. */ @@ -142,9 +151,18 @@ public class ASTUtils { return shouldVisitArgumentsForMethodCalls(call) || graphNode == null; } - public static boolean constructorHasExplicitConstructorInvocation(ConstructorDeclaration declaration) { - final NodeList statements = declaration.getBody().getStatements(); - return !statements.isEmpty() && statements.get(0) instanceof ExplicitConstructorInvocationStmt; + public static boolean shouldInsertExplicitConstructorInvocation(ConstructorDeclaration declaration) { + NodeList statements = declaration.getBody().getStatements(); + if (declaration.findAncestor(TypeDeclaration.class).orElseThrow().isEnumDeclaration()) + return false; + return statements.isEmpty() || !statements.get(0).isExplicitConstructorInvocationStmt(); + } + + public static boolean shouldInsertDynamicInitInEnum(ConstructorDeclaration declaration) { + NodeList statements = declaration.getBody().getStatements(); + return statements.isEmpty() || + !statements.get(0).isExplicitConstructorInvocationStmt() || + !statements.get(0).asExplicitConstructorInvocationStmt().isThis(); } /** @@ -197,14 +215,6 @@ public class ASTUtils { throw new IllegalArgumentException("This operation is only valid for reference type cast operations."); } - /** Given an AST node, visit the parent until finding a ClassOrInterfaceDeclaration */ - public static ClassOrInterfaceDeclaration getClassNode(Node n){ - Node upperNode = n; - while (!(upperNode instanceof ClassOrInterfaceDeclaration)) - upperNode = upperNode.getParentNode().orElseThrow(); - return (ClassOrInterfaceDeclaration) upperNode; - } - /** Generates the default initializer, given a field. In Java, reference types * default to null, booleans to false and all other primitives to 0. */ public static Expression initializerForField(FieldDeclaration field) { @@ -221,22 +231,19 @@ public class ASTUtils { throw new IllegalArgumentException("Invalid typing for a field"); } - /** Returns a List with FieldDeclarations and InitializerDeclarations static/dynamic items of the given class */ - public static List> getClassInit(ClassOrInterfaceDeclaration clazz, boolean isStatic) { - List> classInit = new LinkedList<>(); - for (BodyDeclaration member : clazz.getMembers()) { - if (member.isFieldDeclaration() && - member.asFieldDeclaration().isStatic() == isStatic) - classInit.add(member); - if (member.isInitializerDeclaration() && - member.asInitializerDeclaration().isStatic() == isStatic) - classInit.add(member); + /** Returns a list with field declaration and initializers, which initialize statically or dynamically the type. */ + public static List> getTypeInit(TypeDeclaration type, boolean isStatic) { + List> typeInit = new LinkedList<>(); + for (BodyDeclaration member : type.getMembers()) { + if (member.isFieldDeclaration() && member.asFieldDeclaration().isStatic() == isStatic) + typeInit.add(member); + if (member.isInitializerDeclaration() && member.asInitializerDeclaration().isStatic() == isStatic) + typeInit.add(member); } - return classInit; + return typeInit; } public static ResolvedType resolvedTypeOfCurrentClass(Node n) { - return ASTUtils.resolvedTypeDeclarationToResolvedType( - n.findAncestor(ClassOrInterfaceDeclaration.class).orElseThrow().resolve()); + return resolvedTypeDeclarationToResolvedType(n.findAncestor(TypeDeclaration.class).orElseThrow().resolve()); } } diff --git a/sdg-core/src/main/java/es/upv/mist/slicing/utils/Utils.java b/sdg-core/src/main/java/es/upv/mist/slicing/utils/Utils.java index fe6f6428916bc9b3d52abc3a0ae08a3176ed8b78..64681a8e50713ab6b79fe8ee424850829915a635 100644 --- a/sdg-core/src/main/java/es/upv/mist/slicing/utils/Utils.java +++ b/sdg-core/src/main/java/es/upv/mist/slicing/utils/Utils.java @@ -27,4 +27,24 @@ public class Utils { return element; throw new NoSuchElementException("Could not locate " + object + " in set."); } + + public static String arrayJoin(String[] array, String separator) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < array.length; i++) { + if (i > 0) builder.append(separator); + builder.append(array[i]); + } + return builder.toString(); + } + + public static boolean arrayPrefix(E[] array, E[] prefix) { + if (prefix.length == 0) + return true; + if (array.length < prefix.length) + return false; + for (int i = 0; i < prefix.length; i++) + if (!Objects.equals(array[i], prefix[i])) + return false; + return true; + } } diff --git a/sdg-core/src/main/java/module-info.java b/sdg-core/src/main/java/module-info.java index b00c1ea3fd05a1a5bef6bd635c0b6ecb0d72742f..6b2054a1f1df017a359f2ba42033e628a4469d97 100644 --- a/sdg-core/src/main/java/module-info.java +++ b/sdg-core/src/main/java/module-info.java @@ -1,7 +1,8 @@ module sdg.core { requires com.github.javaparser.core; - requires com.github.javaparser.symbolsolver.core; + requires com.github.javaparser.symbolsolver; requires org.jgrapht.core; + requires java.logging; exports es.upv.mist.slicing.slicing; exports es.upv.mist.slicing.graphs; diff --git a/sdg-core/src/test/res/regression/oo/EnumBasic.java b/sdg-core/src/test/res/regression/oo/EnumBasic.java new file mode 100644 index 0000000000000000000000000000000000000000..09d9a82e07afc9269b093180db9980bb2becaeeb --- /dev/null +++ b/sdg-core/src/test/res/regression/oo/EnumBasic.java @@ -0,0 +1,10 @@ +public class EnumBasic { + public static void main(String[] args) { + Enum e = Enum.A; + System.out.println(e); + } +} + +enum Enum { + A, B, C; +} \ No newline at end of file diff --git a/sdg-core/src/test/res/regression/oo/EnumBasic.java.sdg.criterion b/sdg-core/src/test/res/regression/oo/EnumBasic.java.sdg.criterion new file mode 100644 index 0000000000000000000000000000000000000000..d266d0eec122d9362afae1ccc6794e1002f30cbb --- /dev/null +++ b/sdg-core/src/test/res/regression/oo/EnumBasic.java.sdg.criterion @@ -0,0 +1 @@ +4 e \ No newline at end of file diff --git a/sdg-core/src/test/res/regression/oo/EnumBasic.java.sdg.sliced b/sdg-core/src/test/res/regression/oo/EnumBasic.java.sdg.sliced new file mode 100644 index 0000000000000000000000000000000000000000..e5f84f6c9c69e5365f386a36a8ded712086946b3 --- /dev/null +++ b/sdg-core/src/test/res/regression/oo/EnumBasic.java.sdg.sliced @@ -0,0 +1,12 @@ +public class EnumBasic { + + public static void main(String[] args) { + Enum e = Enum.A; + System.out.println(e); + } +} + +enum Enum { + + A +} diff --git a/sdg-core/src/test/res/regression/oo/EnumWithConstructor.java b/sdg-core/src/test/res/regression/oo/EnumWithConstructor.java new file mode 100644 index 0000000000000000000000000000000000000000..2aeb484ae9885967e4d135313d1b066b4149acec --- /dev/null +++ b/sdg-core/src/test/res/regression/oo/EnumWithConstructor.java @@ -0,0 +1,24 @@ +public class EnumWithConstructor { + public static void main(String[] args) { + String res = ""; + res += Dias.LUNES.getId(); + res += Dias.DOMINGO.getId(); + System.out.println(res); + } +} + +enum Dias { + LUNES, + MARTES, + MIERCOLES, + JUEVES, + VIERNES, + SABADO, + DOMINGO; + + private char id = name().charAt(0); + + public char getId() { + return id; + } +} \ No newline at end of file diff --git a/sdg-core/src/test/res/regression/oo/EnumWithConstructor.java.sdg.criterion b/sdg-core/src/test/res/regression/oo/EnumWithConstructor.java.sdg.criterion new file mode 100644 index 0000000000000000000000000000000000000000..890e5a26c4a014dcc8b3e7ca256238f0090c4da9 --- /dev/null +++ b/sdg-core/src/test/res/regression/oo/EnumWithConstructor.java.sdg.criterion @@ -0,0 +1 @@ +6 res \ No newline at end of file diff --git a/sdg-core/src/test/res/regression/oo/EnumWithConstructor.java.sdg.sliced b/sdg-core/src/test/res/regression/oo/EnumWithConstructor.java.sdg.sliced new file mode 100644 index 0000000000000000000000000000000000000000..420d0498ccc146d2aa74857bdd607b40a9cbf70b --- /dev/null +++ b/sdg-core/src/test/res/regression/oo/EnumWithConstructor.java.sdg.sliced @@ -0,0 +1,20 @@ +public class EnumWithConstructor { + + public static void main(String[] args) { + String res = ""; + res += Dias.LUNES.getId(); + res += Dias.DOMINGO.getId(); + System.out.println(res); + } +} + +enum Dias { + + LUNES, DOMINGO; + + private char id = name().charAt(0); + + public char getId() { + return id; + } +}