1 package annotator.find;
2 
3 import java.io.IOException;
4 import java.util.ArrayList;
5 import java.util.Collection;
6 import java.util.HashMap;
7 import java.util.Iterator;
8 import java.util.List;
9 import java.util.Map;
10 import java.util.regex.Matcher;
11 import java.util.regex.Pattern;
12 
13 import javax.lang.model.element.ElementKind;
14 import javax.lang.model.element.Modifier;
15 import javax.lang.model.element.Name;
16 import javax.lang.model.type.NullType;
17 
18 import com.google.common.collect.LinkedHashMultimap;
19 import com.google.common.collect.Multimaps;
20 import com.google.common.collect.SetMultimap;
21 import com.sun.source.tree.AnnotatedTypeTree;
22 import com.sun.source.tree.AnnotationTree;
23 import com.sun.source.tree.ArrayTypeTree;
24 import com.sun.source.tree.ClassTree;
25 import com.sun.source.tree.CompilationUnitTree;
26 import com.sun.source.tree.ExpressionTree;
27 import com.sun.source.tree.IdentifierTree;
28 import com.sun.source.tree.InstanceOfTree;
29 import com.sun.source.tree.MemberSelectTree;
30 import com.sun.source.tree.MethodTree;
31 import com.sun.source.tree.ModifiersTree;
32 import com.sun.source.tree.NewArrayTree;
33 import com.sun.source.tree.NewClassTree;
34 import com.sun.source.tree.ParameterizedTypeTree;
35 import com.sun.source.tree.PrimitiveTypeTree;
36 import com.sun.source.tree.Tree;
37 import com.sun.source.tree.TypeCastTree;
38 import com.sun.source.tree.TypeParameterTree;
39 import com.sun.source.tree.VariableTree;
40 import com.sun.source.tree.WildcardTree;
41 import com.sun.source.util.TreePath;
42 import com.sun.source.util.TreeScanner;
43 
44 import com.sun.tools.javac.code.Flags;
45 import com.sun.tools.javac.code.Symbol;
46 import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntry;
47 import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntryKind;
48 import com.sun.tools.javac.code.Types;
49 import com.sun.tools.javac.tree.JCTree;
50 import com.sun.tools.javac.tree.JCTree.JCAnnotatedType;
51 import com.sun.tools.javac.tree.JCTree.JCAnnotation;
52 import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree;
53 import com.sun.tools.javac.tree.JCTree.JCBlock;
54 import com.sun.tools.javac.tree.JCTree.JCClassDecl;
55 import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
56 import com.sun.tools.javac.tree.JCTree.JCExpression;
57 import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
58 import com.sun.tools.javac.tree.JCTree.JCIdent;
59 import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
60 import com.sun.tools.javac.tree.JCTree.JCModifiers;
61 import com.sun.tools.javac.tree.JCTree.JCNewArray;
62 import com.sun.tools.javac.tree.JCTree.JCNewClass;
63 import com.sun.tools.javac.tree.JCTree.JCTypeApply;
64 import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
65 import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
66 import com.sun.tools.javac.tree.JCTree.JCWildcard;
67 import com.sun.tools.javac.util.Position;
68 
69 import annotations.io.ASTIndex;
70 import annotations.io.ASTPath;
71 import annotations.io.ASTRecord;
72 import annotations.io.DebugWriter;
73 import annotator.Main;
74 import annotator.scanner.CommonScanner;
75 import annotator.specification.IndexFileSpecification;
76 
77 import plume.Pair;
78 
79 import type.DeclaredType;
80 import type.Type;
81 
82 /**
83  * A {@link TreeScanner} that is able to locate program elements in an
84  * AST based on {@code Criteria}. {@link #getInsertionsByPosition(JCTree.JCCompilationUnit,List)}
85  * scans a tree and returns a
86  * mapping of source positions (as character offsets) to insertion text.
87  */
88 public class TreeFinder extends TreeScanner<Void, List<Insertion>> {
89   public static final DebugWriter dbug = new DebugWriter();
90   public static final DebugWriter stak = new DebugWriter();
91   public static final DebugWriter warn = new DebugWriter();
92 
93   /**
94    * String representation of regular expression matching a comment in
95    * Java code.  The part before {@code |} matches a single-line
96    * comment, and the part after matches a multi-line comment, which
97    * breaks down as follows (adapted from
98    * <a href="http://perldoc.perl.org/perlfaq6.html#How-do-I-use-a-regular-expression-to-strip-C-style-comments-from-a-file%3f">Perl FAQ</a>):
99    * <pre>
100    *          /\*         ##  Start of comment
101    *          [^*]*\*+    ##  Non-* followed by 1-or-more *s
102    *          (
103    *              [^/*][^*]*\*+
104    *          )*          ##  0 or more things which don't start with /
105    *                      ##    but do end with '*'
106    *          /           ##  End of comment
107    * </pre>
108    * Note: Care must be taken to avoid false comment matches starting
109    * inside a string literal.  Ensuring that the code segment being
110    * matched starts at an AST node boundary is sufficient to prevent
111    * this complication.
112    */
113   private final static String comment =
114       "//.*$|/\\*[^*]*+\\*++(?:[^*/][^*]*+\\*++)*+/";
115 
116   /**
117    * Regular expression matching a character or string literal.
118    */
119   private final static String literal =
120       "'(?:(?:\\\\(?:'|[^']*+))|[^\\\\'])'|\"(?:\\\\.|[^\\\\\"])*\"";
121 
122   /**
123    * Regular expression matching a non-commented instance of {@code /}
124    * that is not part of a comment-starting delimiter.
125    */
126   private final static String nonDelimSlash = "/(?=[^*/])";
127 
128   /**
129    * Returns regular expression matching "anything but" {@code c}: a
130    * single comment, character or string literal, or non-{@code c}
131    * character.
132    */
otherThan(char c)133   private final static String otherThan(char c) {
134     String cEscaped;
135 
136     // escape if necessary for use in character class
137     switch (c) {
138     case '/':
139     case '"':
140     case '\'':
141       cEscaped = ""; break;  // already present in class defn
142     case '\\':
143     case '[':
144     case ']':
145       cEscaped = "\\" + c; break;  // escape!
146     default:
147       cEscaped = "" + c;
148     }
149 
150     return "[^/'" + cEscaped + "\"]|" + "|" + literal + "|" + comment
151         + (c == '/' ? "" : nonDelimSlash);
152   }
153 
154   // If this code location is not an array type, return null.  Otherwise,
155   // starting at an array type, walk up the AST as long as still an array,
156   // and stop at the largest containing array (with nothing but arrays in
157   // between).
largestContainingArray(TreePath p)158   public static TreePath largestContainingArray(TreePath p) {
159     if (p.getLeaf().getKind() != Tree.Kind.ARRAY_TYPE) {
160       return null;
161     }
162     while (p.getParentPath().getLeaf().getKind() == Tree.Kind.ARRAY_TYPE) {
163       p = p.getParentPath();
164     }
165     assert p.getLeaf().getKind() == Tree.Kind.ARRAY_TYPE;
166     return p;
167   }
168 
169   /**
170    * Returns the position of the first (non-commented) instance of a
171    * character at or after the given position.  (Assumes position is not
172    * inside a comment.)
173    *
174    * @see #getNthInstanceBetween(char, int, int, int, CompilationUnitTree)
175    */
getFirstInstanceAfter(char c, int i)176   private int getFirstInstanceAfter(char c, int i) {
177     return getNthInstanceInRange(c, i, Integer.MAX_VALUE, 1);
178   }
179 
180   /**
181    * Returns the position of the {@code n}th (non-commented, non-quoted)
182    * instance of a character between the given positions, or the last
183    * instance if {@code n==0}.  (Assumes position is not inside a
184    * comment.)
185    *
186    * @param c the character being sought
187    * @param start position at which the search starts (inclusive)
188    * @param end position at which the search ends (exclusive)
189    * @param n number of repetitions, or 0 for last occurrence
190    * @return position of match in {@code tree}, or
191    *          {@link Position.NOPOS} if match not found
192    */
getNthInstanceInRange(char c, int start, int end, int n)193   private int getNthInstanceInRange(char c, int start, int end, int n) {
194     if (end < 0) {
195       throw new IllegalArgumentException("negative end position");
196     }
197     if (n < 0) {
198       throw new IllegalArgumentException("negative count");
199     }
200 
201     try {
202       CharSequence s = tree.getSourceFile().getCharContent(true);
203       int count = n;
204       int pos = Position.NOPOS;
205       int stop = Math.min(end, s.length());
206       String cQuoted = c == '/' ? nonDelimSlash : Pattern.quote("" + c);
207       String regex = "(?:" + otherThan(c) + ")*+" + cQuoted;
208       Pattern p = Pattern.compile(regex, Pattern.MULTILINE);
209       Matcher m = p.matcher(s).region(start, stop);
210 
211       // using n==0 for "last" ensures that {@code (--n == 0)} is always
212       // false, (reasonably) assuming no underflow
213       while (m.find()) {
214         pos = m.end() - 1;
215         if (--count == 0) { break; }
216       }
217       // positive count means search halted before nth instance was found
218       return count > 0 ? Position.NOPOS : pos;
219     } catch (IOException e) {
220       throw new RuntimeException(e);
221     }
222   }
223 
224   // Find a node's parent in the current source tree.
parent(Tree node)225   private Tree parent(Tree node) {
226     return getPath(node).getParentPath().getLeaf();
227   }
228 
229   /**
230    * An alternative to TreePath.getPath(CompilationUnitTree,Tree) that
231    * caches its results.
232    */
getPath(Tree target)233   public TreePath getPath(Tree target) {
234     if (treePathCache.containsKey(target)) {
235       return treePathCache.get(target);
236     }
237     TreePath result = TreePath.getPath(tree, target);
238     treePathCache.put(target, result);
239     return result;
240   }
241 
242   Map<Tree, TreePath> treePathCache = new HashMap<Tree, TreePath>();
243 
astRecord(Tree node)244   private ASTRecord astRecord(Tree node) {
245     Map<Tree, ASTRecord> index = ASTIndex.indexOf(tree);
246     return index.get(node);
247   }
248 
249   /**
250    * Determines the insertion position for type annotations on various
251    * elements.  For instance, type annotations for a declaration should be
252    * placed before the type rather than the variable name.
253    */
254   private class TypePositionFinder
255   extends TreeScanner<Pair<ASTRecord, Integer>, Insertion> {
pathAndPos(JCTree t)256     private Pair<ASTRecord, Integer> pathAndPos(JCTree t) {
257       return Pair.of(astRecord(t), t.pos);
258     }
259 
pathAndPos(JCTree t, int i)260     private Pair<ASTRecord, Integer> pathAndPos(JCTree t, int i) {
261       return Pair.of(astRecord(t), i);
262     }
263 
264     /** @param t an expression for a type */
getBaseTypePosition(JCTree t)265     private Pair<ASTRecord, Integer> getBaseTypePosition(JCTree t) {
266       while (true) {
267         switch (t.getKind()) {
268         case IDENTIFIER:
269         case PRIMITIVE_TYPE:
270           return pathAndPos(t);
271         case MEMBER_SELECT:
272           JCTree exp = t;
273           do {  // locate pkg name, if any
274             JCFieldAccess jfa = (JCFieldAccess) exp;
275             exp = jfa.getExpression();
276             if (jfa.sym.isStatic()) {
277               return pathAndPos(exp,
278                   getFirstInstanceAfter('.',
279                       exp.getEndPosition(tree.endPositions)) + 1);
280             }
281           } while (exp instanceof JCFieldAccess
282               && ((JCFieldAccess) exp).sym.getKind() != ElementKind.PACKAGE);
283           if (exp != null) {
284             if (exp.getKind() == Tree.Kind.IDENTIFIER) {
285               Symbol sym = ((JCIdent) exp).sym;
286               if (!(sym.isStatic() || sym.getKind() == ElementKind.PACKAGE)) {
287                 return pathAndPos(t, t.getStartPosition());
288               }
289             }
290             t = exp;
291           }
292           return pathAndPos(t,
293               getFirstInstanceAfter('.',
294                   t.getEndPosition(tree.endPositions)) + 1);
295         case ARRAY_TYPE:
296           t = ((JCArrayTypeTree) t).elemtype;
297           break;
298         case PARAMETERIZED_TYPE:
299           return pathAndPos(t, t.getStartPosition());
300         case EXTENDS_WILDCARD:
301         case SUPER_WILDCARD:
302           t = ((JCWildcard) t).inner;
303           break;
304         case UNBOUNDED_WILDCARD:
305           // This is "?" as in "List<?>".  ((JCWildcard) t).inner is null.
306           // There is nowhere to attach the annotation, so for now return
307           // the "?" tree itself.
308           return pathAndPos(t);
309         case ANNOTATED_TYPE:
310           // If this type already has annotations on it, get the underlying
311           // type, without annotations.
312           t = ((JCAnnotatedType) t).underlyingType;
313           break;
314         default:
315           throw new RuntimeException(String.format("Unrecognized type (kind=%s, class=%s): %s", t.getKind(), t.getClass(), t));
316         }
317       }
318     }
319 
320     @Override
visitVariable(VariableTree node, Insertion ins)321     public Pair<ASTRecord, Integer> visitVariable(VariableTree node, Insertion ins) {
322       Name name = node.getName();
323       JCVariableDecl jn = (JCVariableDecl) node;
324       JCTree jt = jn.getType();
325       Criteria criteria = ins.getCriteria();
326       dbug.debug("visitVariable: %s %s%n", jt, jt.getClass());
327       if (name != null && criteria.isOnFieldDeclaration()) {
328         return Pair.of(astRecord(node), jn.getStartPosition());
329       }
330       if (jt instanceof JCTypeApply) {
331         JCExpression type = ((JCTypeApply) jt).clazz;
332         return pathAndPos(type);
333       }
334       return Pair.of(astRecord(node), jn.pos);
335     }
336 
337     // When a method is visited, it is visited for the receiver, not the
338     // return value and not the declaration itself.
339     @Override
visitMethod(MethodTree node, Insertion ins)340     public Pair<ASTRecord, Integer> visitMethod(MethodTree node, Insertion ins) {
341       dbug.debug("TypePositionFinder.visitMethod%n");
342       super.visitMethod(node, ins);
343 
344       JCMethodDecl jcnode = (JCMethodDecl) node;
345       JCVariableDecl jcvar = (JCVariableDecl) node.getReceiverParameter();
346       if (jcvar != null) { return pathAndPos(jcvar); }
347 
348       int pos = Position.NOPOS;
349       ASTRecord astPath = astRecord(jcnode)
350           .extend(Tree.Kind.METHOD, ASTPath.PARAMETER, -1);
351 
352       if (node.getParameters().isEmpty()) {
353         // no parameters; find first (uncommented) '(' after method name
354         pos = findMethodName(jcnode);
355         if (pos >= 0) { pos = getFirstInstanceAfter('(', pos); }
356         if (++pos <= 0) {
357           throw new RuntimeException("Couldn't find param opening paren for: "
358               + jcnode);
359         }
360       } else {
361         pos = ((JCTree) node.getParameters().get(0)).getStartPosition();
362       }
363       return Pair.of(astPath, pos);
364     }
365 
366     @Override
visitIdentifier(IdentifierTree node, Insertion ins)367     public Pair<ASTRecord, Integer> visitIdentifier(IdentifierTree node, Insertion ins) {
368       dbug.debug("TypePositionFinder.visitIdentifier(%s)%n", node);
369       // for arrays, need to indent inside array, not right before type
370       ASTRecord rec = ASTIndex.indexOf(tree).get(node);
371       ASTPath astPath = ins.getCriteria().getASTPath();
372       Tree parent = parent(node);
373       Integer i = null;
374       JCIdent jcnode = (JCIdent) node;
375 
376       // ASTPathEntry.type _n_ is a special case because it does not
377       // correspond to a node in the AST.
378       if (parent.getKind() == Tree.Kind.NEW_ARRAY) { // NewArrayTree)
379         ASTPath.ASTEntry entry;
380         dbug.debug("TypePositionFinder.visitIdentifier: recognized array%n");
381         if (astPath == null) {
382           entry = new ASTPath.ASTEntry(Tree.Kind.NEW_ARRAY, ASTPath.TYPE, 0);
383           astPath = astRecord(parent).extend(entry).astPath;
384         } else {
385           entry = astPath.get(astPath.size() - 1);  // kind is NewArray
386         }
387         if (entry.childSelectorIs(ASTPath.TYPE)) {
388           int n = entry.getArgument();
389           i = jcnode.getStartPosition();
390           if (n < getDimsSize((JCExpression) parent)) {  // else n == #dims
391             i = getNthInstanceInRange('[', i,
392                 ((JCNewArray) parent).getEndPosition(tree.endPositions), n+1);
393           }
394         }
395         if (i == null) {
396           i = jcnode.getEndPosition(tree.endPositions);
397         }
398       } else if (parent.getKind() == Tree.Kind.NEW_CLASS) { // NewClassTree)
399         dbug.debug("TypePositionFinder.visitIdentifier: recognized class%n");
400         JCNewClass nc = (JCNewClass) parent;
401         dbug.debug(
402             "TypePositionFinder.visitIdentifier: clazz %s (%d) constructor %s%n",
403             nc.clazz, nc.clazz.getPreferredPosition(), nc.constructor);
404         i = nc.clazz.getPreferredPosition();
405         if (astPath == null) {
406           astPath = astRecord(node).astPath;
407         }
408       } else {
409         ASTRecord astRecord = astRecord(node);
410         astPath = astRecord.astPath;
411         i = ((JCIdent) node).pos;
412       }
413 
414       dbug.debug("visitIdentifier(%s) => %d where parent (%s) = %s%n",
415           node, i, parent.getClass(), parent);
416       return Pair.of(rec.replacePath(astPath), i);
417     }
418 
419     @Override
visitMemberSelect(MemberSelectTree node, Insertion ins)420     public Pair<ASTRecord, Integer> visitMemberSelect(MemberSelectTree node, Insertion ins) {
421       dbug.debug("TypePositionFinder.visitMemberSelect(%s)%n", node);
422       JCFieldAccess raw = (JCFieldAccess) node;
423       return Pair.of(astRecord(node),
424           raw.getEndPosition(tree.endPositions) - raw.name.length());
425     }
426 
427     @Override
visitTypeParameter(TypeParameterTree node, Insertion ins)428     public Pair<ASTRecord, Integer> visitTypeParameter(TypeParameterTree node, Insertion ins) {
429       JCTypeParameter tp = (JCTypeParameter) node;
430       return Pair.of(astRecord(node), tp.getStartPosition());
431     }
432 
433     @Override
visitWildcard(WildcardTree node, Insertion ins)434     public Pair<ASTRecord, Integer> visitWildcard(WildcardTree node, Insertion ins) {
435       JCWildcard wc = (JCWildcard) node;
436       return Pair.of(astRecord(node), wc.getStartPosition());
437     }
438 
439     @Override
visitPrimitiveType(PrimitiveTypeTree node, Insertion ins)440     public Pair<ASTRecord, Integer> visitPrimitiveType(PrimitiveTypeTree node, Insertion ins) {
441       dbug.debug("TypePositionFinder.visitPrimitiveType(%s)%n", node);
442       return pathAndPos((JCTree) node);
443     }
444 
445     @Override
visitParameterizedType(ParameterizedTypeTree node, Insertion ins)446     public Pair<ASTRecord, Integer> visitParameterizedType(ParameterizedTypeTree node, Insertion ins) {
447       Tree parent = parent(node);
448       dbug.debug("TypePositionFinder.visitParameterizedType %s parent=%s%n",
449           node, parent);
450       Integer pos = getBaseTypePosition(((JCTypeApply) node).getType()).b;
451       return Pair.of(astRecord(node), pos);
452     }
453 
454     /**
455      * Returns the number of array levels that are in the given array type tree,
456      * or 0 if the given node is not an array type tree.
457      */
arrayLevels(com.sun.tools.javac.code.Type t)458     private int arrayLevels(com.sun.tools.javac.code.Type t) {
459       return t.accept(new Types.SimpleVisitor<Integer, Integer>() {
460         @Override
461         public Integer visitArrayType(com.sun.tools.javac.code.Type.ArrayType t,
462             Integer i) {
463           return t.elemtype.accept(this, i+1);
464         }
465         @Override
466         public Integer visitType(com.sun.tools.javac.code.Type t, Integer i) {
467           return i;
468         }
469       }, 0);
470     }
471 
arrayLevels(Tree node)472     private int arrayLevels(Tree node) {
473       int result = 0;
474       while (node.getKind() == Tree.Kind.ARRAY_TYPE) {
475         result++;
476         node = ((ArrayTypeTree) node).getType();
477       }
478       return result;
479     }
480 
arrayContentType(JCArrayTypeTree att)481     private JCTree arrayContentType(JCArrayTypeTree att) {
482       JCTree node = att;
483       do {
484         node = ((JCArrayTypeTree) node).getType();
485       } while (node.getKind() == Tree.Kind.ARRAY_TYPE);
486       return node;
487     }
488 
largestContainingArray(Tree node)489     private ArrayTypeTree largestContainingArray(Tree node) {
490       TreePath p = getPath(node);
491       Tree result = TreeFinder.largestContainingArray(p).getLeaf();
492       assert result.getKind() == Tree.Kind.ARRAY_TYPE;
493       return (ArrayTypeTree) result;
494     }
495 
496     @Override
visitArrayType(ArrayTypeTree node, Insertion ins)497     public Pair<ASTRecord, Integer> visitArrayType(ArrayTypeTree node, Insertion ins) {
498       dbug.debug("TypePositionFinder.visitArrayType(%s)%n", node);
499       JCArrayTypeTree att = (JCArrayTypeTree) node;
500       dbug.debug("TypePositionFinder.visitArrayType(%s) preferred = %s%n",
501           node, att.getPreferredPosition());
502       // If the code has a type like "String[][][]", then this gets called
503       // three times:  for String[][][], String[][], and String[]
504       // respectively.  For each of the three, call String[][][] "largest".
505       ArrayTypeTree largest = largestContainingArray(node);
506       int largestLevels = arrayLevels(largest);
507       int levels = arrayLevels(node);
508       int start = arrayContentType(att).getPreferredPosition() + 1;
509       int end = att.getEndPosition(tree.endPositions);
510       int pos = arrayInsertPos(start, end);
511 
512       dbug.debug("  levels=%d largestLevels=%d%n", levels, largestLevels);
513       for (int i=levels; i<largestLevels; i++) {
514         pos = getFirstInstanceAfter('[', pos+1);
515         dbug.debug("  pos %d at i=%d%n", pos, i);
516       }
517       return Pair.of(astRecord(node), pos);
518     }
519 
520     /**
521      * Find position in source code where annotation is to be inserted.
522      *
523      * @param start beginning of range to be matched
524      * @param end end of range to be matched
525      *
526      * @return position for annotation insertion
527      */
arrayInsertPos(int start, int end)528     private int arrayInsertPos(int start, int end) {
529       try {
530         CharSequence s = tree.getSourceFile().getCharContent(true);
531         int pos = getNthInstanceInRange('[', start, end, 1);
532 
533         if (pos < 0) {
534           // no "[", so check for "..."
535           String nonDot = otherThan('.');
536           String regex = "(?:(?:\\.\\.?)?" + nonDot + ")*(\\.\\.\\.)";
537           Pattern p = Pattern.compile(regex, Pattern.MULTILINE);
538           Matcher m = p.matcher(s).region(start, end);
539 
540           if (m.find()) {
541             pos = m.start(1);
542           }
543           if (pos < 0) {  // should never happen
544             throw new RuntimeException("no \"[\" or \"...\" in array type");
545           }
546         }
547         return pos;
548       } catch (IOException e) {
549         throw new RuntimeException(e);
550       }
551     }
552 
553     @Override
visitCompilationUnit(CompilationUnitTree node, Insertion ins)554     public Pair<ASTRecord, Integer> visitCompilationUnit(CompilationUnitTree node, Insertion ins) {
555       dbug.debug("TypePositionFinder.visitCompilationUnit%n");
556       JCCompilationUnit cu = (JCCompilationUnit) node;
557       return Pair.of(astRecord(node), cu.getStartPosition());
558     }
559 
560     @Override
visitClass(ClassTree node, Insertion ins)561     public Pair<ASTRecord, Integer> visitClass(ClassTree node, Insertion ins) {
562       dbug.debug("TypePositionFinder.visitClass%n");
563       JCClassDecl cd = (JCClassDecl) node;
564       JCTree t = cd.mods == null ? cd : cd.mods;
565       return Pair.of(astRecord(cd), t.getPreferredPosition());
566     }
567 
568     // There are three types of array initializers:
569     //   /*style 1*/ String[] names1 = new String[12];
570     //   /*style 2*/ String[] names2 = { "Alice", "Bob" };
571     //   /*style 3*/ String[] names3 = new String[] { "Alice", "Bob" };
572     // (Can the styles be combined?)
573     //
574     // For style 1, we can just find the location of the
575     // dimensionality expression, and then locate the bracket before it.
576     // For style 2, annotations are impossible.
577     // For style 3, we need to count the brackets and get to the right one.
578     //
579     // The AST depth of the initializer is correct unless all arrays are
580     // empty, in which case it is arbitary.  This is legal:
581     // String[][][][][] names4 = new String[][][][][] { { { } } };
582     //
583     // Array initializers can also be multi-dimensional, but this is not
584     // relevant to us:
585     //   int[][] pascalsTriangle = { { 1 }, { 1,1 }, { 1,2,1 } };
586     //   int[][] pascalsTriangle = new int[][] { { 1 }, { 1,1 }, { 1,2,1 } };
587 
588     // structure stolen from javac's Pretty.java
getDimsSize(JCExpression tree)589     private int getDimsSize(JCExpression tree) {
590       if (tree instanceof JCNewArray) {
591         JCNewArray na = (JCNewArray) tree;
592         if (na.dims.size() != 0) {
593           // when not all dims are given, na.dims.size() gives wrong answer
594           return arrayLevels(na.type);
595         }
596         if (na.elemtype != null) {
597           return getDimsSize(na.elemtype) + 1;
598         }
599         assert na.elems != null;
600         int maxDimsSize = 0;
601         for (JCExpression elem : na.elems) {
602           if (elem instanceof JCNewArray) {
603             int elemDimsSize = getDimsSize((JCNewArray)elem);
604             maxDimsSize = Math.max(maxDimsSize, elemDimsSize);
605           } else if (elem instanceof JCArrayTypeTree) {
606             // Does this ever happen?  javac's Pretty.java handles it.
607             System.out.printf("JCArrayTypeTree: %s%n", elem);
608           }
609         }
610         return maxDimsSize + 1;
611       } else if (tree instanceof JCAnnotatedType) {
612         return getDimsSize(((JCAnnotatedType) tree).underlyingType);
613       } else if (tree instanceof JCArrayTypeTree) {
614         return 1 + getDimsSize(((JCArrayTypeTree) tree).elemtype);
615       } else {
616         return 0;
617       }
618     }
619 
620 
621     // Visit an expression of one of these forms:
622     //   new int[5][10]
623     //   new int[][] {...}
624     //   { ... }            -- as in: String[] names2 = { "Alice", "Bob" };
625     @Override
visitNewArray(NewArrayTree node, Insertion ins)626     public Pair<ASTRecord, Integer> visitNewArray(NewArrayTree node, Insertion ins) {
627       dbug.debug("TypePositionFinder.visitNewArray%n");
628       JCNewArray na = (JCNewArray) node;
629       GenericArrayLocationCriterion galc =
630           ins.getCriteria().getGenericArrayLocation();
631       ASTRecord rec = ASTIndex.indexOf(tree).get(node);
632       ASTPath astPath = ins.getCriteria().getASTPath();
633       String childSelector = null;
634       // Invariant:  na.dims.size() == 0  or  na.elems == null  (but not both)
635       // If na.dims.size() != 0, na.elemtype is non-null.
636       // If na.dims.size() == 0, na.elemtype may be null or non-null.
637       int dimsSize = getDimsSize(na);
638       int dim = galc == null ? 0 : galc.getLocation().size();
639 
640       if (astPath == null) {
641         astPath = astRecord(node).astPath.extendNewArray(dim);
642         childSelector = ASTPath.TYPE;
643       } else {
644         ASTPath.ASTEntry lastEntry = null;
645         int n = astPath.size();
646         int i = n;
647         // find matching node = last path entry w/kind NEW_ARRAY
648         while (--i >= 0) {
649           lastEntry = astPath.get(i);
650           if (lastEntry.getTreeKind() == Tree.Kind.NEW_ARRAY) { break; }
651         }
652         assert i >= 0 : "no matching path entry (kind=NEW_ARRAY)";
653         if (n > i+1) {
654           // find correct node further down and visit if present
655           assert dim + 1 == dimsSize;
656           Tree typeTree = na.elemtype;
657           int j = i + dim + 1;
658           while (--dim >= 0) {
659             typeTree = ((ArrayTypeTree) typeTree).getType();
660           }
661 loop:
662           while (j < n) {
663             ASTPath.ASTEntry entry = astPath.get(j);
664             switch (entry.getTreeKind()) {
665             case ANNOTATED_TYPE:
666               typeTree = ((AnnotatedTypeTree) typeTree).getUnderlyingType();
667               continue;  // no increment
668             case ARRAY_TYPE:
669               typeTree = ((ArrayTypeTree) typeTree).getType();
670               break;
671             case MEMBER_SELECT:
672               if (typeTree instanceof JCTree.JCFieldAccess) {
673                 JCTree.JCFieldAccess jfa = (JCTree.JCFieldAccess) typeTree;
674                 typeTree = jfa.getExpression();
675                 // if just a qualifier, don't increment loop counter
676                 if (jfa.sym.getKind() == ElementKind.PACKAGE) { continue; }
677                 break;
678               }
679               break loop;
680             case PARAMETERIZED_TYPE:
681               if (entry.childSelectorIs(ASTPath.TYPE_ARGUMENT)) {
682                 int arg = entry.getArgument();
683                 List<? extends Tree> typeArgs =
684                     ((ParameterizedTypeTree) typeTree).getTypeArguments();
685                 typeTree = typeArgs.get(arg);
686               } else {  // ASTPath.TYPE
687                 typeTree = ((ParameterizedTypeTree) typeTree).getType();
688               }
689               break;
690             default:
691               break loop;
692             }
693             ++j;
694           }
695           if (j < n) {
696             // sought node is absent, so return default; insertion can
697             // be applied only as an inner of some TypedInsertion anyway
698             return getBaseTypePosition(na);
699           }
700           return typeTree.accept(this, ins);
701         }
702 
703         childSelector = lastEntry.getChildSelector();
704         if (dim > 0 && ASTPath.TYPE.equals(childSelector)) {
705           // rebuild path with current value of dim
706           ASTPath newPath = ASTPath.empty();
707           int j = 0;
708           dim += lastEntry.getArgument();
709           while (j < i) {  // [0,i)
710             newPath = newPath.extend(astPath.get(j));
711             j++;
712           }
713           lastEntry = new ASTPath.ASTEntry(Tree.Kind.NEW_ARRAY,
714               ASTPath.TYPE, dim);  // i
715           newPath = newPath.extend(lastEntry);
716           while (j < n) {  // [i,n)
717             newPath = newPath.extend(astPath.get(j));
718             j++;
719           }
720           astPath = newPath;
721         } else {
722           dim = lastEntry.getArgument();
723         }
724       }
725 
726       if (ASTPath.TYPE.equals(childSelector)) {
727       if (na.toString().startsWith("{")) {
728         if (ins.getKind() == Insertion.Kind.ANNOTATION) {
729           TreePath parentPath = TreePath.getPath(tree, na).getParentPath();
730           if (parentPath != null) {
731             Tree parent = parentPath.getLeaf();
732             if (parent.getKind() == Tree.Kind.VARIABLE) {
733               AnnotationInsertion ai = (AnnotationInsertion) ins;
734               JCTree typeTree = ((JCVariableDecl) parent).getType();
735               ai.setType(typeTree.toString());
736               return Pair.of(rec.replacePath(astPath), na.getStartPosition());
737             }
738           }
739           System.err.println("WARNING: array initializer " + node +
740               " has no explicit type; skipping insertion " + ins);
741           return null;
742         } else {
743           return Pair.of(rec.replacePath(astPath), na.getStartPosition());
744         }
745       }
746       if (dim == dimsSize) {
747         if (na.elemtype == null) {
748           System.err.println("WARNING: array initializer " + node +
749               " has no explicit type; skipping insertion " + ins);
750           return null;
751         }
752         return getBaseTypePosition(na.elemtype);
753       }
754       if (na.dims.size() != 0) {
755         int startPos = na.getStartPosition();
756         int endPos = na.getEndPosition(tree.endPositions);
757         int pos = getNthInstanceInRange('[', startPos, endPos, dim + 1);
758         return Pair.of(rec.replacePath(astPath), pos);
759       }
760       // In a situation like
761       //   node=new String[][][][][]{{{}}}
762       // Also see Pretty.printBrackets.
763       if (dim == 0) {
764         if (na.elemtype == null) {
765           return Pair.of(rec.replacePath(astPath), na.getStartPosition());
766         }
767         // na.elemtype.getPreferredPosition(); seems to be at the end,
768         //  after the brackets.
769         // na.elemtype.getStartPosition(); is before the type name itself.
770         int startPos = na.elemtype.getStartPosition();
771         return Pair.of(rec.replacePath(astPath),
772             getFirstInstanceAfter('[', startPos+1));
773       } else if (dim == dimsSize) {
774         return Pair.of(rec.replacePath(astPath),
775             na.getType().pos().getStartPosition());
776       } else {
777         JCArrayTypeTree jcatt = (JCArrayTypeTree) na.elemtype;
778         for (int i=1; i<dim; i++) {
779           JCTree elem = jcatt.elemtype;
780           if (elem.hasTag(JCTree.Tag.ANNOTATED_TYPE)) {
781             elem = ((JCAnnotatedType) elem).underlyingType;
782           }
783           assert elem.hasTag(JCTree.Tag.TYPEARRAY);
784           jcatt = (JCArrayTypeTree) elem;
785         }
786         return Pair.of(rec.replacePath(astPath),
787             jcatt.pos().getPreferredPosition());
788       }
789       } else if (ASTPath.DIMENSION.equals(childSelector)) {
790         List<JCExpression> inits = na.getInitializers();
791         if (dim < inits.size()) {
792           JCExpression expr = inits.get(dim);
793           return Pair.of(astRecord(expr), expr.getStartPosition());
794         }
795         return null;
796       } else if (ASTPath.INITIALIZER.equals(childSelector)) {
797         JCExpression expr = na.getDimensions().get(dim);
798         return Pair.of(astRecord(expr), expr.getStartPosition());
799       } else {
800         assert false : "Unexpected child selector in AST path: "
801             + (childSelector == null ? "null" : "\"" + childSelector + "\"");
802         return null;
803       }
804     }
805 
806     @Override
visitNewClass(NewClassTree node, Insertion ins)807     public Pair<ASTRecord, Integer> visitNewClass(NewClassTree node, Insertion ins) {
808       JCNewClass na = (JCNewClass) node;
809       JCExpression className = na.clazz;
810       // System.out.printf("classname %s (%s)%n", className, className.getClass());
811       while (! (className.getKind() == Tree.Kind.IDENTIFIER)) { // IdentifierTree
812         if (className instanceof JCAnnotatedType) {
813           className = ((JCAnnotatedType) className).underlyingType;
814         } else if (className instanceof JCTypeApply) {
815           className = ((JCTypeApply) className).clazz;
816         } else if (className instanceof JCFieldAccess) {
817           // This occurs for fully qualified names, e.g. "new java.lang.Object()".
818           // I'm not quite sure why the field "selected" is taken, but "name" would
819           // be a type mismatch. It seems to work, see NewPackage test case.
820           className = ((JCFieldAccess) className).selected;
821         } else {
822           throw new Error(String.format("unrecognized JCNewClass.clazz (%s): %s%n" +
823                   "   surrounding new class tree: %s%n", className.getClass(), className, node));
824         }
825         // System.out.printf("classname %s (%s)%n", className, className.getClass());
826       }
827 
828       return visitIdentifier((IdentifierTree) className, ins);
829     }
830   }
831 
832   /**
833    * Determine the insertion position for declaration annotations on
834    * various elements.  For instance, method declaration annotations should
835    * be placed before all the other modifiers and annotations.
836    */
837   private class DeclarationPositionFinder extends TreeScanner<Integer, Void> {
838 
839     // When a method is visited, it is visited for the declaration itself.
840     @Override
841     public Integer visitMethod(MethodTree node, Void p) {
842       super.visitMethod(node, p);
843 
844       // System.out.printf("DeclarationPositionFinder.visitMethod()%n");
845 
846       ModifiersTree mt = node.getModifiers();
847 
848       // actually List<JCAnnotation>.
849       List<? extends AnnotationTree> annos = mt.getAnnotations();
850       // Set<Modifier> flags = mt.getFlags();
851 
852       JCTree before;
853       if (annos.size() > 1) {
854         before = (JCAnnotation) annos.get(0);
855       } else if (node.getReturnType() != null) {
856         before = (JCTree) node.getReturnType();
857       } else {
858         // if we're a constructor, we have null return type, so we use the constructor's position
859         // rather than the return type's position
860         before = (JCTree) node;
861       }
862       int declPos = before.getStartPosition();
863 
864       // There is no source code location information for Modifiers, so
865       // cannot iterate through the modifiers.  But we don't have to.
866       int modsPos = ((JCModifiers)mt).pos().getStartPosition();
867       if (modsPos != Position.NOPOS) {
868         declPos = Math.min(declPos, modsPos);
869       }
870 
871       return declPos;
872     }
873 
874     @Override
875     public Integer visitCompilationUnit(CompilationUnitTree node, Void p) {
876       JCCompilationUnit cu = (JCCompilationUnit) node;
877       return cu.getStartPosition();
878     }
879 
880     @Override
881     public Integer visitClass(ClassTree node, Void p) {
882       JCClassDecl cd = (JCClassDecl) node;
883       int result = -1;
884       if (cd.mods != null
885           && (cd.mods.flags != 0 || cd.mods.annotations.size() > 0)) {
886         result = cd.mods.getPreferredPosition();
887       }
888       if (result < 0) {
889         result = cd.getPreferredPosition();
890       }
891       assert result >= 0 || cd.name.isEmpty()
892         : String.format("%d %d %d%n", cd.getStartPosition(),
893                         cd.getPreferredPosition(), cd.pos);
894       return result < 0 ? null : result;
895     }
896 
897   }
898 
899   private final TypePositionFinder tpf;
900   private final DeclarationPositionFinder dpf;
901   private final JCCompilationUnit tree;
902   private final SetMultimap<Pair<Integer, ASTPath>, Insertion> insertions;
903   private final SetMultimap<ASTRecord, Insertion> astInsertions;
904 
905   /**
906    * Creates a {@code TreeFinder} from a source tree.
907    *
908    * @param tree the source tree to search
909    */
910   public TreeFinder(JCCompilationUnit tree) {
911     this.tree = tree;
912     this.insertions = LinkedHashMultimap.create();
913     this.astInsertions = LinkedHashMultimap.create();
914     this.tpf = new TypePositionFinder();
915     this.dpf = new DeclarationPositionFinder();
916   }
917 
918   // which nodes are possible insertion sites
919   boolean handled(Tree node) {
920     switch (node.getKind()) {
921     case ANNOTATION:
922     case ARRAY_TYPE:
923     case CLASS:
924     case COMPILATION_UNIT:
925     case ENUM:
926     case EXPRESSION_STATEMENT:
927     case EXTENDS_WILDCARD:
928     case IDENTIFIER:
929     case INTERFACE:
930     case METHOD:
931     case NEW_ARRAY:
932     case NEW_CLASS:
933     case PARAMETERIZED_TYPE:
934     case PRIMITIVE_TYPE:
935     case SUPER_WILDCARD:
936     case TYPE_PARAMETER:
937     case UNBOUNDED_WILDCARD:
938     case VARIABLE:
939       return true;
940     default:
941       return node instanceof ExpressionTree;
942     }
943   }
944 
945   /**
946    * Determines if the last {@link TypePathEntry} in the given list is a
947    * {@link TypePathEntryKind#WILDCARD}.
948    *
949    * @param location the list to check
950    * @return {@code true} if the last {@link TypePathEntry} is a
951    *         {@link TypePathEntryKind#WILDCARD}, {@code false} otherwise.
952    */
953   private boolean wildcardLast(List<TypePathEntry> location) {
954     return location.get(location.size() - 1).tag == TypePathEntryKind.WILDCARD;
955   }
956 
957   /**
958    * Scans this tree, using the list of insertions to generate the source
959    * position to insertion text mapping.  Insertions are removed from the
960    * list when positions are found for them.
961    *
962    * @param node AST node being considered for annotation insertions
963    * @param p list of insertions not yet placed
964    * <p>
965    * When a match is found, this routine removes the insertion from p and
966    * adds it to the insertions map as a value, with a key that is a pair.
967    * On return, p contains only the insertions for which no match was found.
968    */
969   @Override
970   public Void scan(Tree node, List<Insertion> p) {
971     if (node == null) {
972       return null;
973     }
974 
975     dbug.debug("SCANNING: %s %s%n", node.getKind(), node);
976     if (! handled(node)) {
977       dbug.debug("Not handled, skipping (%s): %s%n", node.getClass(), node);
978       // nothing to do
979       return super.scan(node, p);
980     }
981 
982     TreePath path = getPath(node);
983     assert path == null || path.getLeaf() == node :
984       String.format("Mismatch: '%s' '%s' '%s'%n",
985           path, path.getLeaf(), node);
986 
987     // To avoid annotating existing annotations right before
988     // the element you wish to annotate, skip anything inside of
989     // an annotation.
990     if (path != null) {
991       for (Tree t : path) {
992         if (t.getKind() == Tree.Kind.PARAMETERIZED_TYPE) {
993           // We started with something within a parameterized type and
994           // should not look for any further annotations.
995           // TODO: does this work on multiple nested levels?
996           break;
997         }
998         if (t.getKind() == Tree.Kind.ANNOTATION) {
999           return super.scan(node, p);
1000         }
1001       }
1002     }
1003 
1004     for (Iterator<Insertion> it = p.iterator(); it.hasNext(); ) {
1005       Insertion i = it.next();
1006       if (i.getInserted()) {
1007         // Skip this insertion if it has already been inserted. See
1008         // the ReceiverInsertion class for details.
1009         it.remove();
1010         continue;
1011       }
1012       dbug.debug("Considering insertion at tree:%n");
1013       dbug.debug("  Insertion: %s%n", i);
1014       dbug.debug("  First line of node: %s%n", Main.firstLine(node.toString()));
1015       dbug.debug("  Type of node: %s%n", node.getClass());
1016       if (!i.getCriteria().isSatisfiedBy(path, node)) {
1017         dbug.debug("  ... not satisfied%n");
1018         continue;
1019       } else {
1020         dbug.debug("  ... satisfied!%n");
1021         dbug.debug("    First line of node: %s%n", Main.firstLine(node.toString()));
1022         dbug.debug("    Type of node: %s%n", node.getClass());
1023 
1024         ASTPath astPath = i.getCriteria().getASTPath();
1025         Integer pos = astPath == null ? findPosition(path, i)
1026             : Main.convert_jaifs ? null  // already in correct form
1027             : findPositionByASTPath(astPath, path, i);
1028         if (pos != null) {
1029           dbug.debug("  ... satisfied! at %d for node of type %s: %s%n",
1030               pos, node.getClass(), Main.treeToString(node));
1031           insertions.put(Pair.of(pos, astPath), i);
1032         }
1033       }
1034       it.remove();
1035     }
1036     return super.scan(node, p);
1037   }
1038 
1039   // Find insertion position for Insertion whose criteria matched the
1040   // given TreePath.
1041   // If no position is found, report an error and return null.
1042   Integer findPosition(TreePath path, Insertion i) {
1043     Tree node = path.getLeaf();
1044     try {
1045       // As per the JSR308 specification, receiver parameters are not allowed
1046       // on method declarations of anonymous inner classes.
1047       if (i.getCriteria().isOnReceiver()
1048           && path.getParentPath().getParentPath().getLeaf().getKind() == Tree.Kind.NEW_CLASS) {
1049         warn.debug("WARNING: Cannot insert a receiver parameter "
1050             + "on a method declaration of an anonymous inner class.  "
1051             + "This insertion will be skipped.%n    Insertion: %s%n", i);
1052         return null;
1053       }
1054 
1055       // TODO: Find a more fine-grained replacement for the 2nd conjunct below.
1056       // The real issue is whether the insertion will add non-annotation code,
1057       // which is only sometimes the case for a TypedInsertion.
1058       if (alreadyPresent(path, i) && !(i instanceof TypedInsertion)) {
1059         // Don't insert a duplicate if this particular annotation is already
1060         // present at this location.
1061         return null;
1062       }
1063 
1064       if (i.getKind() == Insertion.Kind.CONSTRUCTOR) {
1065         ConstructorInsertion cons = (ConstructorInsertion) i;
1066         if (node.getKind() == Tree.Kind.METHOD) {
1067           JCMethodDecl method = (JCMethodDecl) node;
1068           // TODO: account for the following situation in matching phase instead
1069           if (method.sym.owner.isAnonymous()) { return null; }
1070           if ((method.mods.flags & Flags.GENERATEDCONSTR) != 0) {
1071             addConstructor(path, cons, method);
1072           } else {
1073             cons.setAnnotationsOnly(true);
1074             cons.setInserted(true);
1075             i = cons.getReceiverInsertion();
1076             if (i == null) { return null; }
1077           }
1078         } else {
1079           cons.setAnnotationsOnly(true);
1080         }
1081       }
1082 
1083       if (i.getKind() == Insertion.Kind.RECEIVER && node.getKind() == Tree.Kind.METHOD) {
1084         ReceiverInsertion receiver = (ReceiverInsertion) i;
1085         MethodTree method = (MethodTree) node;
1086         VariableTree rcv = method.getReceiverParameter();
1087 
1088         if (rcv == null) {
1089           addReceiverType(path, receiver, method);
1090         }
1091       }
1092 
1093       if (i.getKind() == Insertion.Kind.NEW && node.getKind() == Tree.Kind.NEW_ARRAY) {
1094         NewInsertion neu = (NewInsertion) i;
1095         NewArrayTree newArray = (NewArrayTree) node;
1096 
1097         if (newArray.toString().startsWith("{")) {
1098           addNewType(path, neu, newArray);
1099         }
1100       }
1101 
1102       // If this is a method, then it might have been selected because of
1103       // the receiver, or because of the return value.  Distinguish those.
1104       // One way would be to set a global variable here.  Another would be
1105       // to look for a particular different node.  I will do the latter.
1106       Integer pos = Position.NOPOS;
1107 
1108       // The insertion location is at or below the matched location
1109       // in the source tree.  For example, a receiver annotation
1110       // matches on the method and inserts on the (possibly newly
1111       // created) receiver.
1112       Map<Tree, ASTRecord> astIndex = ASTIndex.indexOf(tree);
1113       ASTRecord insertRecord = astIndex.get(node);
1114       dbug.debug("TreeFinder.scan: node=%s%n  critera=%s%n",
1115           node, i.getCriteria());
1116 
1117       if (CommonScanner.hasClassKind(node)
1118           && i.getCriteria().isOnTypeDeclarationExtendsClause()
1119           && ((ClassTree) node).getExtendsClause() == null) {
1120         return implicitClassBoundPosition((JCClassDecl) node, i);
1121       }
1122       if (node.getKind() == Tree.Kind.METHOD
1123           && i.getCriteria().isOnReturnType()) {
1124         JCMethodDecl jcnode = (JCMethodDecl) node;
1125         Tree returnType = jcnode.getReturnType();
1126         insertRecord = insertRecord.extend(Tree.Kind.METHOD, ASTPath.TYPE);
1127         if (returnType == null) {
1128           // find constructor name instead
1129           pos = findMethodName(jcnode);
1130           if (pos < 0) {  // skip -- inserted w/generated constructor
1131             return null;
1132           }
1133           dbug.debug("pos = %d at constructor name: %s%n",
1134               pos, jcnode.sym.toString());
1135         } else {
1136           Pair<ASTRecord, Integer> pair = tpf.scan(returnType, i);
1137           insertRecord = pair.a;
1138           pos = pair.b;
1139           assert handled(node);
1140           dbug.debug("pos = %d at return type node: %s%n",
1141               pos, returnType.getClass());
1142         }
1143       } else if (node.getKind() == Tree.Kind.TYPE_PARAMETER
1144           && i.getCriteria().onBoundZero()
1145           && (((TypeParameterTree) node).getBounds().isEmpty()
1146               || (((JCExpression) ((TypeParameterTree) node)
1147                       .getBounds().get(0))).type.tsym.isInterface())
1148           || (node instanceof WildcardTree
1149               && ((WildcardTree) node).getBound() == null
1150               && wildcardLast(i.getCriteria()
1151                       .getGenericArrayLocation().getLocation()))) {
1152         Pair<ASTRecord, Integer> pair = tpf.scan(node, i);
1153         insertRecord = pair.a;
1154         pos = pair.b;
1155 
1156         if (i.getKind() == Insertion.Kind.ANNOTATION) {
1157           if (node.getKind() == Tree.Kind.TYPE_PARAMETER
1158               && !((TypeParameterTree) node).getBounds().isEmpty()) {
1159             Tree bound = ((TypeParameterTree) node).getBounds().get(0);
1160             pos = ((JCExpression) bound).getStartPosition();
1161             ((AnnotationInsertion) i).setGenerateBound(true);
1162           } else {
1163             int limit = ((JCTree) parent(node)).getEndPosition(tree.endPositions);
1164             Integer nextpos1 = getNthInstanceInRange(',', pos+1, limit, 1);
1165             Integer nextpos2 = getNthInstanceInRange('>', pos+1, limit, 1);
1166             pos = (nextpos1 != Position.NOPOS && nextpos1 < nextpos2) ? nextpos1 : nextpos2;
1167             ((AnnotationInsertion) i).setGenerateExtends(true);
1168           }
1169         }
1170       } else if (i.getKind() == Insertion.Kind.CAST) {
1171         Type t = ((CastInsertion) i).getType();
1172         JCTree jcTree = (JCTree) node;
1173         pos = jcTree.getStartPosition();
1174         if (t.getKind() == Type.Kind.DECLARED) {
1175           DeclaredType dt = (DeclaredType) t;
1176           if (dt.getName().isEmpty()) {
1177             dt.setName(jcTree.type instanceof NullType ? "Object"
1178                 : jcTree.type.toString());
1179           }
1180         }
1181       } else if (i.getKind() == Insertion.Kind.CLOSE_PARENTHESIS) {
1182         JCTree jcTree = (JCTree) node;
1183         pos = jcTree.getEndPosition(tree.endPositions);
1184       } else {
1185         boolean typeScan = true;
1186         if (node.getKind() == Tree.Kind.METHOD) { // MethodTree
1187           // looking for the receiver or the declaration
1188           typeScan = i.getCriteria().isOnReceiver();
1189         } else if (CommonScanner.hasClassKind(node)) { // ClassTree
1190           typeScan = ! i.getSeparateLine(); // hacky check
1191         }
1192         if (typeScan) {
1193           // looking for the type
1194           dbug.debug("Calling tpf.scan(%s: %s)%n", node.getClass(), node);
1195           Pair<ASTRecord, Integer> pair = tpf.scan(node, i);
1196           insertRecord = pair.a;
1197           pos = pair.b;
1198           assert handled(node);
1199           dbug.debug("pos = %d at type: %s (%s)%n", pos,
1200               node.toString(), node.getClass());
1201         } else if (node.getKind() == Tree.Kind.METHOD
1202             && i.getKind() == Insertion.Kind.CONSTRUCTOR
1203             && (((JCMethodDecl) node).mods.flags & Flags.GENERATEDCONSTR) != 0) {
1204           Tree parent = path.getParentPath().getLeaf();
1205           pos = ((JCClassDecl) parent).getEndPosition(tree.endPositions) - 1;
1206           insertRecord = null;  // TODO
1207         } else {
1208           // looking for the declaration
1209           pos = dpf.scan(node, null);
1210           insertRecord = astRecord(node);
1211           dbug.debug("pos = %s at declaration: %s%n", pos, node.getClass());
1212         }
1213       }
1214 
1215       if (pos != null) {
1216         assert pos >= 0 :
1217           String.format("pos: %s%nnode: %s%ninsertion: %s%n", pos, node, i);
1218         astInsertions.put(insertRecord, i);
1219       }
1220       return pos;
1221     } catch (Throwable e) {
1222       reportInsertionError(i, e);
1223       return null;
1224     }
1225   }
1226 
1227   // Find insertion position for Insertion whose criteria (including one
1228   // for the ASTPath) matched the given TreePath.
1229   // If no position is found, report an error and return null.
1230   Integer findPositionByASTPath(ASTPath astPath, TreePath path, Insertion i) {
1231     Tree node = path.getLeaf();
1232     try {
1233       ASTPath.ASTEntry entry = astPath.get(-1);
1234       // As per the JSR308 specification, receiver parameters are not allowed
1235       // on method declarations of anonymous inner classes.
1236       if (entry.getTreeKind() == Tree.Kind.METHOD
1237           && entry.childSelectorIs(ASTPath.PARAMETER)
1238           && entry.getArgument() == -1
1239           && path.getParentPath().getParentPath().getLeaf().getKind()
1240           == Tree.Kind.NEW_CLASS) {
1241         warn.debug("WARNING: Cannot insert a receiver parameter "
1242             + "on a method declaration of an anonymous inner class.  "
1243             + "This insertion will be skipped.%n    Insertion: %s%n", i);
1244         return null;
1245       }
1246 
1247       if (alreadyPresent(path, i)) {
1248         // Don't insert a duplicate if this particular annotation is already
1249         // present at this location.
1250         return null;
1251       }
1252 
1253       if (i.getKind() == Insertion.Kind.CONSTRUCTOR) {
1254         ConstructorInsertion cons = (ConstructorInsertion) i;
1255 
1256         if (node.getKind() == Tree.Kind.METHOD) {
1257           JCMethodDecl method = (JCMethodDecl) node;
1258           if ((method.mods.flags & Flags.GENERATEDCONSTR) != 0) {
1259             addConstructor(path, cons, method);
1260           } else {
1261             cons.setAnnotationsOnly(true);
1262             cons.setInserted(true);
1263             i = cons.getReceiverInsertion();
1264             if (i == null) { return null; }
1265           }
1266         } else {
1267           cons.setAnnotationsOnly(true);
1268         }
1269       }
1270 
1271       if (i.getKind() == Insertion.Kind.RECEIVER && node.getKind() == Tree.Kind.METHOD) {
1272         ReceiverInsertion receiver = (ReceiverInsertion) i;
1273         MethodTree method = (MethodTree) node;
1274 
1275         if (method.getReceiverParameter() == null) {
1276           addReceiverType(path, receiver, method);
1277         }
1278       }
1279 
1280       if (i.getKind() == Insertion.Kind.NEW && node.getKind() == Tree.Kind.NEW_ARRAY) {
1281         NewInsertion neu = (NewInsertion) i;
1282         NewArrayTree newArray = (NewArrayTree) node;
1283 
1284         if (newArray.toString().startsWith("{")) {
1285           addNewType(path, neu, newArray);
1286         }
1287       }
1288 
1289       // If this is a method, then it might have been selected because of
1290       // the receiver, or because of the return value.  Distinguish those.
1291       // One way would be to set a global variable here.  Another would be
1292       // to look for a particular different node.  I will do the latter.
1293       Integer pos = Position.NOPOS;
1294 
1295       // The insertion location is at or below the matched location
1296       // in the source tree.  For example, a receiver annotation
1297       // matches on the method and inserts on the (possibly newly
1298       // created) receiver.
1299       Map<Tree, ASTRecord> astIndex = ASTIndex.indexOf(tree);
1300       ASTRecord insertRecord = astIndex.get(node);
1301       dbug.debug("TreeFinder.scan: node=%s%n  criteria=%s%n",
1302           node, i.getCriteria());
1303 
1304       if (CommonScanner.hasClassKind(node)
1305           && entry.childSelectorIs(ASTPath.BOUND)
1306           && entry.getArgument() < 0
1307           && ((ClassTree) node).getExtendsClause() == null) {
1308         return implicitClassBoundPosition((JCClassDecl) node, i);
1309       }
1310       if (node.getKind() == Tree.Kind.METHOD
1311           && i.getCriteria().isOnMethod("<init>()V")
1312           && entry.childSelectorIs(ASTPath.PARAMETER)
1313           && entry.getArgument() < 0) {
1314         if (i.getKind() != Insertion.Kind.CONSTRUCTOR) { return null; }
1315         Tree parent = path.getParentPath().getLeaf();
1316         insertRecord = insertRecord.extend(Tree.Kind.METHOD, ASTPath.PARAMETER, -1);
1317         pos = ((JCTree) parent).getEndPosition(tree.endPositions) - 1;
1318       } else if (node.getKind() == Tree.Kind.METHOD
1319           && entry.childSelectorIs(ASTPath.TYPE)) {
1320         JCMethodDecl jcnode = (JCMethodDecl) node;
1321         Tree returnType = jcnode.getReturnType();
1322         insertRecord = insertRecord.extend(Tree.Kind.METHOD, ASTPath.TYPE);
1323         if (returnType == null) {
1324           // find constructor name instead
1325           pos = findMethodName(jcnode);
1326           if (pos < 0) {  // skip -- inserted w/generated constructor
1327             return null;
1328           }
1329           dbug.debug("pos = %d at constructor name: %s%n",
1330               pos, jcnode.sym.toString());
1331         } else {
1332           Pair<ASTRecord, Integer> pair = tpf.scan(returnType, i);
1333           insertRecord = pair.a;
1334           pos = pair.b;
1335           assert handled(node);
1336           dbug.debug("pos = %d at return type node: %s%n",
1337               pos, returnType.getClass());
1338         }
1339       } else if (node.getKind() == Tree.Kind.TYPE_PARAMETER
1340           && entry.getTreeKind() == Tree.Kind.TYPE_PARAMETER  // TypeParameter.bound
1341           && (((TypeParameterTree) node).getBounds().isEmpty()
1342               || (((JCExpression) ((TypeParameterTree) node)
1343                       .getBounds().get(0))).type.tsym.isInterface())
1344           || ASTPath.isWildcard(node.getKind())
1345           && (entry.getTreeKind() == Tree.Kind.TYPE_PARAMETER
1346               || ASTPath.isWildcard(entry.getTreeKind()))
1347           && entry.childSelectorIs(ASTPath.BOUND)
1348           && (!entry.hasArgument() || entry.getArgument() == 0)) {
1349         Pair<ASTRecord, Integer> pair = tpf.scan(node, i);
1350         insertRecord = pair.a;
1351         pos = pair.b;
1352 
1353         if (i.getKind() == Insertion.Kind.ANNOTATION) {
1354           if (node.getKind() == Tree.Kind.TYPE_PARAMETER
1355               && !((TypeParameterTree) node).getBounds().isEmpty()) {
1356             Tree bound = ((TypeParameterTree) node).getBounds().get(0);
1357             pos = ((JCExpression) bound).getStartPosition();
1358             ((AnnotationInsertion) i).setGenerateBound(true);
1359           } else {
1360             int limit = ((JCTree) parent(node)).getEndPosition(tree.endPositions);
1361             Integer nextpos1 = getNthInstanceInRange(',', pos+1, limit, 1);
1362             Integer nextpos2 = getNthInstanceInRange('>', pos+1, limit, 1);
1363             pos = (nextpos1 != Position.NOPOS && nextpos1 < nextpos2) ? nextpos1 : nextpos2;
1364             ((AnnotationInsertion) i).setGenerateExtends(true);
1365           }
1366         }
1367       } else if (i.getKind() == Insertion.Kind.CAST) {
1368         Type t = ((CastInsertion) i).getType();
1369         JCTree jcTree = (JCTree) node;
1370         if (jcTree.getKind() == Tree.Kind.VARIABLE && !astPath.isEmpty()
1371             && astPath.get(-1).childSelectorIs(ASTPath.INITIALIZER)) {
1372           node = ((JCVariableDecl) node).getInitializer();
1373           if (node == null) { return null; }
1374           jcTree = (JCTree) node;
1375         }
1376         pos = jcTree.getStartPosition();
1377         if (t.getKind() == Type.Kind.DECLARED) {
1378           DeclaredType dt = (DeclaredType) t;
1379           if (dt.getName().isEmpty()) {
1380               if (jcTree.type instanceof NullType) {
1381                 dt.setName("Object");
1382               } else {
1383                 t = Insertions.TypeTree.conv(jcTree.type);
1384                 t.setAnnotations(dt.getAnnotations());
1385                 ((CastInsertion) i).setType(t);
1386               }
1387           }
1388         }
1389       } else if (i.getKind() == Insertion.Kind.CLOSE_PARENTHESIS) {
1390         JCTree jcTree = (JCTree) node;
1391         if (jcTree.getKind() == Tree.Kind.VARIABLE && !astPath.isEmpty()
1392             && astPath.get(-1).childSelectorIs(ASTPath.INITIALIZER)) {
1393           node = ((JCVariableDecl) node).getInitializer();
1394           if (node == null) { return null; }
1395           jcTree = (JCTree) node;
1396         }
1397         pos = jcTree.getEndPosition(tree.endPositions);
1398       } else {
1399         boolean typeScan = true;
1400         if (node.getKind() == Tree.Kind.METHOD) { // MethodTree
1401           // looking for the receiver or the declaration
1402           typeScan = IndexFileSpecification.isOnReceiver(i.getCriteria());
1403         } else if (node.getKind() == Tree.Kind.CLASS) { // ClassTree
1404           typeScan = ! i.getSeparateLine(); // hacky check
1405         }
1406         if (typeScan) {
1407           // looking for the type
1408           dbug.debug("Calling tpf.scan(%s: %s)%n", node.getClass(), node);
1409           Pair<ASTRecord, Integer> pair = tpf.scan(node, i);
1410           insertRecord = pair.a;
1411           pos = pair.b;
1412           assert handled(node);
1413           dbug.debug("pos = %d at type: %s (%s)%n",
1414               pos, node.toString(), node.getClass());
1415         } else if (node.getKind() == Tree.Kind.METHOD
1416             && i.getKind() == Insertion.Kind.CONSTRUCTOR
1417             && (((JCMethodDecl) node).mods.flags & Flags.GENERATEDCONSTR) != 0) {
1418           Tree parent = path.getParentPath().getLeaf();
1419           pos = ((JCClassDecl) parent).getEndPosition(tree.endPositions) - 1;
1420           insertRecord = null;  // TODO
1421         } else {
1422           // looking for the declaration
1423           pos = dpf.scan(node, null);
1424           insertRecord = astRecord(node);
1425           assert pos != null;
1426           dbug.debug("pos = %d at declaration: %s%n", pos, node.getClass());
1427         }
1428       }
1429 
1430       if (pos != null) {
1431         assert pos >= 0 :
1432           String.format("pos: %s%nnode: %s%ninsertion: %s%n", pos, node, i);
1433         astInsertions.put(insertRecord, i);
1434       }
1435       return pos;
1436     } catch (Throwable e) {
1437       reportInsertionError(i, e);
1438       return null;
1439     }
1440   }
1441 
1442   private Integer implicitClassBoundPosition(JCClassDecl cd, Insertion i) {
1443     Integer pos;
1444     if (cd.sym == null || cd.sym.isAnonymous()
1445         || i.getKind() != Insertion.Kind.ANNOTATION) {
1446       return null;
1447     }
1448     JCModifiers mods = cd.getModifiers();
1449     String name = cd.getSimpleName().toString();
1450     if (cd.typarams == null || cd.typarams.isEmpty()) {
1451       int start = cd.getStartPosition();
1452       int offset = Math.max(start,
1453           mods.getEndPosition(tree.endPositions) + 1);
1454       String s = cd.toString().substring(offset - start);
1455       Pattern p = Pattern.compile("(?:\\s|" + comment
1456           + ")*+class(?:\\s|" + comment
1457           + ")++" + Pattern.quote(name) + "\\b");
1458       Matcher m = p.matcher(s);
1459       if (!m.find() || m.start() != 0) { return null; }
1460       pos = offset + m.end() - 1;
1461     } else {  // generic class
1462       JCTypeParameter param = cd.typarams.get(cd.typarams.length()-1);
1463       int start = param.getEndPosition(tree.endPositions);
1464       pos = getFirstInstanceAfter('>', start) + 1;
1465     }
1466     ((AnnotationInsertion) i).setGenerateExtends(true);
1467     return pos;
1468   }
1469 
1470   /**
1471    * Returns the start position of the method's name.  In particular,
1472    * works properly for constructors, for which the name field in the
1473    * AST is always "<init>" instead of the name from the source.
1474    *
1475    * @param node AST node of method declaration
1476    * @return position of method name (from {@link JCMethodDecl#sym}) in source
1477    */
1478   private int findMethodName(JCMethodDecl node) {
1479     String sym = node.sym.toString();
1480     String name = sym.substring(0, sym.indexOf('('));
1481     JCModifiers mods = node.getModifiers();
1482     JCBlock body = node.body;
1483     if ((mods.flags & Flags.GENERATEDCONSTR) != 0) { return Position.NOPOS; }
1484     int nodeStart = node.getStartPosition();
1485     int nodeEnd = node.getEndPosition(tree.endPositions);
1486     int nodeLength = nodeEnd - nodeStart;
1487     int modsLength = mods.getEndPosition(tree.endPositions)
1488         - mods.getStartPosition();  // can't trust string length!
1489     int bodyLength = body == null ? 1
1490         : body.getEndPosition(tree.endPositions) - body.getStartPosition();
1491     int start = nodeStart + modsLength;
1492     int end = nodeStart + nodeLength - bodyLength;
1493     int angle = name.lastIndexOf('>');  // check for type params
1494     if (angle >= 0) { name = name.substring(angle + 1); }
1495 
1496     try {
1497       CharSequence s = tree.getSourceFile().getCharContent(true);
1498       String regex = "\\b" + Pattern.quote(name) + "\\b";  // sufficient?
1499       Pattern pat = Pattern.compile(regex, Pattern.MULTILINE);
1500       Matcher mat = pat.matcher(s).region(start, end);
1501       return mat.find() ? mat.start() : Position.NOPOS;
1502     } catch (IOException e) {
1503       throw new RuntimeException(e);
1504     }
1505   }
1506 
1507   /**
1508    * Determines if the annotation in the given insertion is already present
1509    * at the given location in the AST.
1510    *
1511    * @param path the location in the AST to check for the annotation
1512    * @param ins the annotation to check for
1513    * @return {@code true} if the given annotation is already at the given
1514    *         location in the AST, {@code false} otherwise.
1515    */
1516   private boolean alreadyPresent(TreePath path, Insertion ins) {
1517     List<? extends AnnotationTree> alreadyPresent = null;
1518     if (path != null) {
1519       for (Tree n : path) {
1520         if (n.getKind() == Tree.Kind.CLASS) {
1521           alreadyPresent = ((ClassTree) n).getModifiers().getAnnotations();
1522           break;
1523         } else if (n.getKind() == Tree.Kind.METHOD) {
1524           alreadyPresent = ((MethodTree) n).getModifiers().getAnnotations();
1525           break;
1526         } else if (n.getKind() == Tree.Kind.VARIABLE) {
1527           alreadyPresent = ((VariableTree) n).getModifiers().getAnnotations();
1528           break;
1529         } else if (n.getKind() == Tree.Kind.TYPE_CAST) {
1530           Tree type = ((TypeCastTree) n).getType();
1531           if (type.getKind() == Tree.Kind.ANNOTATED_TYPE) {
1532             alreadyPresent = ((AnnotatedTypeTree) type).getAnnotations();
1533           }
1534           break;
1535         } else if (n.getKind() == Tree.Kind.INSTANCE_OF) {
1536           Tree type = ((InstanceOfTree) n).getType();
1537           if (type.getKind() == Tree.Kind.ANNOTATED_TYPE) {
1538             alreadyPresent = ((AnnotatedTypeTree) type).getAnnotations();
1539           }
1540           break;
1541         } else if (n.getKind() == Tree.Kind.NEW_CLASS) {
1542           JCNewClass nc = (JCNewClass) n;
1543           if (nc.clazz.getKind() == Tree.Kind.ANNOTATED_TYPE) {
1544             alreadyPresent = ((AnnotatedTypeTree) nc.clazz).getAnnotations();
1545           }
1546           break;
1547         } else if (n.getKind() == Tree.Kind.PARAMETERIZED_TYPE) {
1548           // If we pass through a parameterized type, stop, otherwise we
1549           // mix up annotations on the outer type.
1550           break;
1551         } else if (n.getKind() == Tree.Kind.ARRAY_TYPE) {
1552           Tree type = ((ArrayTypeTree) n).getType();
1553           if (type.getKind() == Tree.Kind.ANNOTATED_TYPE) {
1554             alreadyPresent = ((AnnotatedTypeTree) type).getAnnotations();
1555           }
1556           break;
1557         } else if (n.getKind() == Tree.Kind.ANNOTATED_TYPE) {
1558           alreadyPresent = ((AnnotatedTypeTree) n).getAnnotations();
1559           break;
1560         }
1561         // TODO: don't add cast insertion if it's already present.
1562       }
1563     }
1564 
1565     if (alreadyPresent != null) {
1566       for (AnnotationTree at : alreadyPresent) {
1567         // Compare the to-be-inserted annotation to the existing
1568         // annotation, ignoring its arguments (duplicate annotations are
1569         // never allowed even if they differ in arguments).  If we did
1570         // have to compare our arguments, we'd have to deal with enum
1571         // arguments potentially being fully qualified or not:
1572         // @Retention(java.lang.annotation.RetentionPolicy.CLASS) vs
1573         // @Retention(RetentionPolicy.CLASS)
1574         String ann = at.getAnnotationType().toString();
1575         // strip off leading @ along w/any leading or trailing whitespace
1576         String text = ins.getText();
1577         String iann = Main.removeArgs(text).a.trim()
1578             .substring(text.startsWith("@") ? 1 : 0);
1579         String iannNoPackage = Insertion.removePackage(iann).b;
1580         // System.out.printf("Comparing: %s %s %s%n", ann, iann, iannNoPackage);
1581         if (ann.equals(iann) || ann.equals(iannNoPackage)) {
1582           dbug.debug("Already present, not reinserting: %s%n", ann);
1583           return true;
1584         }
1585       }
1586     }
1587     return false;
1588   }
1589 
1590   /**
1591    * Reports an error inserting an insertion to {@code System.err}.
1592    * @param i the insertion that caused the error
1593    * @param e the error. If there's a message it will be printed.
1594    */
1595   public static void reportInsertionError(Insertion i, Throwable e) {
1596     System.err.println("Error processing insertion:");
1597     System.err.println("\t" + i);
1598     if (e.getMessage() != null) {
1599       // If the message has multiple lines, indent them so it's easier to read.
1600       System.err.println("\tError: " + e.getMessage().replace("\n", "\n\t\t"));
1601     }
1602     if (dbug.or(stak).isEnabled()) {
1603       e.printStackTrace();
1604     } else {
1605       System.err.println("\tRun with --print_error_stack to see the stack trace.");
1606     }
1607     System.err.println("\tThis insertion will be skipped.");
1608   }
1609 
1610   /**
1611    * Modifies the given receiver insertion so that it contains the type
1612    * information necessary to insert a full method declaration receiver
1613    * parameter. This is for receiver insertions where a receiver does not
1614    * already exist in the source code. This will also add the annotations to be
1615    * inserted to the correct part of the receiver type.
1616    *
1617    * @param path the location in the AST to insert the receiver
1618    * @param receiver details of the receiver to insert
1619    * @param method the method the receiver is being inserted into
1620    */
1621   private void addReceiverType(TreePath path, ReceiverInsertion receiver,
1622       MethodTree method) {
1623     // Find the name of the class
1624     // with type parameters to create the receiver. Walk up the tree and
1625     // pick up class names to add to the receiver type. Since we're
1626     // starting from the innermost class, the classes we get to at earlier
1627     // iterations of the loop are inside of the classes we get to at later
1628     // iterations.
1629     TreePath parent = path;
1630     Tree leaf = parent.getLeaf();
1631     Tree.Kind kind = leaf.getKind();
1632     // This is the outermost type, currently containing only the
1633     // annotation to add to the receiver.
1634     Type outerType = receiver.getType();
1635     DeclaredType baseType = receiver.getBaseType();
1636     // This holds the inner types as they're being read in.
1637     DeclaredType innerTypes = null;
1638     DeclaredType staticType = null;
1639     // For an inner class constructor, the receiver comes from the
1640     // superclass, so skip past the first type definition.
1641     boolean isCon = ((MethodTree) parent.getLeaf()).getReturnType() == null;
1642     boolean skip = isCon;
1643 
1644     while (kind != Tree.Kind.COMPILATION_UNIT
1645         && kind != Tree.Kind.NEW_CLASS) {
1646       if (kind == Tree.Kind.CLASS
1647           || kind == Tree.Kind.INTERFACE
1648           || kind == Tree.Kind.ENUM
1649           || kind == Tree.Kind.ANNOTATION_TYPE) {
1650         ClassTree clazz = (ClassTree) leaf;
1651         String className = clazz.getSimpleName().toString();
1652         boolean isStatic = kind == Tree.Kind.INTERFACE
1653             || kind == Tree.Kind.ENUM
1654             || clazz.getModifiers().getFlags().contains(Modifier.STATIC);
1655         skip &= !isStatic;
1656         if (skip) {
1657           skip = false;
1658           receiver.setQualifyType(true);
1659         } else if (!className.isEmpty()) {
1660           // className will be empty for the CLASS node directly inside an
1661           // anonymous inner class NEW_CLASS node.
1662           DeclaredType inner = new DeclaredType(className);
1663           if (staticType == null) {
1664             // Only include type parameters on the classes to the right of and
1665             // including the rightmost static class.
1666             for (TypeParameterTree tree : clazz.getTypeParameters()) {
1667               inner.addTypeParameter(new DeclaredType(tree.getName().toString()));
1668             }
1669           }
1670           if (staticType == null && isStatic) {
1671             // If this is the first static class then move the annotations here.
1672             inner.setAnnotations(outerType.getAnnotations());
1673             outerType.clearAnnotations();
1674             staticType = inner;
1675           }
1676           if (innerTypes == null) {
1677             // This is the first type we've read in, so set it as the
1678             // innermost type.
1679             innerTypes = inner;
1680           } else {
1681             // inner (the type just read in this iteration) is outside of
1682             // innerTypes (the types already read in previous iterations).
1683             inner.setInnerType(innerTypes);
1684             innerTypes = inner;
1685           }
1686         }
1687       }
1688       parent = parent.getParentPath();
1689       leaf = parent.getLeaf();
1690       kind = leaf.getKind();
1691     }
1692     if (isCon && innerTypes == null) {
1693       throw new IllegalArgumentException(
1694           "can't annotate (non-existent) receiver of non-inner constructor");
1695     }
1696 
1697     // Merge innerTypes into outerType: outerType only has the annotations
1698     // on the receiver, while innerTypes has everything else. innerTypes can
1699     // have the annotations if it is a static class.
1700     baseType.setName(innerTypes.getName());
1701     baseType.setTypeParameters(innerTypes.getTypeParameters());
1702     baseType.setInnerType(innerTypes.getInnerType());
1703     if (staticType != null && !innerTypes.getAnnotations().isEmpty()) {
1704       outerType.setAnnotations(innerTypes.getAnnotations());
1705     }
1706 
1707     Type type = (staticType == null) ? baseType : staticType;
1708     Insertion.decorateType(receiver.getInnerTypeInsertions(), type,
1709         receiver.getCriteria().getASTPath());
1710 
1711     // If the method doesn't have parameters, don't add a comma.
1712     receiver.setAddComma(method.getParameters().size() > 0);
1713   }
1714 
1715   private void addNewType(TreePath path, NewInsertion neu,
1716       NewArrayTree newArray) {
1717     DeclaredType baseType = neu.getBaseType();
1718     if (baseType.getName().isEmpty()) {
1719       List<String> annotations = neu.getType().getAnnotations();
1720       Type newType = Insertions.TypeTree.conv(
1721           ((JCTree.JCNewArray) newArray).type);
1722       for (String ann : annotations) {
1723         newType.addAnnotation(ann);
1724       }
1725       neu.setType(newType);
1726     }
1727     Insertion.decorateType(neu.getInnerTypeInsertions(), neu.getType(),
1728         neu.getCriteria().getASTPath());
1729   }
1730 
1731   private void addConstructor(TreePath path, ConstructorInsertion cons,
1732       MethodTree method) {
1733     ReceiverInsertion recv = cons.getReceiverInsertion();
1734     MethodTree leaf = (MethodTree) path.getLeaf();
1735     ClassTree parent = (ClassTree) path.getParentPath().getLeaf();
1736     DeclaredType baseType = cons.getBaseType();
1737     if (baseType.getName().isEmpty()) {
1738       List<String> annotations = baseType.getAnnotations();
1739       String className = parent.getSimpleName().toString();
1740       Type newType = new DeclaredType(className);
1741       cons.setType(newType);
1742       for (String ann : annotations) {
1743         newType.addAnnotation(ann);
1744       }
1745     }
1746     if (recv != null) {
1747       Iterator<Insertion> iter = cons.getInnerTypeInsertions().iterator();
1748       List<Insertion> recvInner = new ArrayList<Insertion>();
1749       addReceiverType(path, recv, leaf);
1750       while (iter.hasNext()) {
1751         Insertion i = iter.next();
1752         if (i.getCriteria().isOnReceiver()) {
1753           recvInner.add(i);
1754           iter.remove();
1755         }
1756       }
1757       Insertion.decorateType(recvInner, recv.getType(),
1758           cons.getCriteria().getASTPath());
1759     }
1760     Insertion.decorateType(cons.getInnerTypeInsertions(), cons.getType(),
1761         cons.getCriteria().getASTPath());
1762   }
1763 
1764   public SetMultimap<ASTRecord, Insertion> getPaths() {
1765     return Multimaps.unmodifiableSetMultimap(astInsertions);
1766   }
1767 
1768   /**
1769    * Scans the given tree with the given insertion list and returns the
1770    * mapping from source position to insertion text.  The positions are sorted
1771    * in decreasing order of index, so that inserting one doesn't throw
1772    * off the index for a subsequent one.
1773    *
1774    * <p>
1775    * <i>N.B.:</i> This method calls {@code scan()} internally.
1776    * </p>
1777    *
1778    * @param node the tree to scan
1779    * @param p the list of insertion criteria
1780    * @return the source position to insertion text mapping
1781    */
1782   public SetMultimap<Pair<Integer, ASTPath>, Insertion>
1783   getInsertionsByPosition(JCCompilationUnit node, List<Insertion> p) {
1784     List<Insertion> uninserted = new ArrayList<Insertion>(p);
1785     this.scan(node, uninserted);
1786     // There may be many extra annotations in a .jaif file.  For instance,
1787     // the .jaif file may be for an entire library, but its compilation
1788     // units are processed one by one.
1789     // However, we should warn about any insertions that were within the
1790     // given compilation unit but still didn't get inserted.
1791     List<? extends Tree> typeDecls = node.getTypeDecls();
1792     for (Insertion i : uninserted) {
1793       InClassCriterion c = i.getCriteria().getInClass();
1794       if (c == null) {
1795         continue;
1796       }
1797       for (Tree t : typeDecls) {
1798         if (c.isSatisfiedBy(TreePath.getPath(node, t))) {
1799           // Avoid warnings about synthetic generated methods.
1800           // This test is too coarse, but is good enough for now.
1801           // There are also synthetic local variables; maybe suppress
1802           // warnings about them, too.
1803           if (! (i.getCriteria().isOnMethod("<init>()V")
1804                  || i.getCriteria().isOnLocalVariable())) {
1805             // Should be made more user-friendly
1806             System.err.printf("Found class %s, but unable to insert %s:%n  %s%n", c.className, i.getText(), i);
1807           }
1808         }
1809       }
1810     }
1811     if (dbug.isEnabled()) {
1812       // Output every insertion that was not given a position:
1813       for (Insertion i : uninserted) {
1814         System.err.println("Unable to insert: " + i);
1815       }
1816     }
1817     dbug.debug("getPositions => %d positions%n", insertions.size());
1818     return Multimaps.unmodifiableSetMultimap(insertions);
1819   }
1820 
1821   /**
1822    * Scans the given tree with the given {@link Insertions} and returns
1823    * the mapping from source position to insertion text.
1824    *
1825    * <p>
1826    * <i>N.B.:</i> This method calls {@code scan()} internally.
1827    * </p>
1828    *
1829    * @param node the tree to scan
1830    * @param insertions the insertion criteria
1831    * @return the source position to insertion text mapping
1832    */
1833   public SetMultimap<Pair<Integer, ASTPath>, Insertion>
1834   getPositions(JCCompilationUnit node, Insertions insertions) {
1835     List<Insertion> list = new ArrayList<Insertion>();
1836     treePathCache.clear();
1837     list.addAll(insertions.forOuterClass(node, ""));
1838     for (JCTree decl : node.getTypeDecls()) {
1839       if (decl.getTag() == JCTree.Tag.CLASSDEF) {
1840         String name = ((JCClassDecl) decl).sym.className();
1841         Collection<Insertion> forClass = insertions.forOuterClass(node, name);
1842         list.addAll(forClass);
1843       }
1844     }
1845     return getInsertionsByPosition(node, list);
1846   }
1847 }
1848