Home | History | Annotate | Download | only in specification
      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