1 package annotator.specification; 2 3 import java.io.IOException; 4 import java.util.ArrayList; 5 import java.util.LinkedHashSet; 6 import java.util.List; 7 import java.util.Map; 8 import java.util.Map.Entry; 9 import java.util.Set; 10 11 import org.objectweb.asm.ClassReader; 12 13 import plume.FileIOException; 14 import plume.Pair; 15 import type.DeclaredType; 16 import type.Type; 17 import annotations.Annotation; 18 import annotations.el.ABlock; 19 import annotations.el.AClass; 20 import annotations.el.AElement; 21 import annotations.el.AExpression; 22 import annotations.el.AField; 23 import annotations.el.AMethod; 24 import annotations.el.AScene; 25 import annotations.el.ATypeElement; 26 import annotations.el.ATypeElementWithType; 27 import annotations.el.AnnotationDef; 28 import annotations.el.BoundLocation; 29 import annotations.el.InnerTypeLocation; 30 import annotations.el.LocalLocation; 31 import annotations.el.RelativeLocation; 32 import annotations.el.TypeIndexLocation; 33 import annotations.field.AnnotationFieldType; 34 import annotations.io.ASTPath; 35 import annotations.io.IndexFileParser; 36 import annotations.util.coll.VivifyingMap; 37 import annotator.find.AnnotationInsertion; 38 import annotator.find.CastInsertion; 39 import annotator.find.CloseParenthesisInsertion; 40 import annotator.find.ConstructorInsertion; 41 import annotator.find.Criteria; 42 import annotator.find.GenericArrayLocationCriterion; 43 import annotator.find.Insertion; 44 import annotator.find.IntersectionTypeLocationCriterion; 45 import annotator.find.NewInsertion; 46 import annotator.find.ReceiverInsertion; 47 import annotator.scanner.MethodOffsetClassVisitor; 48 49 import com.google.common.collect.LinkedHashMultimap; 50 import com.google.common.collect.Multimap; 51 import com.sun.source.tree.Tree; 52 53 public class IndexFileSpecification implements Specification { 54 private final Multimap<Insertion, Annotation> insertionSources = 55 LinkedHashMultimap.<Insertion, Annotation>create(); 56 private final List<Insertion> insertions = new ArrayList<Insertion>(); 57 private final AScene scene; 58 private final String indexFileName; 59 60 // If set, do not attempt to read class files with Asm. 61 // Mostly for debugging and workarounds. 62 public static boolean noAsm = false; 63 64 private static boolean debug = false; 65 66 private ConstructorInsertion cons = null; 67 68 public IndexFileSpecification(String indexFileName) { 69 this.indexFileName = indexFileName; 70 scene = new AScene(); 71 } 72 73 @Override 74 public List<Insertion> parse() throws FileIOException { 75 try { 76 Map<String, AnnotationDef> annotationDefs = 77 IndexFileParser.parseFile(indexFileName, scene); 78 Set<String> defKeys = annotationDefs.keySet(); 79 Set<String> ambiguous = new LinkedHashSet<String>(); 80 // If a qualified name's unqualified counterpart maps to null in 81 // defKeys, it means that the unqualified name is ambiguous and 82 // thus should always be qualified. 83 for (String key : defKeys) { 84 int ix = Math.max(key.lastIndexOf("."), key.lastIndexOf("$")); 85 if (ix >= 0) { 86 String name = key.substring(ix+1); 87 // containsKey() would give wrong result here 88 if (annotationDefs.get(name) == null) { ambiguous.add(name); } 89 } 90 } 91 Insertion.setAlwaysQualify(ambiguous); 92 } catch (FileIOException e) { 93 throw e; 94 } catch (Exception e) { 95 throw new RuntimeException("Exception while parsing index file", e); 96 } 97 98 if (debug) { 99 System.out.printf("Scene parsed from %s:%n", indexFileName); 100 System.out.println(scene.unparse()); 101 } 102 103 parseScene(); 104 // debug("---------------------------------------------------------"); 105 return this.insertions; 106 } 107 108 public Map<String, Set<String>> annotationImports() { 109 return scene.imports; 110 } 111 112 public Multimap<Insertion, Annotation> insertionSources() { 113 return insertionSources; 114 } 115 116 private void addInsertionSource(Insertion ins, Annotation anno) { 117 insertionSources.put(ins, anno); 118 } 119 120 private static void debug(String s) { 121 if (debug) { 122 System.out.println(s); 123 } 124 } 125 126 /* 127 private static void debug(String s, Object... args) { 128 if (debug) { 129 System.out.printf(s, args); 130 } 131 } 132 */ 133 134 public AScene getScene() { return scene; } 135 136 /** Fill in this.insertions with insertion pairs. */ 137 private void parseScene() { 138 debug("parseScene()"); 139 140 // Empty criterion to work from. 141 CriterionList clist = new CriterionList(); 142 143 VivifyingMap<String, AElement> packages = scene.packages; 144 for (Map.Entry<String, AElement> entry : packages.entrySet()) { 145 parsePackage(clist, entry.getKey(), entry.getValue()); 146 } 147 148 VivifyingMap<String, AClass> classes = scene.classes; 149 for (Map.Entry<String, AClass> entry : classes.entrySet()) { 150 String key = entry.getKey(); 151 AClass clazz = entry.getValue(); 152 if (key.endsWith(".package-info")) { 153 // strip off suffix to get package name 154 parsePackage(clist, key.substring(0, key.length()-13), clazz); 155 } else { 156 parseClass(clist, key, clazz); 157 } 158 } 159 } 160 161 // There is no .class file corresponding to the package-info.java file. 162 private void parsePackage(CriterionList clist, String packageName, AElement element) { 163 // There is no Tree.Kind.PACKAGE, only Tree.Kind.COMPILATION_UNIT. 164 // CompilationUnitTree has getPackageName and getPackageAnnotations 165 CriterionList packageClist = clist.add(Criteria.packageDecl(packageName)); 166 parseElement(packageClist, element); 167 } 168 169 170 171 /** Fill in this.insertions with insertion pairs. 172 * @param className is fully qualified 173 */ 174 private void parseClass(CriterionList clist, String className, AClass clazz) { 175 cons = null; // 0 or 1 per class 176 if (! noAsm) { 177 // load extra info using asm 178 debug("parseClass(" + className + ")"); 179 try { 180 ClassReader classReader = new ClassReader(className); 181 MethodOffsetClassVisitor cv = new MethodOffsetClassVisitor(classReader); 182 classReader.accept(cv, false); 183 debug("Done reading " + className + ".class"); 184 } catch (IOException e) { 185 // If .class file not found, still proceed, in case 186 // user only wants method signature annotations. 187 // (TODO: It would be better to store which classes could not be 188 // found, then issue a warning only if an attempt is made to use 189 // the (missing) information. See 190 // https://github.com/typetools/annotation-tools/issues/34 .) 191 System.out.println("Warning: IndexFileSpecification did not find classfile for: " + className); 192 // throw new RuntimeException("IndexFileSpecification.parseClass: " + e); 193 } catch (RuntimeException e) { 194 System.err.println("IndexFileSpecification had a problem reading class: " + className); 195 throw e; 196 } catch (Error e) { 197 System.err.println("IndexFileSpecification had a problem reading class: " + className); 198 throw e; 199 } 200 } 201 202 CriterionList clistSansClass = clist; 203 204 clist = clist.add(Criteria.inClass(className, true)); 205 CriterionList classClist = clistSansClass.add(Criteria.is(Tree.Kind.CLASS, className)); 206 parseElement(classClist, clazz); 207 208 VivifyingMap<BoundLocation, ATypeElement> bounds = clazz.bounds; 209 for (Entry<BoundLocation, ATypeElement> entry : bounds.entrySet()) { 210 BoundLocation boundLoc = entry.getKey(); 211 ATypeElement bound = entry.getValue(); 212 CriterionList boundList = clist.add(Criteria.classBound(className, boundLoc)); 213 for (Entry<InnerTypeLocation, ATypeElement> innerEntry : bound.innerTypes.entrySet()) { 214 InnerTypeLocation innerLoc = innerEntry.getKey(); 215 AElement ae = innerEntry.getValue(); 216 CriterionList innerBoundList = boundList.add(Criteria.atLocation(innerLoc)); 217 parseElement(innerBoundList, ae); 218 } 219 CriterionList outerClist = boundList.add(Criteria.atLocation()); 220 parseElement(outerClist, bound); 221 } 222 223 clist = clist.add(Criteria.inClass(className, /*exactMatch=*/ false)); 224 225 VivifyingMap<TypeIndexLocation, ATypeElement> extimpl = clazz.extendsImplements; 226 for (Entry<TypeIndexLocation, ATypeElement> entry : extimpl.entrySet()) { 227 TypeIndexLocation eiLoc = entry.getKey(); 228 ATypeElement ei = entry.getValue(); 229 CriterionList eiList = clist.add(Criteria.atExtImplsLocation(className, eiLoc)); 230 231 for (Entry<InnerTypeLocation, ATypeElement> innerEntry : ei.innerTypes.entrySet()) { 232 InnerTypeLocation innerLoc = innerEntry.getKey(); 233 AElement ae = innerEntry.getValue(); 234 CriterionList innerBoundList = eiList.add(Criteria.atLocation(innerLoc)); 235 parseElement(innerBoundList, ae); 236 } 237 parseElement(eiList, ei); 238 } 239 240 parseASTInsertions(clist, clazz.insertAnnotations, clazz.insertTypecasts); 241 242 for (Map.Entry<String, AField> entry : clazz.fields.entrySet()) { 243 // clist = clist.add(Criteria.notInMethod()); // TODO: necessary? what is in class but not in method? 244 parseField(clist, entry.getKey(), entry.getValue()); 245 } 246 for (Map.Entry<String, AMethod> entry : clazz.methods.entrySet()) { 247 parseMethod(clist, className, entry.getKey(), entry.getValue()); 248 } 249 for (Map.Entry<Integer, ABlock> entry : clazz.staticInits.entrySet()) { 250 parseStaticInit(clist, className, entry.getKey(), entry.getValue()); 251 } 252 for (Map.Entry<Integer, ABlock> entry : clazz.instanceInits.entrySet()) { 253 parseInstanceInit(clist, className, entry.getKey(), entry.getValue()); 254 } 255 for (Map.Entry<String, AExpression> entry : clazz.fieldInits.entrySet()) { 256 parseFieldInit(clist, className, entry.getKey(), entry.getValue()); 257 } 258 259 debug("parseClass(" + className + "): done"); 260 } 261 262 /** Fill in this.insertions with insertion pairs. */ 263 private void parseField(CriterionList clist, String fieldName, AField field) { 264 // parse declaration annotations 265 parseElement(clist.add(Criteria.field(fieldName, true)), field); 266 267 // parse type annotations 268 clist = clist.add(Criteria.field(fieldName, false)); 269 parseInnerAndOuterElements(clist, field.type); 270 parseASTInsertions(clist, field.insertAnnotations, field.insertTypecasts); 271 } 272 273 private void parseStaticInit(CriterionList clist, String className, int blockID, ABlock block) { 274 clist = clist.add(Criteria.inStaticInit(blockID)); 275 // the method name argument is not used for static initializers, which are only used 276 // in source specifications. Same for instance and field initializers. 277 // the empty () are there to prevent the whole string to be removed in later parsing. 278 parseBlock(clist, className, "static init number " + blockID + "()", block); 279 } 280 281 private void parseInstanceInit(CriterionList clist, String className, int blockID, ABlock block) { 282 clist = clist.add(Criteria.inInstanceInit(blockID)); 283 parseBlock(clist, className, "instance init number " + blockID + "()", block); 284 } 285 286 // keep the descriptive strings for field initializers and static inits consistent 287 // with text used in NewCriterion. 288 289 private void parseFieldInit(CriterionList clist, String className, String fieldName, AExpression exp) { 290 clist = clist.add(Criteria.inFieldInit(fieldName)); 291 parseExpression(clist, className, "init for field " + fieldName + "()", exp); 292 } 293 294 /** 295 * Fill in this.insertions with insertion pairs. 296 * @param clist the criteria specifying the location of the insertions 297 * @param element holds the annotations to be inserted 298 * @return a list of the {@link AnnotationInsertion}s that are created 299 */ 300 private List<Insertion> parseElement(CriterionList clist, AElement element) { 301 return parseElement(clist, element, new ArrayList<Insertion>(), false); 302 } 303 304 /** 305 * Fill in this.insertions with insertion pairs. 306 * @param clist the criteria specifying the location of the insertions 307 * @param element holds the annotations to be inserted 308 * @param isCastInsertion {@code true} if this for a cast insertion, {@code false} 309 * otherwise. 310 * @return a list of the {@link AnnotationInsertion}s that are created 311 */ 312 private List<Insertion> parseElement(CriterionList clist, AElement element, 313 boolean isCastInsertion) { 314 return parseElement(clist, element, new ArrayList<Insertion>(), isCastInsertion); 315 } 316 317 /** 318 * Fill in this.insertions with insertion pairs. 319 * @param clist the criteria specifying the location of the insertions 320 * @param element holds the annotations to be inserted 321 * @param add {@code true} if the create {@link AnnotationInsertion}s should 322 * be added to {@link #insertions}, {@code false} otherwise. 323 * @return a list of the {@link AnnotationInsertion}s that are created 324 */ 325 private List<Insertion> parseElement(CriterionList clist, AElement element, 326 List<Insertion> innerTypeInsertions) { 327 return parseElement(clist, element, innerTypeInsertions, false); 328 } 329 330 /** 331 * Fill in this.insertions with insertion pairs. 332 * @param clist the criteria specifying the location of the insertions 333 * @param element holds the annotations to be inserted 334 * @param innerTypeInsertions the insertions on the inner type of this 335 * element. This is only used for receiver and "new" insertions. 336 * See {@link ReceiverInsertion} for more details. 337 * @param isCastInsertion {@code true} if this for a cast insertion, {@code false} 338 * otherwise. 339 * @return a list of the {@link AnnotationInsertion}s that are created 340 */ 341 private List<Insertion> parseElement(CriterionList clist, AElement element, 342 List<Insertion> innerTypeInsertions, boolean isCastInsertion) { 343 // Use at most one receiver and one cast insertion and add all of the 344 // annotations to the one insertion. 345 ReceiverInsertion receiver = null; 346 NewInsertion neu = null; 347 CastInsertion cast = null; 348 CloseParenthesisInsertion closeParen = null; 349 List<Insertion> annotationInsertions = new ArrayList<Insertion>(); 350 Set<Pair<String, Annotation>> elementAnnotations = getElementAnnotations(element); 351 if (elementAnnotations.isEmpty()) { 352 Criteria criteria = clist.criteria(); 353 if (element instanceof ATypeElementWithType) { 354 // Still insert even if it's a cast insertion with no outer 355 // annotations to just insert a cast, or insert a cast with 356 // annotations on the compound types. 357 Pair<CastInsertion, CloseParenthesisInsertion> pair = 358 createCastInsertion(((ATypeElementWithType) element).getType(), 359 null, innerTypeInsertions, criteria); 360 cast = pair.a; 361 closeParen = pair.b; 362 } else if (!innerTypeInsertions.isEmpty()) { 363 if (isOnReceiver(criteria)) { 364 receiver = new ReceiverInsertion(new DeclaredType(), 365 criteria, innerTypeInsertions); 366 } else if (isOnNew(criteria)) { 367 neu = new NewInsertion(new DeclaredType(), 368 criteria, innerTypeInsertions); 369 } 370 } 371 } 372 373 for (Pair<String, Annotation> p : elementAnnotations) { 374 List<Insertion> elementInsertions = new ArrayList<Insertion>(); 375 String annotationString = p.a; 376 Annotation annotation = p.b; 377 Criteria criteria = clist.criteria(); 378 Boolean isDeclarationAnnotation = !annotation.def.isTypeAnnotation() 379 || criteria.isOnFieldDeclaration(); 380 if (noTypePath(criteria) && isOnReceiver(criteria)) { 381 if (receiver == null) { 382 DeclaredType type = new DeclaredType(); 383 type.addAnnotation(annotationString); 384 receiver = new ReceiverInsertion(type, criteria, innerTypeInsertions); 385 elementInsertions.add(receiver); 386 } else { 387 receiver.getType().addAnnotation(annotationString); 388 } 389 addInsertionSource(receiver, annotation); 390 } else if (noTypePath(criteria) && isOnNew(criteria)) { 391 if (neu == null) { 392 DeclaredType type = new DeclaredType(); 393 type.addAnnotation(annotationString); 394 neu = new NewInsertion(type, criteria, innerTypeInsertions); 395 elementInsertions.add(neu); 396 } else { 397 neu.getType().addAnnotation(annotationString); 398 } 399 addInsertionSource(neu, annotation); 400 } else if (element instanceof ATypeElementWithType) { 401 if (cast == null) { 402 Pair<CastInsertion, CloseParenthesisInsertion> insertions = createCastInsertion( 403 ((ATypeElementWithType) element).getType(), annotationString, 404 innerTypeInsertions, criteria); 405 cast = insertions.a; 406 closeParen = insertions.b; 407 elementInsertions.add(cast); 408 elementInsertions.add(closeParen); 409 // no addInsertionSource, as closeParen is not explicit in scene 410 } else { 411 cast.getType().addAnnotation(annotationString); 412 } 413 addInsertionSource(cast, annotation); 414 } else { 415 RelativeLocation loc = criteria.getCastRelativeLocation(); 416 if (loc != null && loc.type_index > 0) { 417 criteria.add(new IntersectionTypeLocationCriterion(loc)); 418 } 419 Insertion ins = new AnnotationInsertion(annotationString, criteria, 420 isDeclarationAnnotation); 421 debug("parsed: " + ins); 422 if (!isCastInsertion) { 423 // Annotations on compound types of a cast insertion will be 424 // inserted directly on the cast insertion. 425 // this.insertions.add(ins); 426 elementInsertions.add(ins); 427 } 428 annotationInsertions.add(ins); 429 addInsertionSource(ins, annotation); 430 } 431 this.insertions.addAll(elementInsertions); 432 433 // exclude expression annotations 434 if (noTypePath(criteria) && isOnNullaryConstructor(criteria)) { 435 if (cons == null) { 436 DeclaredType type = new DeclaredType(criteria.getClassName()); 437 cons = new ConstructorInsertion(type, criteria, 438 new ArrayList<Insertion>()); 439 this.insertions.add(cons); 440 } 441 // no addInsertionSource, as cons is not explicit in scene 442 for (Insertion i : elementInsertions) { 443 if (i.getKind() == Insertion.Kind.RECEIVER) { 444 cons.addReceiverInsertion((ReceiverInsertion) i); 445 } else if (criteria.isOnReturnType()) { 446 ((DeclaredType) cons.getType()).addAnnotation(annotationString); 447 } else if (isDeclarationAnnotation) { 448 cons.addDeclarationInsertion(i); 449 i.setInserted(true); 450 } else { 451 annotationInsertions.add(i); 452 } 453 } 454 } 455 elementInsertions.clear(); 456 } 457 if (receiver != null) { 458 this.insertions.add(receiver); 459 } 460 if (neu != null) { 461 this.insertions.add(neu); 462 } 463 if (cast != null) { 464 this.insertions.add(cast); 465 this.insertions.add(closeParen); 466 } 467 return annotationInsertions; 468 } 469 470 private boolean noTypePath(Criteria criteria) { 471 GenericArrayLocationCriterion galc = 472 criteria.getGenericArrayLocation(); 473 return galc == null || galc.getLocation().isEmpty(); 474 } 475 476 public static boolean isOnReceiver(Criteria criteria) { 477 ASTPath astPath = criteria.getASTPath(); 478 if (astPath == null) { return criteria.isOnReceiver(); } 479 if (astPath.isEmpty()) { return false; } 480 ASTPath.ASTEntry entry = astPath.get(-1); 481 return entry.childSelectorIs(ASTPath.PARAMETER) 482 && entry.getArgument() < 0; 483 } 484 485 public static boolean isOnNew(Criteria criteria) { 486 ASTPath astPath = criteria.getASTPath(); 487 if (astPath == null || astPath.isEmpty()) { return criteria.isOnNew(); } 488 ASTPath.ASTEntry entry = astPath.get(-1); 489 Tree.Kind kind = entry.getTreeKind(); 490 return kind == Tree.Kind.NEW_ARRAY 491 && entry.childSelectorIs(ASTPath.TYPE) 492 && entry.getArgument() == 0 493 || kind == Tree.Kind.NEW_CLASS 494 && entry.childSelectorIs(ASTPath.IDENTIFIER); 495 } 496 497 private static boolean isOnNullaryConstructor(Criteria criteria) { 498 if (criteria.isOnMethod("<init>()V")) { 499 ASTPath astPath = criteria.getASTPath(); 500 if (astPath == null || astPath.isEmpty()) { 501 return !criteria.isOnNew(); // exclude expression annotations 502 } 503 ASTPath.ASTEntry entry = astPath.get(0); 504 return entry.getTreeKind() == Tree.Kind.METHOD 505 && (entry.childSelectorIs(ASTPath.TYPE) || isOnReceiver(criteria)); 506 } 507 return false; 508 } 509 510 /** 511 * Creates the {@link CastInsertion} and {@link CloseParenthesisInsertion} 512 * for a cast insertion. 513 * 514 * @param type the cast type to insert 515 * @param annotationString the annotation on the outermost type, or 516 * {@code null} if none. With no outermost annotation this cast 517 * insertion will either be a cast without any annotations or a cast 518 * with annotations only on the compound types. 519 * @param innerTypeInsertions the annotations on the inner types 520 * @param criteria the criteria for the location of this insertion 521 * @return the {@link CastInsertion} and {@link CloseParenthesisInsertion} 522 */ 523 private Pair<CastInsertion, CloseParenthesisInsertion> createCastInsertion( 524 Type type, String annotationString, List<Insertion> innerTypeInsertions, 525 Criteria criteria) { 526 if (annotationString != null) { 527 type.addAnnotation(annotationString); 528 } 529 Insertion.decorateType(innerTypeInsertions, type, criteria.getASTPath()); 530 CastInsertion cast = new CastInsertion(criteria, type); 531 CloseParenthesisInsertion closeParen = new CloseParenthesisInsertion( 532 criteria, cast.getSeparateLine()); 533 return new Pair<CastInsertion, CloseParenthesisInsertion>(cast, closeParen); 534 } 535 536 /** 537 * Fill in this.insertions with insertion pairs for the outer and inner types. 538 * @param clist the criteria specifying the location of the insertions 539 * @param typeElement holds the annotations to be inserted 540 */ 541 private void parseInnerAndOuterElements(CriterionList clist, ATypeElement typeElement) { 542 parseInnerAndOuterElements(clist, typeElement, false); 543 } 544 545 /** 546 * Fill in this.insertions with insertion pairs for the outer and inner types. 547 * @param clist the criteria specifying the location of the insertions 548 * @param typeElement holds the annotations to be inserted 549 * @param isCastInsertion {@code true} if this for a cast insertion, {@code false} 550 * otherwise. 551 */ 552 private void parseInnerAndOuterElements(CriterionList clist, 553 ATypeElement typeElement, boolean isCastInsertion) { 554 List<Insertion> innerInsertions = new ArrayList<Insertion>(); 555 for (Entry<InnerTypeLocation, ATypeElement> innerEntry: typeElement.innerTypes.entrySet()) { 556 InnerTypeLocation innerLoc = innerEntry.getKey(); 557 AElement innerElement = innerEntry.getValue(); 558 CriterionList innerClist = clist.add(Criteria.atLocation(innerLoc)); 559 innerInsertions.addAll(parseElement(innerClist, innerElement, isCastInsertion)); 560 } 561 CriterionList outerClist = clist; 562 if (!isCastInsertion) { 563 // Cast insertion is never on an existing type. 564 outerClist = clist.add(Criteria.atLocation()); 565 } 566 parseElement(outerClist, typeElement, innerInsertions); 567 } 568 569 // Returns a string representation of the annotations at the element. 570 private Set<Pair<String, Annotation>> 571 getElementAnnotations(AElement element) { 572 Set<Pair<String, Annotation>> result = 573 new LinkedHashSet<Pair<String, Annotation>>( 574 element.tlAnnotationsHere.size()); 575 for (Annotation a : element.tlAnnotationsHere) { 576 AnnotationDef adef = a.def; 577 String annotationString = "@" + adef.name; 578 579 if (a.fieldValues.size() == 1 && a.fieldValues.containsKey("value")) { 580 annotationString += "(" + formatFieldValue(a, "value") + ")"; 581 } else if (a.fieldValues.size() > 0) { 582 annotationString += "("; 583 boolean first = true; 584 for (String field : a.fieldValues.keySet()) { 585 // parameters of the annotation 586 if (!first) { 587 annotationString += ", "; 588 } 589 annotationString += field + "=" + formatFieldValue(a, field); 590 first = false; 591 } 592 annotationString += ")"; 593 } 594 // annotationString += " "; 595 result.add(new Pair<String, Annotation>(annotationString, a)); 596 } 597 return result; 598 } 599 600 private String formatFieldValue(Annotation a, String field) { 601 AnnotationFieldType fieldType = a.def.fieldTypes.get(field); 602 assert fieldType != null; 603 return fieldType.format(a.fieldValues.get(field)); 604 } 605 606 private void parseMethod(CriterionList clist, String className, String methodName, AMethod method) { 607 // Being "in" a method refers to being somewhere in the 608 // method's tree, which includes return types, parameters, receiver, and 609 // elements inside the method body. 610 clist = clist.add(Criteria.inMethod(methodName)); 611 612 // parse declaration annotations 613 parseElement(clist, method); 614 615 // parse receiver 616 CriterionList receiverClist = clist.add(Criteria.receiver(methodName)); 617 parseInnerAndOuterElements(receiverClist, method.receiver.type); 618 619 // parse return type 620 CriterionList returnClist = clist.add(Criteria.returnType(className, methodName)); 621 parseInnerAndOuterElements(returnClist, method.returnType); 622 623 // parse bounds of method 624 for (Entry<BoundLocation, ATypeElement> entry : method.bounds.entrySet()) { 625 BoundLocation boundLoc = entry.getKey(); 626 ATypeElement bound = entry.getValue(); 627 CriterionList boundClist = clist.add(Criteria.methodBound(methodName, boundLoc)); 628 parseInnerAndOuterElements(boundClist, bound); 629 } 630 631 // parse parameters of method 632 for (Entry<Integer, AField> entry : method.parameters.entrySet()) { 633 Integer index = entry.getKey(); 634 AField param = entry.getValue(); 635 CriterionList paramClist = clist.add(Criteria.param(methodName, index)); 636 // parse declaration annotations 637 // parseField(paramClist, index.toString(), param); 638 parseInnerAndOuterElements(paramClist, param.type); 639 } 640 641 // parse insert annotations/typecasts of method 642 parseASTInsertions(clist, method.insertAnnotations, method.insertTypecasts); 643 644 // parse body of method 645 parseBlock(clist, className, methodName, method.body); 646 } 647 648 private void parseBlock(CriterionList clist, String className, String methodName, ABlock block) { 649 // parse locals of method 650 for (Entry<LocalLocation, AField> entry : block.locals.entrySet()) { 651 LocalLocation loc = entry.getKey(); 652 AElement var = entry.getValue(); 653 CriterionList varClist = clist.add(Criteria.local(methodName, loc)); 654 // parse declaration annotations 655 parseElement(varClist, var); 656 parseInnerAndOuterElements(varClist, var.type); 657 } 658 659 parseExpression(clist, className, methodName, block); 660 } 661 662 private void parseExpression(CriterionList clist, String className, String methodName, AExpression exp) { 663 // parse typecasts of method 664 for (Entry<RelativeLocation, ATypeElement> entry : exp.typecasts.entrySet()) { 665 RelativeLocation loc = entry.getKey(); 666 ATypeElement cast = entry.getValue(); 667 CriterionList castClist = clist.add(Criteria.cast(methodName, loc)); 668 parseInnerAndOuterElements(castClist, cast); 669 } 670 671 // parse news (object creation) of method 672 for (Entry<RelativeLocation, ATypeElement> entry : exp.news.entrySet()) { 673 RelativeLocation loc = entry.getKey(); 674 ATypeElement newObject = entry.getValue(); 675 CriterionList newClist = clist.add(Criteria.newObject(methodName, loc)); 676 parseInnerAndOuterElements(newClist, newObject); 677 } 678 679 // parse instanceofs of method 680 for (Entry<RelativeLocation, ATypeElement> entry : exp.instanceofs.entrySet()) { 681 RelativeLocation loc = entry.getKey(); 682 ATypeElement instanceOf = entry.getValue(); 683 CriterionList instanceOfClist = clist.add(Criteria.instanceOf(methodName, loc)); 684 parseInnerAndOuterElements(instanceOfClist, instanceOf); 685 } 686 687 // parse member references of method 688 for (Entry<RelativeLocation, ATypeElement> entry : exp.refs.entrySet()) { 689 RelativeLocation loc = entry.getKey(); 690 ATypeElement ref = entry.getValue(); 691 CriterionList instanceOfClist = 692 clist.add(Criteria.memberReference(methodName, loc)); 693 parseInnerAndOuterElements(instanceOfClist, ref); 694 } 695 696 // parse method invocations of method 697 for (Entry<RelativeLocation, ATypeElement> entry : exp.calls.entrySet()) { 698 RelativeLocation loc = entry.getKey(); 699 ATypeElement call = entry.getValue(); 700 CriterionList instanceOfClist = 701 clist.add(Criteria.methodCall(methodName, loc)); 702 parseInnerAndOuterElements(instanceOfClist, call); 703 } 704 705 // parse lambda expressions of method 706 for (Entry<RelativeLocation, AMethod> entry : exp.funs.entrySet()) { 707 RelativeLocation loc = entry.getKey(); 708 AMethod lambda = entry.getValue(); 709 CriterionList lambdaClist = clist.add(Criteria.lambda(methodName, loc)); 710 parseLambdaExpression(className, methodName, lambda, lambdaClist); 711 } 712 } 713 714 private void parseLambdaExpression(String className, String methodName, 715 AMethod lambda, CriterionList clist) { 716 for (Entry<Integer, AField> entry : lambda.parameters.entrySet()) { 717 Integer index = entry.getKey(); 718 AField param = entry.getValue(); 719 CriterionList paramClist = clist.add(Criteria.param("(anonymous)", index)); 720 parseInnerAndOuterElements(paramClist, param.type); 721 parseASTInsertions(paramClist, param.insertAnnotations, param.insertTypecasts); 722 } 723 parseBlock(clist, className, methodName, lambda.body); 724 } 725 726 private void parseASTInsertions(CriterionList clist, 727 VivifyingMap<ASTPath, ATypeElement> insertAnnotations, 728 VivifyingMap<ASTPath, ATypeElementWithType> insertTypecasts) { 729 for (Entry<ASTPath, ATypeElement> entry : insertAnnotations.entrySet()) { 730 ASTPath astPath = entry.getKey(); 731 ATypeElement insertAnnotation = entry.getValue(); 732 CriterionList insertAnnotationClist = 733 clist.add(Criteria.astPath(astPath)); 734 parseInnerAndOuterElements(insertAnnotationClist, 735 insertAnnotation, true); 736 } 737 for (Entry<ASTPath, ATypeElementWithType> entry : insertTypecasts.entrySet()) { 738 ASTPath astPath = entry.getKey(); 739 ATypeElementWithType insertTypecast = entry.getValue(); 740 CriterionList insertTypecastClist = clist.add(Criteria.astPath(astPath)); 741 parseInnerAndOuterElements(insertTypecastClist, insertTypecast, true); 742 } 743 } 744 } 745