aboutsummaryrefslogtreecommitdiff
path: root/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/TypeExtractor.java
diff options
context:
space:
mode:
Diffstat (limited to 'javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/TypeExtractor.java')
-rw-r--r--javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/TypeExtractor.java517
1 files changed, 517 insertions, 0 deletions
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 000000000..44c8d2715
--- /dev/null
+++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/TypeExtractor.java
@@ -0,0 +1,517 @@
+package com.github.javaparser.symbolsolver.javaparsermodel;
+
+import com.github.javaparser.ast.CompilationUnit;
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
+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.UnknownType;
+import com.github.javaparser.resolution.MethodUsage;
+import com.github.javaparser.resolution.UnsolvedSymbolException;
+import com.github.javaparser.resolution.declarations.ResolvedClassDeclaration;
+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.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.*;
+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.typesolvers.ReflectionTypeSolver;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static com.github.javaparser.symbolsolver.javaparser.Navigator.requireParentNode;
+import static com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade.solveGenericTypes;
+
+public class TypeExtractor extends DefaultVisitorAdapter {
+
+ private static Logger logger = Logger.getLogger(TypeExtractor.class.getCanonicalName());
+
+ static {
+ logger.setLevel(Level.INFO);
+ ConsoleHandler consoleHandler = new ConsoleHandler();
+ consoleHandler.setLevel(Level.INFO);
+ logger.addHandler(consoleHandler);
+ }
+
+ private TypeSolver typeSolver;
+ private JavaParserFacade facade;
+
+ public TypeExtractor(TypeSolver typeSolver, JavaParserFacade facade) {
+ this.typeSolver = typeSolver;
+ this.facade = facade;
+ }
+
+ @Override
+ public ResolvedType visit(VariableDeclarator node, Boolean solveLambdas) {
+ if (requireParentNode(node) instanceof FieldDeclaration) {
+ return facade.convertToUsageVariableType(node);
+ } else if (requireParentNode(node) instanceof VariableDeclarationExpr) {
+ return facade.convertToUsageVariableType(node);
+ }
+ throw new UnsupportedOperationException(requireParentNode(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:
+ return facade.getBinaryTypeConcrete(node.getLeft(), node.getRight(), solveLambdas);
+ case LESS_EQUALS:
+ case LESS:
+ case GREATER:
+ case GREATER_EQUALS:
+ case EQUALS:
+ case NOT_EQUALS:
+ case OR:
+ case AND:
+ return ResolvedPrimitiveType.BOOLEAN;
+ case BINARY_AND:
+ case BINARY_OR:
+ case SIGNED_RIGHT_SHIFT:
+ case UNSIGNED_RIGHT_SHIFT:
+ case LEFT_SHIFT:
+ case REMAINDER:
+ case XOR:
+ return node.getLeft().accept(this, solveLambdas);
+ 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.
+ com.github.javaparser.ast.type.Type astType = node.getType();
+ ResolvedType jssType = facade.convertToUsage(astType, node.getType());
+ return new ReferenceTypeImpl(new ReflectionClassDeclaration(Class.class, typeSolver), ImmutableList.of(jssType), typeSolver);
+ }
+
+ @Override
+ public ResolvedType visit(ConditionalExpr node, Boolean solveLambdas) {
+ return node.getThenExpr().accept(this, solveLambdas);
+ }
+
+ @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.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<ResolvedTypeDeclaration> typeAccessedStatically = JavaParserFactory.getContext(node, typeSolver).solveType(staticValue.toString(), typeSolver);
+ 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<ResolvedTypeDeclaration> 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<ResolvedReferenceTypeDeclaration> sr = typeSolver.tryToSolveType(node.getScope().toString());
+ if (sr.isSolved()) {
+ return solveDotExpressionType(sr.getCorrespondingDeclaration(), node);
+ }
+ }
+ Optional<Value> value = Optional.empty();
+ try {
+ value = new SymbolSolver(typeSolver).solveSymbolAsValue(node.getName().getId(), node);
+ } catch (com.github.javaparser.resolution.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<ResolvedReferenceTypeDeclaration> sref = typeSolver.tryToSolveType(node.toString());
+ if (sref.isSolved()) {
+ return new ReferenceTypeImpl(sref.getCorrespondingDeclaration(), typeSolver);
+ }
+ }
+ if (value.isPresent()) {
+ return value.get().getType();
+ }
+ throw new com.github.javaparser.resolution.UnsolvedSymbolException(node.getName().getId());
+ }
+
+ @Override
+ public ResolvedType visit(InstanceOfExpr node, Boolean solveLambdas) {
+ return ResolvedPrimitiveType.BOOLEAN;
+ }
+
+ @Override
+ public ResolvedType visit(StringLiteralExpr node, Boolean solveLambdas) {
+ return new ReferenceTypeImpl(new ReflectionTypeSolver().solveType(String.class.getCanonicalName()), typeSolver);
+ }
+
+ @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) {
+ logger.finest("getType on method call " + node);
+ // first solve the method
+ MethodUsage ref = facade.solveMethodAsUsage(node);
+ logger.finest("getType on method call " + node + " resolved to " + ref);
+ logger.finest("getType on method call " + node + " return type is " + ref.returnType());
+ return ref.returnType();
+ // the type is the return type of the method
+ }
+
+ @Override
+ public ResolvedType visit(NameExpr node, Boolean solveLambdas) {
+ logger.finest("getType on name expr " + node);
+ Optional<Value> value = new SymbolSolver(typeSolver).solveSymbolAsValue(node.getName().getId(), node);
+ if (!value.isPresent()) {
+ throw new com.github.javaparser.resolution.UnsolvedSymbolException("Solving " + node, node.getName().getId());
+ } else {
+ return value.get().getType();
+ }
+ }
+
+ @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.getClassExpr().isPresent()) {
+ // Get the class name
+ String className = node.getClassExpr().get().toString();
+ // Attempt to resolve using a typeSolver
+ SymbolReference<ResolvedReferenceTypeDeclaration> clazz = typeSolver.tryToSolveType(className);
+ if (clazz.isSolved()) {
+ return new ReferenceTypeImpl(clazz.getCorrespondingDeclaration(), typeSolver);
+ }
+ // Attempt to resolve locally in Compilation unit
+ Optional<CompilationUnit> cu = node.getAncestorOfType(CompilationUnit.class);
+ if (cu.isPresent()) {
+ Optional<ClassOrInterfaceDeclaration> classByName = cu.get().getClassByName(className);
+ if (classByName.isPresent()) {
+ return new ReferenceTypeImpl(facade.getTypeDeclaration(classByName.get()), typeSolver);
+ }
+ }
+
+ }
+ return new ReferenceTypeImpl(facade.getTypeDeclaration(facade.findContainingTypeDeclOrObjectCreationExpr(node)), typeSolver);
+ }
+
+ @Override
+ public ResolvedType visit(SuperExpr node, Boolean solveLambdas) {
+ ResolvedTypeDeclaration typeOfNode = facade.getTypeDeclaration(facade.findContainingTypeDecl(node));
+ if (typeOfNode instanceof ResolvedClassDeclaration) {
+ return ((ResolvedClassDeclaration) typeOfNode).getSuperClass();
+ } else {
+ throw new UnsupportedOperationException(node.getClass().getCanonicalName());
+ }
+ }
+
+ @Override
+ public ResolvedType visit(UnaryExpr node, Boolean solveLambdas) {
+ switch (node.getOperator()) {
+ case MINUS:
+ case PLUS:
+ return node.getExpression().accept(this, solveLambdas);
+ case LOGICAL_COMPLEMENT:
+ return ResolvedPrimitiveType.BOOLEAN;
+ case POSTFIX_DECREMENT:
+ case PREFIX_DECREMENT:
+ case POSTFIX_INCREMENT:
+ case PREFIX_INCREMENT:
+ 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 (requireParentNode(node) instanceof MethodCallExpr) {
+ MethodCallExpr callExpr = (MethodCallExpr) requireParentNode(node);
+ int pos = JavaParserSymbolDeclaration.getParamPos(node);
+ SymbolReference<ResolvedMethodDeclaration> refMethod = facade.solve(callExpr);
+ if (!refMethod.isSolved()) {
+ throw new com.github.javaparser.resolution.UnsolvedSymbolException(requireParentNode(node).toString(), callExpr.getName().getId());
+ }
+ logger.finest("getType on lambda expr " + 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.getScope().isPresent()) {
+ 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<ResolvedTypeDeclaration> type = JavaParserFactory.getContext(nameExpr, typeSolver).solveType(nameExpr.getName().getId(), typeSolver);
+ 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, typeSolver);
+
+ //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<MethodUsage> 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<T=? super Stream.T, ? extends map.R>
+ // 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<ReturnStmt> 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 {
+ return 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 (requireParentNode(node) instanceof MethodCallExpr) {
+ MethodCallExpr callExpr = (MethodCallExpr) requireParentNode(node);
+ int pos = JavaParserSymbolDeclaration.getParamPos(node);
+ SymbolReference<ResolvedMethodDeclaration> refMethod = facade.solve(callExpr, false);
+ if (!refMethod.isSolved()) {
+ throw new com.github.javaparser.resolution.UnsolvedSymbolException(requireParentNode(node).toString(), callExpr.getName().getId());
+ }
+ logger.finest("getType on method reference expr " + refMethod.getCorrespondingDeclaration().getName());
+ //logger.finest("Method param " + refMethod.getCorrespondingDeclaration().getParam(pos));
+ 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, typeSolver);
+
+ //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
+ if (FunctionalInterfaceLogic.getFunctionalMethod(result).isPresent()) {
+ MethodReferenceExpr methodReferenceExpr = node;
+
+ ResolvedType actualType = facade.toMethodUsage(methodReferenceExpr).returnType();
+ ResolvedType formalType = FunctionalInterfaceLogic.getFunctionalMethod(result).get().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");
+ }
+}