1 package com.github.javaparser.symbolsolver.javaparsermodel; 2 3 import com.github.javaparser.ast.CompilationUnit; 4 import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; 5 import com.github.javaparser.ast.body.FieldDeclaration; 6 import com.github.javaparser.ast.body.Parameter; 7 import com.github.javaparser.ast.body.VariableDeclarator; 8 import com.github.javaparser.ast.expr.*; 9 import com.github.javaparser.ast.stmt.BlockStmt; 10 import com.github.javaparser.ast.stmt.ExpressionStmt; 11 import com.github.javaparser.ast.stmt.ReturnStmt; 12 import com.github.javaparser.ast.type.UnknownType; 13 import com.github.javaparser.resolution.MethodUsage; 14 import com.github.javaparser.resolution.UnsolvedSymbolException; 15 import com.github.javaparser.resolution.declarations.ResolvedClassDeclaration; 16 import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; 17 import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; 18 import com.github.javaparser.resolution.declarations.ResolvedTypeDeclaration; 19 import com.github.javaparser.resolution.types.ResolvedArrayType; 20 import com.github.javaparser.resolution.types.ResolvedPrimitiveType; 21 import com.github.javaparser.resolution.types.ResolvedType; 22 import com.github.javaparser.resolution.types.ResolvedVoidType; 23 import com.github.javaparser.symbolsolver.core.resolution.Context; 24 import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserSymbolDeclaration; 25 import com.github.javaparser.symbolsolver.logic.FunctionalInterfaceLogic; 26 import com.github.javaparser.symbolsolver.logic.InferenceContext; 27 import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; 28 import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; 29 import com.github.javaparser.symbolsolver.model.resolution.Value; 30 import com.github.javaparser.symbolsolver.model.typesystem.*; 31 import com.github.javaparser.symbolsolver.reflectionmodel.MyObjectProvider; 32 import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionClassDeclaration; 33 import com.github.javaparser.symbolsolver.resolution.SymbolSolver; 34 import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver; 35 import com.google.common.collect.ImmutableList; 36 37 import java.util.List; 38 import java.util.Optional; 39 import java.util.logging.ConsoleHandler; 40 import java.util.logging.Level; 41 import java.util.logging.Logger; 42 43 import static com.github.javaparser.symbolsolver.javaparser.Navigator.requireParentNode; 44 import static com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade.solveGenericTypes; 45 46 public class TypeExtractor extends DefaultVisitorAdapter { 47 48 private static Logger logger = Logger.getLogger(TypeExtractor.class.getCanonicalName()); 49 50 static { 51 logger.setLevel(Level.INFO); 52 ConsoleHandler consoleHandler = new ConsoleHandler(); 53 consoleHandler.setLevel(Level.INFO); 54 logger.addHandler(consoleHandler); 55 } 56 57 private TypeSolver typeSolver; 58 private JavaParserFacade facade; 59 60 public TypeExtractor(TypeSolver typeSolver, JavaParserFacade facade) { 61 this.typeSolver = typeSolver; 62 this.facade = facade; 63 } 64 65 @Override 66 public ResolvedType visit(VariableDeclarator node, Boolean solveLambdas) { 67 if (requireParentNode(node) instanceof FieldDeclaration) { 68 return facade.convertToUsageVariableType(node); 69 } else if (requireParentNode(node) instanceof VariableDeclarationExpr) { 70 return facade.convertToUsageVariableType(node); 71 } 72 throw new UnsupportedOperationException(requireParentNode(node).getClass().getCanonicalName()); 73 } 74 75 @Override 76 public ResolvedType visit(Parameter node, Boolean solveLambdas) { 77 if (node.getType() instanceof UnknownType) { 78 throw new IllegalStateException("Parameter has unknown type: " + node); 79 } 80 return facade.convertToUsage(node.getType(), node); 81 } 82 83 84 @Override 85 public ResolvedType visit(ArrayAccessExpr node, Boolean solveLambdas) { 86 ResolvedType arrayUsageType = node.getName().accept(this, solveLambdas); 87 if (arrayUsageType.isArray()) { 88 return ((ResolvedArrayType) arrayUsageType).getComponentType(); 89 } 90 return arrayUsageType; 91 } 92 93 @Override 94 public ResolvedType visit(ArrayCreationExpr node, Boolean solveLambdas) { 95 ResolvedType res = facade.convertToUsage(node.getElementType(), JavaParserFactory.getContext(node, typeSolver)); 96 for (int i = 0; i < node.getLevels().size(); i++) { 97 res = new ResolvedArrayType(res); 98 } 99 return res; 100 } 101 102 @Override 103 public ResolvedType visit(ArrayInitializerExpr node, Boolean solveLambdas) { 104 throw new UnsupportedOperationException(node.getClass().getCanonicalName()); 105 } 106 107 @Override 108 public ResolvedType visit(AssignExpr node, Boolean solveLambdas) { 109 return node.getTarget().accept(this, solveLambdas); 110 } 111 112 @Override 113 public ResolvedType visit(BinaryExpr node, Boolean solveLambdas) { 114 switch (node.getOperator()) { 115 case PLUS: 116 case MINUS: 117 case DIVIDE: 118 case MULTIPLY: 119 return facade.getBinaryTypeConcrete(node.getLeft(), node.getRight(), solveLambdas); 120 case LESS_EQUALS: 121 case LESS: 122 case GREATER: 123 case GREATER_EQUALS: 124 case EQUALS: 125 case NOT_EQUALS: 126 case OR: 127 case AND: 128 return ResolvedPrimitiveType.BOOLEAN; 129 case BINARY_AND: 130 case BINARY_OR: 131 case SIGNED_RIGHT_SHIFT: 132 case UNSIGNED_RIGHT_SHIFT: 133 case LEFT_SHIFT: 134 case REMAINDER: 135 case XOR: 136 return node.getLeft().accept(this, solveLambdas); 137 default: 138 throw new UnsupportedOperationException("Operator " + node.getOperator().name()); 139 } 140 } 141 142 @Override 143 public ResolvedType visit(CastExpr node, Boolean solveLambdas) { 144 return facade.convertToUsage(node.getType(), JavaParserFactory.getContext(node, typeSolver)); 145 } 146 147 @Override 148 public ResolvedType visit(ClassExpr node, Boolean solveLambdas) { 149 // This implementation does not regard the actual type argument of the ClassExpr. 150 com.github.javaparser.ast.type.Type astType = node.getType(); 151 ResolvedType jssType = facade.convertToUsage(astType, node.getType()); 152 return new ReferenceTypeImpl(new ReflectionClassDeclaration(Class.class, typeSolver), ImmutableList.of(jssType), typeSolver); 153 } 154 155 @Override 156 public ResolvedType visit(ConditionalExpr node, Boolean solveLambdas) { 157 return node.getThenExpr().accept(this, solveLambdas); 158 } 159 160 @Override 161 public ResolvedType visit(EnclosedExpr node, Boolean solveLambdas) { 162 return node.getInner().accept(this, solveLambdas); 163 } 164 165 /** 166 * Java Parser can't differentiate between packages, internal types, and fields. 167 * All three are lumped together into FieldAccessExpr. We need to differentiate them. 168 */ 169 private ResolvedType solveDotExpressionType(ResolvedReferenceTypeDeclaration parentType, FieldAccessExpr node) { 170 // Fields and internal type declarations cannot have the same name. 171 // Thus, these checks will always be mutually exclusive. 172 if (parentType.hasField(node.getName().getId())) { 173 return parentType.getField(node.getName().getId()).getType(); 174 } else if (parentType.hasInternalType(node.getName().getId())) { 175 return new ReferenceTypeImpl(parentType.getInternalType(node.getName().getId()), typeSolver); 176 } else { 177 throw new UnsolvedSymbolException(node.getName().getId()); 178 } 179 } 180 181 @Override 182 public ResolvedType visit(FieldAccessExpr node, Boolean solveLambdas) { 183 // We should understand if this is a static access 184 if (node.getScope() instanceof NameExpr || 185 node.getScope() instanceof FieldAccessExpr) { 186 Expression staticValue = node.getScope(); 187 SymbolReference<ResolvedTypeDeclaration> typeAccessedStatically = JavaParserFactory.getContext(node, typeSolver).solveType(staticValue.toString(), typeSolver); 188 if (typeAccessedStatically.isSolved()) { 189 // TODO here maybe we have to substitute type typeParametersValues 190 return solveDotExpressionType( 191 typeAccessedStatically.getCorrespondingDeclaration().asReferenceType(), node); 192 } 193 } else if (node.getScope() instanceof ThisExpr) { 194 // If we are accessing through a 'this' expression, first resolve the type 195 // corresponding to 'this' 196 SymbolReference<ResolvedTypeDeclaration> solve = facade.solve((ThisExpr) node.getScope()); 197 // If found get it's declaration and get the field in there 198 if (solve.isSolved()) { 199 ResolvedTypeDeclaration correspondingDeclaration = solve.getCorrespondingDeclaration(); 200 if (correspondingDeclaration instanceof ResolvedReferenceTypeDeclaration) { 201 return solveDotExpressionType(correspondingDeclaration.asReferenceType(), node); 202 } 203 } 204 205 } else if (node.getScope().toString().indexOf('.') > 0) { 206 // try to find fully qualified name 207 SymbolReference<ResolvedReferenceTypeDeclaration> sr = typeSolver.tryToSolveType(node.getScope().toString()); 208 if (sr.isSolved()) { 209 return solveDotExpressionType(sr.getCorrespondingDeclaration(), node); 210 } 211 } 212 Optional<Value> value = Optional.empty(); 213 try { 214 value = new SymbolSolver(typeSolver).solveSymbolAsValue(node.getName().getId(), node); 215 } catch (com.github.javaparser.resolution.UnsolvedSymbolException use) { 216 // This node may have a package name as part of its fully qualified name. 217 // We should solve for the type declaration inside this package. 218 SymbolReference<ResolvedReferenceTypeDeclaration> sref = typeSolver.tryToSolveType(node.toString()); 219 if (sref.isSolved()) { 220 return new ReferenceTypeImpl(sref.getCorrespondingDeclaration(), typeSolver); 221 } 222 } 223 if (value.isPresent()) { 224 return value.get().getType(); 225 } 226 throw new com.github.javaparser.resolution.UnsolvedSymbolException(node.getName().getId()); 227 } 228 229 @Override 230 public ResolvedType visit(InstanceOfExpr node, Boolean solveLambdas) { 231 return ResolvedPrimitiveType.BOOLEAN; 232 } 233 234 @Override 235 public ResolvedType visit(StringLiteralExpr node, Boolean solveLambdas) { 236 return new ReferenceTypeImpl(new ReflectionTypeSolver().solveType(String.class.getCanonicalName()), typeSolver); 237 } 238 239 @Override 240 public ResolvedType visit(IntegerLiteralExpr node, Boolean solveLambdas) { 241 return ResolvedPrimitiveType.INT; 242 } 243 244 @Override 245 public ResolvedType visit(LongLiteralExpr node, Boolean solveLambdas) { 246 return ResolvedPrimitiveType.LONG; 247 } 248 249 @Override 250 public ResolvedType visit(CharLiteralExpr node, Boolean solveLambdas) { 251 return ResolvedPrimitiveType.CHAR; 252 } 253 254 @Override 255 public ResolvedType visit(DoubleLiteralExpr node, Boolean solveLambdas) { 256 if (node.getValue().toLowerCase().endsWith("f")) { 257 return ResolvedPrimitiveType.FLOAT; 258 } 259 return ResolvedPrimitiveType.DOUBLE; 260 } 261 262 @Override 263 public ResolvedType visit(BooleanLiteralExpr node, Boolean solveLambdas) { 264 return ResolvedPrimitiveType.BOOLEAN; 265 } 266 267 @Override 268 public ResolvedType visit(NullLiteralExpr node, Boolean solveLambdas) { 269 return NullType.INSTANCE; 270 } 271 272 @Override 273 public ResolvedType visit(MethodCallExpr node, Boolean solveLambdas) { 274 logger.finest("getType on method call " + node); 275 // first solve the method 276 MethodUsage ref = facade.solveMethodAsUsage(node); 277 logger.finest("getType on method call " + node + " resolved to " + ref); 278 logger.finest("getType on method call " + node + " return type is " + ref.returnType()); 279 return ref.returnType(); 280 // the type is the return type of the method 281 } 282 283 @Override 284 public ResolvedType visit(NameExpr node, Boolean solveLambdas) { 285 logger.finest("getType on name expr " + node); 286 Optional<Value> value = new SymbolSolver(typeSolver).solveSymbolAsValue(node.getName().getId(), node); 287 if (!value.isPresent()) { 288 throw new com.github.javaparser.resolution.UnsolvedSymbolException("Solving " + node, node.getName().getId()); 289 } else { 290 return value.get().getType(); 291 } 292 } 293 294 @Override 295 public ResolvedType visit(ObjectCreationExpr node, Boolean solveLambdas) { 296 return facade.convertToUsage(node.getType(), node); 297 } 298 299 @Override 300 public ResolvedType visit(ThisExpr node, Boolean solveLambdas) { 301 // If 'this' is prefixed by a class eg. MyClass.this 302 if (node.getClassExpr().isPresent()) { 303 // Get the class name 304 String className = node.getClassExpr().get().toString(); 305 // Attempt to resolve using a typeSolver 306 SymbolReference<ResolvedReferenceTypeDeclaration> clazz = typeSolver.tryToSolveType(className); 307 if (clazz.isSolved()) { 308 return new ReferenceTypeImpl(clazz.getCorrespondingDeclaration(), typeSolver); 309 } 310 // Attempt to resolve locally in Compilation unit 311 Optional<CompilationUnit> cu = node.getAncestorOfType(CompilationUnit.class); 312 if (cu.isPresent()) { 313 Optional<ClassOrInterfaceDeclaration> classByName = cu.get().getClassByName(className); 314 if (classByName.isPresent()) { 315 return new ReferenceTypeImpl(facade.getTypeDeclaration(classByName.get()), typeSolver); 316 } 317 } 318 319 } 320 return new ReferenceTypeImpl(facade.getTypeDeclaration(facade.findContainingTypeDeclOrObjectCreationExpr(node)), typeSolver); 321 } 322 323 @Override 324 public ResolvedType visit(SuperExpr node, Boolean solveLambdas) { 325 ResolvedTypeDeclaration typeOfNode = facade.getTypeDeclaration(facade.findContainingTypeDecl(node)); 326 if (typeOfNode instanceof ResolvedClassDeclaration) { 327 return ((ResolvedClassDeclaration) typeOfNode).getSuperClass(); 328 } else { 329 throw new UnsupportedOperationException(node.getClass().getCanonicalName()); 330 } 331 } 332 333 @Override 334 public ResolvedType visit(UnaryExpr node, Boolean solveLambdas) { 335 switch (node.getOperator()) { 336 case MINUS: 337 case PLUS: 338 return node.getExpression().accept(this, solveLambdas); 339 case LOGICAL_COMPLEMENT: 340 return ResolvedPrimitiveType.BOOLEAN; 341 case POSTFIX_DECREMENT: 342 case PREFIX_DECREMENT: 343 case POSTFIX_INCREMENT: 344 case PREFIX_INCREMENT: 345 return node.getExpression().accept(this, solveLambdas); 346 default: 347 throw new UnsupportedOperationException(node.getOperator().name()); 348 } 349 } 350 351 @Override 352 public ResolvedType visit(VariableDeclarationExpr node, Boolean solveLambdas) { 353 if (node.getVariables().size() != 1) { 354 throw new UnsupportedOperationException(); 355 } 356 return facade.convertToUsageVariableType(node.getVariables().get(0)); 357 } 358 359 360 @Override 361 public ResolvedType visit(LambdaExpr node, Boolean solveLambdas) { 362 if (requireParentNode(node) instanceof MethodCallExpr) { 363 MethodCallExpr callExpr = (MethodCallExpr) requireParentNode(node); 364 int pos = JavaParserSymbolDeclaration.getParamPos(node); 365 SymbolReference<ResolvedMethodDeclaration> refMethod = facade.solve(callExpr); 366 if (!refMethod.isSolved()) { 367 throw new com.github.javaparser.resolution.UnsolvedSymbolException(requireParentNode(node).toString(), callExpr.getName().getId()); 368 } 369 logger.finest("getType on lambda expr " + refMethod.getCorrespondingDeclaration().getName()); 370 if (solveLambdas) { 371 372 // The type parameter referred here should be the java.util.stream.Stream.T 373 ResolvedType result = refMethod.getCorrespondingDeclaration().getParam(pos).getType(); 374 375 if (callExpr.getScope().isPresent()) { 376 Expression scope = callExpr.getScope().get(); 377 378 // If it is a static call we should not try to get the type of the scope 379 boolean staticCall = false; 380 if (scope instanceof NameExpr) { 381 NameExpr nameExpr = (NameExpr) scope; 382 try { 383 SymbolReference<ResolvedTypeDeclaration> type = JavaParserFactory.getContext(nameExpr, typeSolver).solveType(nameExpr.getName().getId(), typeSolver); 384 if (type.isSolved()) { 385 staticCall = true; 386 } 387 } catch (Exception e) { 388 389 } 390 } 391 392 if (!staticCall) { 393 ResolvedType scopeType = facade.getType(scope); 394 if (scopeType.isReferenceType()) { 395 result = scopeType.asReferenceType().useThisTypeParametersOnTheGivenType(result); 396 } 397 } 398 } 399 400 // We need to replace the type variables 401 Context ctx = JavaParserFactory.getContext(node, typeSolver); 402 result = solveGenericTypes(result, ctx, typeSolver); 403 404 //We should find out which is the functional method (e.g., apply) and replace the params of the 405 //solveLambdas with it, to derive so the values. We should also consider the value returned by the 406 //lambdas 407 Optional<MethodUsage> functionalMethod = FunctionalInterfaceLogic.getFunctionalMethod(result); 408 if (functionalMethod.isPresent()) { 409 LambdaExpr lambdaExpr = node; 410 411 InferenceContext lambdaCtx = new InferenceContext(MyObjectProvider.INSTANCE); 412 InferenceContext funcInterfaceCtx = new InferenceContext(MyObjectProvider.INSTANCE); 413 414 // At this point parameterType 415 // if Function<T=? super Stream.T, ? extends map.R> 416 // we should replace Stream.T 417 ResolvedType functionalInterfaceType = ReferenceTypeImpl.undeterminedParameters(functionalMethod.get().getDeclaration().declaringType(), typeSolver); 418 419 lambdaCtx.addPair(result, functionalInterfaceType); 420 421 ResolvedType actualType; 422 423 if (lambdaExpr.getBody() instanceof ExpressionStmt) { 424 actualType = facade.getType(((ExpressionStmt) lambdaExpr.getBody()).getExpression()); 425 } else if (lambdaExpr.getBody() instanceof BlockStmt) { 426 BlockStmt blockStmt = (BlockStmt) lambdaExpr.getBody(); 427 428 // Get all the return statements in the lambda block 429 List<ReturnStmt> returnStmts = blockStmt.findAll(ReturnStmt.class); 430 431 if (returnStmts.size() > 0) { 432 actualType = returnStmts.stream() 433 .map(returnStmt -> returnStmt.getExpression().map(e -> facade.getType(e)).orElse(ResolvedVoidType.INSTANCE)) 434 .filter(x -> x != null && !x.isVoid() && !x.isNull()) 435 .findFirst() 436 .orElse(ResolvedVoidType.INSTANCE); 437 438 } else { 439 return ResolvedVoidType.INSTANCE; 440 } 441 442 443 } else { 444 throw new UnsupportedOperationException(); 445 } 446 447 ResolvedType formalType = functionalMethod.get().returnType(); 448 449 // Infer the functional interfaces' return vs actual type 450 funcInterfaceCtx.addPair(formalType, actualType); 451 // Substitute to obtain a new type 452 ResolvedType functionalTypeWithReturn = funcInterfaceCtx.resolve(funcInterfaceCtx.addSingle(functionalInterfaceType)); 453 454 // if the functional method returns void anyway 455 // we don't need to bother inferring types 456 if (!(formalType instanceof ResolvedVoidType)) { 457 lambdaCtx.addPair(result, functionalTypeWithReturn); 458 result = lambdaCtx.resolve(lambdaCtx.addSingle(result)); 459 } 460 } 461 462 return result; 463 } else { 464 return refMethod.getCorrespondingDeclaration().getParam(pos).getType(); 465 } 466 } else { 467 throw new UnsupportedOperationException("The type of a lambda expr depends on the position and its return value"); 468 } 469 } 470 471 @Override 472 public ResolvedType visit(MethodReferenceExpr node, Boolean solveLambdas) { 473 if (requireParentNode(node) instanceof MethodCallExpr) { 474 MethodCallExpr callExpr = (MethodCallExpr) requireParentNode(node); 475 int pos = JavaParserSymbolDeclaration.getParamPos(node); 476 SymbolReference<ResolvedMethodDeclaration> refMethod = facade.solve(callExpr, false); 477 if (!refMethod.isSolved()) { 478 throw new com.github.javaparser.resolution.UnsolvedSymbolException(requireParentNode(node).toString(), callExpr.getName().getId()); 479 } 480 logger.finest("getType on method reference expr " + refMethod.getCorrespondingDeclaration().getName()); 481 //logger.finest("Method param " + refMethod.getCorrespondingDeclaration().getParam(pos)); 482 if (solveLambdas) { 483 MethodUsage usage = facade.solveMethodAsUsage(callExpr); 484 ResolvedType result = usage.getParamType(pos); 485 // We need to replace the type variables 486 Context ctx = JavaParserFactory.getContext(node, typeSolver); 487 result = solveGenericTypes(result, ctx, typeSolver); 488 489 //We should find out which is the functional method (e.g., apply) and replace the params of the 490 //solveLambdas with it, to derive so the values. We should also consider the value returned by the 491 //lambdas 492 if (FunctionalInterfaceLogic.getFunctionalMethod(result).isPresent()) { 493 MethodReferenceExpr methodReferenceExpr = node; 494 495 ResolvedType actualType = facade.toMethodUsage(methodReferenceExpr).returnType(); 496 ResolvedType formalType = FunctionalInterfaceLogic.getFunctionalMethod(result).get().returnType(); 497 498 InferenceContext inferenceContext = new InferenceContext(MyObjectProvider.INSTANCE); 499 inferenceContext.addPair(formalType, actualType); 500 result = inferenceContext.resolve(inferenceContext.addSingle(result)); 501 } 502 503 return result; 504 } 505 return refMethod.getCorrespondingDeclaration().getParam(pos).getType(); 506 } 507 throw new UnsupportedOperationException("The type of a method reference expr depends on the position and its return value"); 508 } 509 510 @Override 511 public ResolvedType visit(FieldDeclaration node, Boolean solveLambdas) { 512 if (node.getVariables().size() == 1) { 513 return node.getVariables().get(0).accept(this, solveLambdas); 514 } 515 throw new IllegalArgumentException("Cannot resolve the type of a field with multiple variable declarations. Pick one"); 516 } 517 } 518