1 /*
2  * Copyright 2016 Federico Tomassetti
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.github.javaparser.symbolsolver.javaparsermodel;
18 
19 import com.github.javaparser.ast.CompilationUnit;
20 import com.github.javaparser.ast.DataKey;
21 import com.github.javaparser.ast.Node;
22 import com.github.javaparser.ast.NodeList;
23 import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
24 import com.github.javaparser.ast.body.EnumDeclaration;
25 import com.github.javaparser.ast.body.TypeDeclaration;
26 import com.github.javaparser.ast.body.VariableDeclarator;
27 import com.github.javaparser.ast.expr.*;
28 import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt;
29 import com.github.javaparser.ast.type.*;
30 import com.github.javaparser.resolution.MethodUsage;
31 import com.github.javaparser.resolution.UnsolvedSymbolException;
32 import com.github.javaparser.resolution.declarations.*;
33 import com.github.javaparser.resolution.types.*;
34 import com.github.javaparser.symbolsolver.core.resolution.Context;
35 import com.github.javaparser.symbolsolver.javaparsermodel.contexts.FieldAccessContext;
36 import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserAnonymousClassDeclaration;
37 import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserEnumDeclaration;
38 import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserTypeVariableDeclaration;
39 import com.github.javaparser.symbolsolver.model.resolution.SymbolReference;
40 import com.github.javaparser.symbolsolver.model.resolution.TypeSolver;
41 import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl;
42 import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionClassDeclaration;
43 import com.github.javaparser.symbolsolver.resolution.ConstructorResolutionLogic;
44 import com.github.javaparser.symbolsolver.resolution.SymbolSolver;
45 import com.github.javaparser.utils.Log;
46 
47 import java.util.*;
48 import java.util.stream.Collectors;
49 
50 import static com.github.javaparser.symbolsolver.javaparser.Navigator.requireParentNode;
51 
52 /**
53  * Class to be used by final users to solve symbols for JavaParser ASTs.
54  *
55  * @author Federico Tomassetti
56  */
57 public class JavaParserFacade {
58 
59     private static final DataKey<ResolvedType> TYPE_WITH_LAMBDAS_RESOLVED = new DataKey<ResolvedType>() {
60     };
61     private static final DataKey<ResolvedType> TYPE_WITHOUT_LAMBDAS_RESOLVED = new DataKey<ResolvedType>() {
62     };
63 
64     private static final Map<TypeSolver, JavaParserFacade> instances = new WeakHashMap<>();
65     private final TypeSolver typeSolver;
66     private final TypeExtractor typeExtractor;
67     private final SymbolSolver symbolSolver;
68 
JavaParserFacade(TypeSolver typeSolver)69     private JavaParserFacade(TypeSolver typeSolver) {
70         this.typeSolver = typeSolver.getRoot();
71         this.symbolSolver = new SymbolSolver(typeSolver);
72         this.typeExtractor = new TypeExtractor(typeSolver, this);
73     }
74 
getTypeSolver()75     public TypeSolver getTypeSolver() {
76         return typeSolver;
77     }
78 
getSymbolSolver()79     public SymbolSolver getSymbolSolver() {
80         return symbolSolver;
81     }
82 
get(TypeSolver typeSolver)83     public static JavaParserFacade get(TypeSolver typeSolver) {
84         return instances.computeIfAbsent(typeSolver, JavaParserFacade::new);
85     }
86 
87     /**
88      * This method is used to clear internal caches for the sake of releasing memory.
89      */
clearInstances()90     public static void clearInstances() {
91         instances.clear();
92     }
93 
solveGenericTypes(ResolvedType type, Context context)94     protected static ResolvedType solveGenericTypes(ResolvedType type, Context context) {
95         if (type.isTypeVariable()) {
96             return context.solveGenericType(type.describe()).orElse(type);
97         }
98         if (type.isWildcard()) {
99             if (type.asWildcard().isExtends() || type.asWildcard().isSuper()) {
100                 ResolvedWildcard wildcardUsage = type.asWildcard();
101                 ResolvedType boundResolved = solveGenericTypes(wildcardUsage.getBoundedType(), context);
102                 if (wildcardUsage.isExtends()) {
103                     return ResolvedWildcard.extendsBound(boundResolved);
104                 } else {
105                     return ResolvedWildcard.superBound(boundResolved);
106                 }
107             }
108         }
109         return type;
110     }
111 
solve(NameExpr nameExpr)112     public SymbolReference<? extends ResolvedValueDeclaration> solve(NameExpr nameExpr) {
113         return symbolSolver.solveSymbol(nameExpr.getName().getId(), nameExpr);
114     }
115 
solve(SimpleName nameExpr)116     public SymbolReference<? extends ResolvedValueDeclaration> solve(SimpleName nameExpr) {
117         return symbolSolver.solveSymbol(nameExpr.getId(), nameExpr);
118     }
119 
solve(Expression expr)120     public SymbolReference<? extends ResolvedValueDeclaration> solve(Expression expr) {
121         return expr.toNameExpr().map(this::solve).orElseThrow(() -> new IllegalArgumentException(expr.getClass().getCanonicalName()));
122     }
123 
solve(MethodCallExpr methodCallExpr)124     public SymbolReference<ResolvedMethodDeclaration> solve(MethodCallExpr methodCallExpr) {
125         return solve(methodCallExpr, true);
126     }
127 
solve(ObjectCreationExpr objectCreationExpr)128     public SymbolReference<ResolvedConstructorDeclaration> solve(ObjectCreationExpr objectCreationExpr) {
129         return solve(objectCreationExpr, true);
130     }
131 
solve(ExplicitConstructorInvocationStmt explicitConstructorInvocationStmt)132     public SymbolReference<ResolvedConstructorDeclaration> solve(ExplicitConstructorInvocationStmt explicitConstructorInvocationStmt) {
133         return solve(explicitConstructorInvocationStmt, true);
134     }
135 
solve(ExplicitConstructorInvocationStmt explicitConstructorInvocationStmt, boolean solveLambdas)136     public SymbolReference<ResolvedConstructorDeclaration> solve(ExplicitConstructorInvocationStmt explicitConstructorInvocationStmt, boolean solveLambdas) {
137         List<ResolvedType> argumentTypes = new LinkedList<>();
138         List<LambdaArgumentTypePlaceholder> placeholders = new LinkedList<>();
139 
140         solveArguments(explicitConstructorInvocationStmt, explicitConstructorInvocationStmt.getArguments(), solveLambdas, argumentTypes, placeholders);
141 
142         Optional<ClassOrInterfaceDeclaration> optAncestor = explicitConstructorInvocationStmt.findAncestor(ClassOrInterfaceDeclaration.class);
143         if (!optAncestor.isPresent()) {
144             return SymbolReference.unsolved(ResolvedConstructorDeclaration.class);
145         }
146         ClassOrInterfaceDeclaration classNode = optAncestor.get();
147         ResolvedTypeDeclaration typeDecl = null;
148         if (!explicitConstructorInvocationStmt.isThis()) {
149             ResolvedType classDecl = JavaParserFacade.get(typeSolver).convert(classNode.getExtendedTypes(0), classNode);
150             if (classDecl.isReferenceType()) {
151                 typeDecl = classDecl.asReferenceType().getTypeDeclaration();
152             }
153         } else {
154             SymbolReference<ResolvedTypeDeclaration> sr = JavaParserFactory.getContext(classNode, typeSolver).solveType(classNode.getNameAsString());
155             if (sr.isSolved()) {
156                 typeDecl = sr.getCorrespondingDeclaration();
157             }
158         }
159         if (typeDecl == null) {
160             return SymbolReference.unsolved(ResolvedConstructorDeclaration.class);
161         }
162         SymbolReference<ResolvedConstructorDeclaration> res = ConstructorResolutionLogic.findMostApplicable(((ResolvedClassDeclaration) typeDecl).getConstructors(), argumentTypes, typeSolver);
163         for (LambdaArgumentTypePlaceholder placeholder : placeholders) {
164             placeholder.setMethod(res);
165         }
166         return res;
167     }
168 
solve(ThisExpr node)169     public SymbolReference<ResolvedTypeDeclaration> solve(ThisExpr node) {
170         // If 'this' is prefixed by a class eg. MyClass.this
171         if (node.getTypeName().isPresent()) {
172             // Get the class name
173             String className = node.getTypeName().get().asString();
174             // Attempt to resolve using a typeSolver
175             SymbolReference<ResolvedReferenceTypeDeclaration> clazz = typeSolver.tryToSolveType(className);
176             if (clazz.isSolved()) {
177                 return SymbolReference.solved(clazz.getCorrespondingDeclaration());
178             }
179             // Attempt to resolve locally in Compilation unit
180             Optional<CompilationUnit> cu = node.findAncestor(CompilationUnit.class);
181             if (cu.isPresent()) {
182                 Optional<ClassOrInterfaceDeclaration> classByName = cu.get().getClassByName(className);
183                 if (classByName.isPresent()) {
184                     return SymbolReference.solved(getTypeDeclaration(classByName.get()));
185                 }
186             }
187         }
188         return SymbolReference.solved(getTypeDeclaration(findContainingTypeDeclOrObjectCreationExpr(node)));
189     }
190 
191     /**
192      * Given a constructor call find out to which constructor declaration it corresponds.
193      */
solve(ObjectCreationExpr objectCreationExpr, boolean solveLambdas)194     public SymbolReference<ResolvedConstructorDeclaration> solve(ObjectCreationExpr objectCreationExpr, boolean solveLambdas) {
195         List<ResolvedType> argumentTypes = new LinkedList<>();
196         List<LambdaArgumentTypePlaceholder> placeholders = new LinkedList<>();
197 
198         solveArguments(objectCreationExpr, objectCreationExpr.getArguments(), solveLambdas, argumentTypes, placeholders);
199 
200         ResolvedType classDecl = JavaParserFacade.get(typeSolver).convert(objectCreationExpr.getType(), objectCreationExpr);
201         if (!classDecl.isReferenceType()) {
202             return SymbolReference.unsolved(ResolvedConstructorDeclaration.class);
203         }
204         SymbolReference<ResolvedConstructorDeclaration> res = ConstructorResolutionLogic.findMostApplicable(classDecl.asReferenceType().getTypeDeclaration().getConstructors(), argumentTypes, typeSolver);
205         for (LambdaArgumentTypePlaceholder placeholder : placeholders) {
206             placeholder.setMethod(res);
207         }
208         return res;
209     }
210 
solveArguments(Node node, NodeList<Expression> args, boolean solveLambdas, List<ResolvedType> argumentTypes, List<LambdaArgumentTypePlaceholder> placeholders)211     private void solveArguments(Node node, NodeList<Expression> args, boolean solveLambdas, List<ResolvedType> argumentTypes,
212                                 List<LambdaArgumentTypePlaceholder> placeholders) {
213         int i = 0;
214         for (Expression parameterValue : args) {
215             if (parameterValue instanceof LambdaExpr || parameterValue instanceof MethodReferenceExpr) {
216                 LambdaArgumentTypePlaceholder placeholder = new LambdaArgumentTypePlaceholder(i);
217                 argumentTypes.add(placeholder);
218                 placeholders.add(placeholder);
219             } else {
220                 try {
221                     argumentTypes.add(JavaParserFacade.get(typeSolver).getType(parameterValue, solveLambdas));
222                 } catch (com.github.javaparser.resolution.UnsolvedSymbolException e) {
223                     throw e;
224                 } catch (Exception e) {
225                     throw new RuntimeException(String.format("Unable to calculate the type of a parameter of a method call. Method call: %s, Parameter: %s",
226                             node, parameterValue), e);
227                 }
228             }
229             i++;
230         }
231     }
232 
233     /**
234      * Given a method call find out to which method declaration it corresponds.
235      */
solve(MethodCallExpr methodCallExpr, boolean solveLambdas)236     public SymbolReference<ResolvedMethodDeclaration> solve(MethodCallExpr methodCallExpr, boolean solveLambdas) {
237         List<ResolvedType> argumentTypes = new LinkedList<>();
238         List<LambdaArgumentTypePlaceholder> placeholders = new LinkedList<>();
239 
240         solveArguments(methodCallExpr, methodCallExpr.getArguments(), solveLambdas, argumentTypes, placeholders);
241 
242         SymbolReference<ResolvedMethodDeclaration> res = JavaParserFactory.getContext(methodCallExpr, typeSolver).solveMethod(methodCallExpr.getName().getId(), argumentTypes, false);
243         for (LambdaArgumentTypePlaceholder placeholder : placeholders) {
244             placeholder.setMethod(res);
245         }
246         return res;
247     }
248 
solve(AnnotationExpr annotationExpr)249     public SymbolReference<ResolvedAnnotationDeclaration> solve(AnnotationExpr annotationExpr) {
250         Context context = JavaParserFactory.getContext(annotationExpr, typeSolver);
251         SymbolReference<ResolvedTypeDeclaration> typeDeclarationSymbolReference = context.solveType(annotationExpr.getNameAsString());
252         if (typeDeclarationSymbolReference.isSolved()) {
253             ResolvedAnnotationDeclaration annotationDeclaration = (ResolvedAnnotationDeclaration) typeDeclarationSymbolReference.getCorrespondingDeclaration();
254             return SymbolReference.solved(annotationDeclaration);
255         } else {
256             return SymbolReference.unsolved(ResolvedAnnotationDeclaration.class);
257         }
258     }
259 
solve(FieldAccessExpr fieldAccessExpr)260     public SymbolReference<ResolvedValueDeclaration> solve(FieldAccessExpr fieldAccessExpr) {
261         return ((FieldAccessContext) JavaParserFactory.getContext(fieldAccessExpr, typeSolver)).solveField(fieldAccessExpr.getName().getId());
262     }
263 
264     /**
265      * Get the type associated with the node.
266      * <p>
267      * This method was originally intended to get the type of a value: any value has a type.
268      * <p>
269      * For example:
270      * <pre>
271      * int foo(int a) {
272      *     return a; // when getType is invoked on "a" it returns the type "int"
273      * }
274      * </pre>
275      * <p>
276      * Now, users started using also of names of types itself, which do not have a type.
277      * <p>
278      * For example:
279      * <pre>
280      * class A {
281      *     int foo(int a) {
282      *         return A.someStaticField; // when getType is invoked on "A", which represents a class, it returns
283      *             // the type "A" itself while it used to throw UnsolvedSymbolException
284      * }
285      * </pre>
286      * <p>
287      * To accomodate this usage and avoid confusion this method return
288      * the type itself when used on the name of type.
289      */
getType(Node node)290     public ResolvedType getType(Node node) {
291         try {
292             return getType(node, true);
293         } catch (UnsolvedSymbolException e) {
294             if (node instanceof NameExpr) {
295                 NameExpr nameExpr = (NameExpr) node;
296                 SymbolReference<ResolvedTypeDeclaration> typeDeclaration = JavaParserFactory.getContext(node, typeSolver)
297                         .solveType(nameExpr.getNameAsString());
298                 if (typeDeclaration.isSolved() && typeDeclaration.getCorrespondingDeclaration() instanceof ResolvedReferenceTypeDeclaration) {
299                     ResolvedReferenceTypeDeclaration resolvedReferenceTypeDeclaration = (ResolvedReferenceTypeDeclaration) typeDeclaration.getCorrespondingDeclaration();
300                     return ReferenceTypeImpl.undeterminedParameters(resolvedReferenceTypeDeclaration, typeSolver);
301                 }
302             }
303             throw e;
304         }
305     }
306 
getType(Node node, boolean solveLambdas)307     public ResolvedType getType(Node node, boolean solveLambdas) {
308         if (solveLambdas) {
309             if (!node.containsData(TYPE_WITH_LAMBDAS_RESOLVED)) {
310                 ResolvedType res = getTypeConcrete(node, solveLambdas);
311 
312                 node.setData(TYPE_WITH_LAMBDAS_RESOLVED, res);
313 
314                 boolean secondPassNecessary = false;
315                 if (node instanceof MethodCallExpr) {
316                     MethodCallExpr methodCallExpr = (MethodCallExpr) node;
317                     for (Node arg : methodCallExpr.getArguments()) {
318                         if (!arg.containsData(TYPE_WITH_LAMBDAS_RESOLVED)) {
319                             getType(arg, true);
320                             secondPassNecessary = true;
321                         }
322                     }
323                 }
324                 if (secondPassNecessary) {
325                     node.removeData(TYPE_WITH_LAMBDAS_RESOLVED);
326                     ResolvedType type = getType(node, true);
327                     node.setData(TYPE_WITH_LAMBDAS_RESOLVED, type);
328 
329                 }
330                 Log.trace("getType on %s  -> %s" ,()-> node, ()-> res);
331             }
332             return node.getData(TYPE_WITH_LAMBDAS_RESOLVED);
333         } else {
334             Optional<ResolvedType> res = find(TYPE_WITH_LAMBDAS_RESOLVED, node);
335             if (res.isPresent()) {
336                 return res.get();
337             }
338             res = find(TYPE_WITHOUT_LAMBDAS_RESOLVED, node);
339             if (!res.isPresent()) {
340                 ResolvedType resType = getTypeConcrete(node, solveLambdas);
341                 node.setData(TYPE_WITHOUT_LAMBDAS_RESOLVED, resType);
342                 Optional<ResolvedType> finalRes = res;
343                 Log.trace("getType on %s (no solveLambdas) -> %s", ()-> node, ()-> finalRes);
344                 return resType;
345             }
346             return res.get();
347         }
348     }
349 
find(DataKey<ResolvedType> dataKey, Node node)350     private Optional<ResolvedType> find(DataKey<ResolvedType> dataKey, Node node) {
351         if (node.containsData(dataKey)) {
352             return Optional.of(node.getData(dataKey));
353         }
354         return Optional.empty();
355     }
356 
toMethodUsage(MethodReferenceExpr methodReferenceExpr)357     protected MethodUsage toMethodUsage(MethodReferenceExpr methodReferenceExpr) {
358         if (!(methodReferenceExpr.getScope() instanceof TypeExpr)) {
359             throw new UnsupportedOperationException();
360         }
361         TypeExpr typeExpr = (TypeExpr) methodReferenceExpr.getScope();
362         if (!(typeExpr.getType() instanceof com.github.javaparser.ast.type.ClassOrInterfaceType)) {
363             throw new UnsupportedOperationException(typeExpr.getType().getClass().getCanonicalName());
364         }
365         ClassOrInterfaceType classOrInterfaceType = (ClassOrInterfaceType) typeExpr.getType();
366         SymbolReference<ResolvedTypeDeclaration> typeDeclarationSymbolReference = JavaParserFactory.getContext(classOrInterfaceType, typeSolver).solveType(classOrInterfaceType.getName().getId());
367         if (!typeDeclarationSymbolReference.isSolved()) {
368             throw new UnsupportedOperationException();
369         }
370         List<MethodUsage> methodUsages = ((ResolvedReferenceTypeDeclaration) typeDeclarationSymbolReference.getCorrespondingDeclaration()).getAllMethods().stream().filter(it -> it.getName().equals(methodReferenceExpr.getIdentifier())).collect(Collectors.toList());
371         switch (methodUsages.size()) {
372             case 0:
373                 throw new UnsupportedOperationException();
374             case 1:
375                 return methodUsages.get(0);
376             default:
377                 throw new UnsupportedOperationException();
378         }
379     }
380 
getBinaryTypeConcrete(Node left, Node right, boolean solveLambdas, BinaryExpr.Operator operator)381     protected ResolvedType getBinaryTypeConcrete(Node left, Node right, boolean solveLambdas, BinaryExpr.Operator operator) {
382         ResolvedType leftType = getTypeConcrete(left, solveLambdas);
383         ResolvedType rightType = getTypeConcrete(right, solveLambdas);
384 
385         // JLS 15.18.1. String Concatenation Operator +
386         // If only one operand expression is of type String, then string conversion (§5.1.11) is performed on the other
387         // operand to produce a string at run time.
388         //
389         // The result of string concatenation is a reference to a String object that is the concatenation of the two
390         // operand strings. The characters of the left-hand operand precede the characters of the right-hand operand in
391         // the newly created string.
392 
393         if (operator == BinaryExpr.Operator.PLUS) {
394             boolean isLeftString = leftType.isReferenceType() && leftType.asReferenceType()
395                     .getQualifiedName().equals(String.class.getCanonicalName());
396             boolean isRightString = rightType.isReferenceType() && rightType.asReferenceType()
397                     .getQualifiedName().equals(String.class.getCanonicalName());
398             if (isLeftString || isRightString) {
399                 return isLeftString ? leftType : rightType;
400             }
401         }
402 
403         // JLS 5.6.2. Binary Numeric Promotion
404         //
405         // Widening primitive conversion (§5.1.2) is applied to convert either or both operands as specified by the
406         // following rules:
407         //
408         // * If either operand is of type double, the other is converted to double.
409         // * Otherwise, if either operand is of type float, the other is converted to float.
410         // * Otherwise, if either operand is of type long, the other is converted to long.
411         // * Otherwise, both operands are converted to type int.
412 
413         boolean isLeftNumeric = leftType.isPrimitive() && leftType.asPrimitive().isNumeric();
414         boolean isRightNumeric = rightType.isPrimitive() && rightType.asPrimitive().isNumeric();
415 
416         if (isLeftNumeric && isRightNumeric) {
417             if (leftType.asPrimitive().equals(ResolvedPrimitiveType.DOUBLE)
418                     || rightType.asPrimitive().equals(ResolvedPrimitiveType.DOUBLE)) {
419                 return ResolvedPrimitiveType.DOUBLE;
420             }
421 
422             if (leftType.asPrimitive().equals(ResolvedPrimitiveType.FLOAT)
423                     || rightType.asPrimitive().equals(ResolvedPrimitiveType.FLOAT)) {
424                 return ResolvedPrimitiveType.FLOAT;
425             }
426 
427             if (leftType.asPrimitive().equals(ResolvedPrimitiveType.LONG)
428                     || rightType.asPrimitive().equals(ResolvedPrimitiveType.LONG)) {
429                 return ResolvedPrimitiveType.LONG;
430             }
431 
432             return ResolvedPrimitiveType.INT;
433         }
434 
435         if (rightType.isAssignableBy(leftType)) {
436             return rightType;
437         }
438         return leftType;
439     }
440 
441 
442     /**
443      * Should return more like a TypeApplication: a TypeDeclaration and possible typeParametersValues or array
444      * modifiers.
445      */
getTypeConcrete(Node node, boolean solveLambdas)446     private ResolvedType getTypeConcrete(Node node, boolean solveLambdas) {
447         if (node == null) throw new IllegalArgumentException();
448         return node.accept(typeExtractor, solveLambdas);
449     }
450 
findContainingTypeDecl(Node node)451     protected com.github.javaparser.ast.body.TypeDeclaration<?> findContainingTypeDecl(Node node) {
452         if (node instanceof ClassOrInterfaceDeclaration) {
453             return (ClassOrInterfaceDeclaration) node;
454         }
455         if (node instanceof EnumDeclaration) {
456             return (EnumDeclaration) node;
457         }
458         return findContainingTypeDecl(requireParentNode(node));
459 
460     }
461 
findContainingTypeDeclOrObjectCreationExpr(Node node)462     protected Node findContainingTypeDeclOrObjectCreationExpr(Node node) {
463         if (node instanceof ClassOrInterfaceDeclaration) {
464             return node;
465         }
466         if (node instanceof EnumDeclaration) {
467             return node;
468         }
469         Node parent = requireParentNode(node);
470         if (parent instanceof ObjectCreationExpr && !((ObjectCreationExpr) parent).getArguments().contains(node)) {
471             return parent;
472         }
473         return findContainingTypeDeclOrObjectCreationExpr(parent);
474     }
475 
convertToUsageVariableType(VariableDeclarator var)476     public ResolvedType convertToUsageVariableType(VariableDeclarator var) {
477         return get(typeSolver).convertToUsage(var.getType(), var);
478     }
479 
convertToUsage(com.github.javaparser.ast.type.Type type, Node context)480     public ResolvedType convertToUsage(com.github.javaparser.ast.type.Type type, Node context) {
481         if (type.isUnknownType()) {
482             throw new IllegalArgumentException("Inferred lambda parameter type");
483         }
484         return convertToUsage(type, JavaParserFactory.getContext(context, typeSolver));
485     }
486 
convertToUsage(com.github.javaparser.ast.type.Type type)487     public ResolvedType convertToUsage(com.github.javaparser.ast.type.Type type) {
488         return convertToUsage(type, type);
489     }
490 
491     // This is an hack around an issue in JavaParser
qName(ClassOrInterfaceType classOrInterfaceType)492     private String qName(ClassOrInterfaceType classOrInterfaceType) {
493         String name = classOrInterfaceType.getName().getId();
494         if (classOrInterfaceType.getScope().isPresent()) {
495             return qName(classOrInterfaceType.getScope().get()) + "." + name;
496         }
497         return name;
498     }
499 
convertToUsage(com.github.javaparser.ast.type.Type type, Context context)500     protected ResolvedType convertToUsage(com.github.javaparser.ast.type.Type type, Context context) {
501         if (context == null) {
502             throw new NullPointerException("Context should not be null");
503         }
504         if (type instanceof ClassOrInterfaceType) {
505             ClassOrInterfaceType classOrInterfaceType = (ClassOrInterfaceType) type;
506             String name = qName(classOrInterfaceType);
507             SymbolReference<ResolvedTypeDeclaration> ref = context.solveType(name);
508             if (!ref.isSolved()) {
509                 throw new UnsolvedSymbolException(name);
510             }
511             ResolvedTypeDeclaration typeDeclaration = ref.getCorrespondingDeclaration();
512             List<ResolvedType> typeParameters = Collections.emptyList();
513             if (classOrInterfaceType.getTypeArguments().isPresent()) {
514                 typeParameters = classOrInterfaceType.getTypeArguments().get().stream().map((pt) -> convertToUsage(pt, context)).collect(Collectors.toList());
515             }
516             if (typeDeclaration.isTypeParameter()) {
517                 if (typeDeclaration instanceof ResolvedTypeParameterDeclaration) {
518                     return new ResolvedTypeVariable((ResolvedTypeParameterDeclaration) typeDeclaration);
519                 } else {
520                     JavaParserTypeVariableDeclaration javaParserTypeVariableDeclaration = (JavaParserTypeVariableDeclaration) typeDeclaration;
521                     return new ResolvedTypeVariable(javaParserTypeVariableDeclaration.asTypeParameter());
522                 }
523             } else {
524                 return new ReferenceTypeImpl((ResolvedReferenceTypeDeclaration) typeDeclaration, typeParameters, typeSolver);
525             }
526         } else if (type instanceof com.github.javaparser.ast.type.PrimitiveType) {
527             return ResolvedPrimitiveType.byName(((com.github.javaparser.ast.type.PrimitiveType) type).getType().name());
528         } else if (type instanceof WildcardType) {
529             WildcardType wildcardType = (WildcardType) type;
530             if (wildcardType.getExtendedType().isPresent() && !wildcardType.getSuperType().isPresent()) {
531                 return ResolvedWildcard.extendsBound(convertToUsage(wildcardType.getExtendedType().get(), context)); // removed (ReferenceTypeImpl)
532             } else if (!wildcardType.getExtendedType().isPresent() && wildcardType.getSuperType().isPresent()) {
533                 return ResolvedWildcard.superBound(convertToUsage(wildcardType.getSuperType().get(), context)); // removed (ReferenceTypeImpl)
534             } else if (!wildcardType.getExtendedType().isPresent() && !wildcardType.getSuperType().isPresent()) {
535                 return ResolvedWildcard.UNBOUNDED;
536             } else {
537                 throw new UnsupportedOperationException(wildcardType.toString());
538             }
539         } else if (type instanceof com.github.javaparser.ast.type.VoidType) {
540             return ResolvedVoidType.INSTANCE;
541         } else if (type instanceof com.github.javaparser.ast.type.ArrayType) {
542             com.github.javaparser.ast.type.ArrayType jpArrayType = (com.github.javaparser.ast.type.ArrayType) type;
543             return new ResolvedArrayType(convertToUsage(jpArrayType.getComponentType(), context));
544         } else if (type instanceof UnionType) {
545             UnionType unionType = (UnionType) type;
546             return new ResolvedUnionType(unionType.getElements().stream().map(el -> convertToUsage(el, context)).collect(Collectors.toList()));
547         } else if (type instanceof VarType) {
548             Node parent = type.getParentNode().get();
549             if (!(parent instanceof VariableDeclarator)) {
550                 throw new IllegalStateException("Trying to resolve a `var` which is not in a variable declaration.");
551             }
552             final VariableDeclarator variableDeclarator = (VariableDeclarator) parent;
553             return variableDeclarator.getInitializer()
554                     .map(Expression::calculateResolvedType)
555                     .orElseThrow(() -> new IllegalStateException("Cannot resolve `var` which has no initializer."));
556         } else {
557             throw new UnsupportedOperationException(type.getClass().getCanonicalName());
558         }
559     }
560 
561 
convert(Type type, Node node)562     public ResolvedType convert(Type type, Node node) {
563         return convert(type, JavaParserFactory.getContext(node, typeSolver));
564     }
565 
convert(com.github.javaparser.ast.type.Type type, Context context)566     public ResolvedType convert(com.github.javaparser.ast.type.Type type, Context context) {
567         return convertToUsage(type, context);
568     }
569 
solveMethodAsUsage(MethodCallExpr call)570     public MethodUsage solveMethodAsUsage(MethodCallExpr call) {
571         List<ResolvedType> params = new ArrayList<>();
572         if (call.getArguments() != null) {
573             for (Expression param : call.getArguments()) {
574                 //getTypeConcrete(Node node, boolean solveLambdas)
575                 try {
576                     params.add(getType(param, false));
577                 } catch (Exception e) {
578                     throw new RuntimeException(String.format("Error calculating the type of parameter %s of method call %s", param, call), e);
579                 }
580                 //params.add(getTypeConcrete(param, false));
581             }
582         }
583         Context context = JavaParserFactory.getContext(call, typeSolver);
584         Optional<MethodUsage> methodUsage = context.solveMethodAsUsage(call.getName().getId(), params);
585         if (!methodUsage.isPresent()) {
586             throw new RuntimeException("Method '" + call.getName() + "' cannot be resolved in context "
587                     + call + " (line: " + call.getRange().map(r -> "" + r.begin.line).orElse("??") + ") " + context + ". Parameter types: " + params);
588         }
589         return methodUsage.get();
590     }
591 
getTypeDeclaration(Node node)592     public ResolvedReferenceTypeDeclaration getTypeDeclaration(Node node) {
593         if (node instanceof TypeDeclaration) {
594             return getTypeDeclaration((TypeDeclaration) node);
595         } else if (node instanceof ObjectCreationExpr) {
596             return new JavaParserAnonymousClassDeclaration((ObjectCreationExpr) node, typeSolver);
597         } else {
598             throw new IllegalArgumentException();
599         }
600     }
601 
getTypeDeclaration(ClassOrInterfaceDeclaration classOrInterfaceDeclaration)602     public ResolvedReferenceTypeDeclaration getTypeDeclaration(ClassOrInterfaceDeclaration classOrInterfaceDeclaration) {
603         return JavaParserFactory.toTypeDeclaration(classOrInterfaceDeclaration, typeSolver);
604     }
605 
606     /**
607      * "this" inserted in the given point, which type would have?
608      */
getTypeOfThisIn(Node node)609     public ResolvedType getTypeOfThisIn(Node node) {
610         // TODO consider static methods
611         if (node instanceof ClassOrInterfaceDeclaration) {
612             return new ReferenceTypeImpl(getTypeDeclaration((ClassOrInterfaceDeclaration) node), typeSolver);
613         } else if (node instanceof EnumDeclaration) {
614             JavaParserEnumDeclaration enumDeclaration = new JavaParserEnumDeclaration((EnumDeclaration) node, typeSolver);
615             return new ReferenceTypeImpl(enumDeclaration, typeSolver);
616         } else if (node instanceof ObjectCreationExpr && ((ObjectCreationExpr) node).getAnonymousClassBody().isPresent()) {
617             JavaParserAnonymousClassDeclaration anonymousDeclaration = new JavaParserAnonymousClassDeclaration((ObjectCreationExpr) node, typeSolver);
618             return new ReferenceTypeImpl(anonymousDeclaration, typeSolver);
619         }
620         return getTypeOfThisIn(requireParentNode(node));
621     }
622 
getTypeDeclaration(com.github.javaparser.ast.body.TypeDeclaration<?> typeDeclaration)623     public ResolvedReferenceTypeDeclaration getTypeDeclaration(com.github.javaparser.ast.body.TypeDeclaration<?> typeDeclaration) {
624         return JavaParserFactory.toTypeDeclaration(typeDeclaration, typeSolver);
625     }
626 
classToResolvedType(Class<?> clazz)627     public ResolvedType classToResolvedType(Class<?> clazz) {
628         if (clazz.isPrimitive()) {
629             return ResolvedPrimitiveType.byName(clazz.getName());
630         }
631         return new ReferenceTypeImpl(new ReflectionClassDeclaration(clazz, typeSolver), typeSolver);
632     }
633 }
634