1package annotations.io;
2
3import java.util.ArrayDeque;
4import java.util.ArrayList;
5import java.util.Deque;
6import java.util.HashMap;
7import java.util.List;
8import java.util.Map;
9
10import javax.lang.model.element.Name;
11
12import com.google.common.collect.BiMap;
13import com.google.common.collect.HashBiMap;
14import com.sun.source.tree.AnnotatedTypeTree;
15import com.sun.source.tree.AnnotationTree;
16import com.sun.source.tree.ArrayAccessTree;
17import com.sun.source.tree.ArrayTypeTree;
18import com.sun.source.tree.AssertTree;
19import com.sun.source.tree.AssignmentTree;
20import com.sun.source.tree.BinaryTree;
21import com.sun.source.tree.BlockTree;
22import com.sun.source.tree.CaseTree;
23import com.sun.source.tree.CatchTree;
24import com.sun.source.tree.ClassTree;
25import com.sun.source.tree.CompilationUnitTree;
26import com.sun.source.tree.CompoundAssignmentTree;
27import com.sun.source.tree.ConditionalExpressionTree;
28import com.sun.source.tree.DoWhileLoopTree;
29import com.sun.source.tree.EnhancedForLoopTree;
30import com.sun.source.tree.ExpressionStatementTree;
31import com.sun.source.tree.ExpressionTree;
32import com.sun.source.tree.ForLoopTree;
33import com.sun.source.tree.IfTree;
34import com.sun.source.tree.InstanceOfTree;
35import com.sun.source.tree.IntersectionTypeTree;
36import com.sun.source.tree.LabeledStatementTree;
37import com.sun.source.tree.LambdaExpressionTree;
38import com.sun.source.tree.MemberReferenceTree;
39import com.sun.source.tree.MemberSelectTree;
40import com.sun.source.tree.MethodInvocationTree;
41import com.sun.source.tree.MethodTree;
42import com.sun.source.tree.ModifiersTree;
43import com.sun.source.tree.NewArrayTree;
44import com.sun.source.tree.NewClassTree;
45import com.sun.source.tree.ParameterizedTypeTree;
46import com.sun.source.tree.ParenthesizedTree;
47import com.sun.source.tree.ReturnTree;
48import com.sun.source.tree.SwitchTree;
49import com.sun.source.tree.SynchronizedTree;
50import com.sun.source.tree.ThrowTree;
51import com.sun.source.tree.Tree;
52import com.sun.source.tree.Tree.Kind;
53import com.sun.source.tree.TryTree;
54import com.sun.source.tree.TypeCastTree;
55import com.sun.source.tree.TypeParameterTree;
56import com.sun.source.tree.UnaryTree;
57import com.sun.source.tree.UnionTypeTree;
58import com.sun.source.tree.VariableTree;
59import com.sun.source.tree.WhileLoopTree;
60import com.sun.source.tree.WildcardTree;
61import com.sun.source.util.SimpleTreeVisitor;
62import com.sun.source.util.TreePath;
63import com.sun.tools.javac.code.Symbol.ClassSymbol;
64import com.sun.tools.javac.tree.JCTree;
65
66import annotations.util.JVMNames;
67import annotations.util.coll.WrapperMap;
68
69/**
70 * Cache of {@code ASTPath} data for the nodes of a compilation unit tree.
71 *
72 * @author dbro
73 */
74public class ASTIndex extends WrapperMap<Tree, ASTRecord> {
75  // single-item cache
76  private static Tree cachedRoot = null;
77  private static Map<Tree, ASTRecord> cachedIndex = null;
78  private static final int EXPECTED_SIZE = 128;
79
80  private final CompilationUnitTree cut;
81  private final Map<String, Map<String, List<String>>> formals;
82
83  /**
84   * Maps source trees in compilation unit to corresponding AST paths.
85   *
86   * @param root compilation unit to be indexed
87   * @return map of trees in compilation unit to AST paths
88   */
89  public static Map<Tree, ASTRecord> indexOf(CompilationUnitTree root) {
90    if (cachedRoot == null || !cachedRoot.equals(root)) {
91      cachedRoot = root;
92      cachedIndex = new ASTIndex(root);
93    }
94    return cachedIndex;
95  }
96
97  private ASTIndex(CompilationUnitTree root) {
98    super(HashBiMap.<Tree, ASTRecord>create(EXPECTED_SIZE));
99    cut = root;
100    formals = new HashMap<String, Map<String, List<String>>>();
101
102    // The visitor implementation is slightly complicated by the
103    // inclusion of information from both parent and child nodes in each
104    // ASTEntry.  The pattern for most node types is to call save() and
105    // saveAll() as needed to handle the node's descendants and finally
106    // to invoke defaultAction() to save the entry for the current node.
107    // (If the JVM could take advantage of tail recursion, it would be
108    // better to save the current node's entry first, at a small cost to
109    // the clarity of the code.)
110    cut.accept(new SimpleTreeVisitor<Void, ASTRecord>() {
111      Deque<Integer> counters = new ArrayDeque<Integer>();
112      String inMethod = null;
113
114      private void save(Tree node, ASTRecord rec,
115          Kind kind, String sel) {
116        if (node != null) {
117          node.accept(this, rec.extend(kind, sel));
118        }
119      }
120
121      private void save(Tree node, ASTRecord rec,
122          Kind kind, String sel, int arg) {
123        if (node != null) {
124          node.accept(this, rec.extend(kind, sel, arg));
125        }
126      }
127
128      private void saveAll(Iterable<? extends Tree> nodes,
129          ASTRecord rec, Kind kind, String sel) {
130        if (nodes != null) {
131          int i = 0;
132          for (Tree node : nodes) {
133            save(node, rec, kind, sel, i++);
134          }
135        }
136      }
137
138      private void saveClass(ClassTree node) {
139        String className =
140            ((JCTree.JCClassDecl) node).sym.flatname.toString();
141        ASTRecord rec =
142            new ASTRecord(cut, className, null, null, ASTPath.empty());
143        counters.push(0);
144        node.accept(this, rec);
145        counters.pop();
146      }
147
148      @Override
149      public Void defaultAction(Tree node, ASTRecord rec) {
150        switch (node.getKind()) {
151        case BREAK:
152        case COMPILATION_UNIT:
153        case CONTINUE:
154        case IMPORT:
155        case MODIFIERS:
156          break;  // not handled
157        default:
158          put(node, rec);
159        }
160        return null;
161      }
162
163      @Override
164      public Void visitAnnotatedType(AnnotatedTypeTree node,
165          ASTRecord rec) {
166        Kind kind = node.getKind();
167        saveAll(node.getAnnotations(), rec, kind, ASTPath.ANNOTATION);
168        save(node.getUnderlyingType(), rec, kind, ASTPath.UNDERLYING_TYPE);
169        return defaultAction(node, rec);
170      }
171
172      @Override
173      public Void visitAnnotation(AnnotationTree node,
174          ASTRecord rec) {
175        Kind kind = node.getKind();
176        save(node.getAnnotationType(), rec, kind, ASTPath.TYPE);
177        saveAll(node.getArguments(), rec, kind, ASTPath.ARGUMENT);
178        return defaultAction(node, rec);
179      }
180
181      @Override
182      public Void visitMethodInvocation(MethodInvocationTree node,
183          ASTRecord rec) {
184        Kind kind = node.getKind();
185        saveAll(node.getTypeArguments(), rec, kind, ASTPath.TYPE_ARGUMENT);
186        save(node.getMethodSelect(), rec, kind, ASTPath.METHOD_SELECT);
187        saveAll(node.getArguments(), rec, kind, ASTPath.ARGUMENT);
188        return defaultAction(node, rec);
189      }
190
191      @Override
192      public Void visitAssert(AssertTree node, ASTRecord rec) {
193        Kind kind = node.getKind();
194        save(node.getCondition(), rec, kind, ASTPath.CONDITION);
195        save(node.getDetail(), rec, kind, ASTPath.DETAIL);
196        return defaultAction(node, rec);
197      }
198
199      @Override
200      public Void visitAssignment(AssignmentTree node, ASTRecord rec) {
201        Kind kind = node.getKind();
202        save(node.getExpression(), rec, kind, ASTPath.EXPRESSION);
203        save(node.getVariable(), rec, kind, ASTPath.VARIABLE);
204        return defaultAction(node, rec);
205      }
206
207      @Override
208      public Void visitCompoundAssignment(CompoundAssignmentTree node,
209          ASTRecord rec) {
210        Kind kind = node.getKind();
211        save(node.getExpression(), rec, kind, ASTPath.EXPRESSION);
212        save(node.getVariable(), rec, kind, ASTPath.VARIABLE);
213        return defaultAction(node, rec);
214      }
215
216      @Override
217      public Void visitBinary(BinaryTree node, ASTRecord rec) {
218        Kind kind = node.getKind();
219        save(node.getLeftOperand(), rec, kind, ASTPath.LEFT_OPERAND);
220        save(node.getRightOperand(), rec, kind, ASTPath.RIGHT_OPERAND);
221        return defaultAction(node, rec);
222      }
223
224      @Override
225      public Void visitBlock(BlockTree node, ASTRecord rec) {
226        Iterable<? extends Tree> nodes = node.getStatements();
227        if (nodes != null) {
228          int i = 0;
229          for (Tree stmt : nodes) {
230            if (ASTPath.isClassEquiv(stmt.getKind())) {
231              saveClass((ClassTree) stmt);
232            } else {
233              save(stmt, rec, node.getKind(), ASTPath.STATEMENT, i);
234            }
235            ++i;
236          }
237        }
238        return defaultAction(node, rec);
239      }
240
241      @Override
242      public Void visitCase(CaseTree node, ASTRecord rec) {
243        Kind kind = node.getKind();
244        save(node.getExpression(), rec, kind, ASTPath.EXPRESSION);
245        saveAll(node.getStatements(), rec, kind, ASTPath.STATEMENT);
246        return defaultAction(node, rec);
247      }
248
249      @Override
250      public Void visitCatch(CatchTree node, ASTRecord rec) {
251        Kind kind = node.getKind();
252        save(node.getBlock(), rec, kind, ASTPath.BLOCK);
253        save(node.getParameter(), rec, kind, ASTPath.PARAMETER);
254        return defaultAction(node, rec);
255      }
256
257      @Override
258      public Void visitClass(ClassTree node, ASTRecord rec) {
259        Kind kind = Tree.Kind.CLASS;  // use for all class-equivalent kinds
260        int i = 0;
261        formals.put(rec.className, new HashMap<String, List<String>>());
262        if (node.getSimpleName().length() > 0) {
263          // don't save exts/impls for anonymous inner class
264          save(node.getExtendsClause(), rec, kind, ASTPath.BOUND, -1);
265          saveAll(node.getImplementsClause(), rec, kind, ASTPath.BOUND);
266        }
267        saveAll(node.getTypeParameters(), rec, kind, ASTPath.TYPE_PARAMETER);
268        for (Tree member : node.getMembers()) {
269          if (member.getKind() == Tree.Kind.BLOCK) {
270            save(member, rec, kind, ASTPath.INITIALIZER, i++);
271          } else if (ASTPath.isClassEquiv(member.getKind())) {
272            String className =
273                ((JCTree.JCClassDecl) member).sym.flatname.toString();
274            member.accept(this,
275                new ASTRecord(cut, className, null, null, ASTPath.empty()));
276          } else {
277            member.accept(this, rec);
278          }
279        }
280        return defaultAction(node, rec);
281      }
282
283      @Override
284      public Void visitConditionalExpression(ConditionalExpressionTree node,
285          ASTRecord rec) {
286        Kind kind = node.getKind();
287        save(node.getCondition(), rec, kind, ASTPath.CONDITION);
288        save(node.getFalseExpression(), rec, kind, ASTPath.FALSE_EXPRESSION);
289        save(node.getTrueExpression(), rec, kind, ASTPath.TRUE_EXPRESSION);
290        return defaultAction(node, rec);
291      }
292
293      @Override
294      public Void visitDoWhileLoop(DoWhileLoopTree node,
295          ASTRecord rec) {
296        Kind kind = node.getKind();
297        save(node.getCondition(), rec, kind, ASTPath.CONDITION);
298        save(node.getStatement(), rec, kind, ASTPath.STATEMENT);
299        return defaultAction(node, rec);
300      }
301
302      @Override
303      public Void visitExpressionStatement(ExpressionStatementTree node,
304          ASTRecord rec) {
305        save(node.getExpression(), rec, node.getKind(), ASTPath.EXPRESSION);
306        return defaultAction(node, rec);
307      }
308
309      @Override
310      public Void visitEnhancedForLoop(EnhancedForLoopTree node,
311          ASTRecord rec) {
312        Kind kind = node.getKind();
313        save(node.getVariable(), rec, kind, ASTPath.VARIABLE);
314        save(node.getExpression(), rec, kind, ASTPath.EXPRESSION);
315        save(node.getStatement(), rec, kind, ASTPath.STATEMENT);
316        return defaultAction(node, rec);
317      }
318
319      @Override
320      public Void visitForLoop(ForLoopTree node, ASTRecord rec) {
321        Kind kind = node.getKind();
322        saveAll(node.getInitializer(), rec, kind, ASTPath.INITIALIZER);
323        save(node.getCondition(), rec, kind, ASTPath.CONDITION);
324        save(node.getStatement(), rec, kind, ASTPath.STATEMENT);
325        saveAll(node.getUpdate(), rec, kind, ASTPath.UPDATE);
326        return defaultAction(node, rec);
327      }
328
329      @Override
330      public Void visitIf(IfTree node, ASTRecord rec) {
331        Kind kind = node.getKind();
332        save(node.getCondition(), rec, kind, ASTPath.CONDITION);
333        save(node.getThenStatement(), rec, kind, ASTPath.THEN_STATEMENT);
334        save(node.getElseStatement(), rec, kind, ASTPath.ELSE_STATEMENT);
335        return defaultAction(node, rec);
336      }
337
338      @Override
339      public Void visitArrayAccess(ArrayAccessTree node,
340          ASTRecord rec) {
341        Kind kind = node.getKind();
342        save(node.getExpression(), rec, kind, ASTPath.EXPRESSION);
343        save(node.getIndex(), rec, kind, ASTPath.INDEX);
344        return defaultAction(node, rec);
345      }
346
347      @Override
348      public Void visitLabeledStatement(LabeledStatementTree node,
349          ASTRecord rec) {
350        save(node.getStatement(), rec, node.getKind(), ASTPath.STATEMENT);
351        return defaultAction(node, rec);
352      }
353
354      @Override
355      public Void visitMethod(MethodTree node, ASTRecord rec) {
356        Kind kind = node.getKind();
357        Tree rcvr = node.getReceiverParameter();
358        ModifiersTree mods = node.getModifiers();
359        List<? extends Tree> params = node.getParameters();
360        String outMethod = inMethod;
361        inMethod = JVMNames.getJVMMethodName(node);
362        rec = new ASTRecord(cut, rec.className, inMethod, null,
363            ASTPath.empty());
364        if (mods != null) {
365          save(mods, rec, kind, ASTPath.MODIFIERS);
366        }
367        if (rcvr != null) {
368          rcvr.accept(this, rec.extend(kind, ASTPath.PARAMETER, -1));
369        }
370        if (params != null && !params.isEmpty()) {
371          Map<String, List<String>> map = formals.get(rec.className);
372          List<String> names = new ArrayList<String>(params.size());
373          int i = 0;
374          map.put(inMethod, names);
375          for (Tree param : params) {
376            if (param != null) {
377              names.add(((VariableTree) param).getName().toString());
378              param.accept(this,
379                  rec.extend(Tree.Kind.METHOD, ASTPath.PARAMETER, i++));
380            }
381          }
382        }
383        save(node.getReturnType(), rec, kind, ASTPath.TYPE);
384        saveAll(node.getTypeParameters(), rec, kind, ASTPath.TYPE_PARAMETER);
385        // save(node.getReceiverParameter(), rec, kind, ASTPath.PARAMETER, -1);
386        // saveAll(node.getParameters(), rec, kind, ASTPath.PARAMETER);
387        saveAll(node.getThrows(), rec, kind, ASTPath.THROWS);
388        save(node.getBody(), rec, kind, ASTPath.BODY);
389        inMethod = outMethod;
390        return defaultAction(node, rec);
391      }
392
393      @Override
394      public Void visitModifiers(ModifiersTree node, ASTRecord rec) {
395        Kind kind = node.getKind();
396        saveAll(node.getAnnotations(), rec, kind, ASTPath.ANNOTATION);
397        return defaultAction(node, rec);
398      }
399
400      @Override
401      public Void visitNewArray(NewArrayTree node, ASTRecord rec) {
402        Kind kind = node.getKind();
403        Tree type = node.getType();
404        int n = node.getDimensions().size();
405        do {
406          save(type, rec, kind, ASTPath.TYPE, n);
407        } while (--n > 0);
408        saveAll(node.getDimensions(), rec, kind, ASTPath.DIMENSION);
409        saveAll(node.getInitializers(), rec, kind, ASTPath.INITIALIZER);
410        return defaultAction(node, rec);
411      }
412
413      @Override
414      public Void visitNewClass(NewClassTree node, ASTRecord rec) {
415        JCTree.JCClassDecl classBody =
416            (JCTree.JCClassDecl) node.getClassBody();
417        Kind kind = node.getKind();
418        save(node.getEnclosingExpression(), rec, kind,
419            ASTPath.ENCLOSING_EXPRESSION);
420        saveAll(node.getTypeArguments(), rec, kind, ASTPath.TYPE_ARGUMENT);
421        save(node.getIdentifier(), rec, kind, ASTPath.IDENTIFIER);
422        saveAll(node.getArguments(), rec, kind, ASTPath.ARGUMENT);
423        if (classBody != null) {
424          Name name = classBody.getSimpleName();
425          String className = null;
426          if (name == null || name.toString().isEmpty()) {
427            int i = counters.pop();
428            counters.push(++i);
429            className = rec.className + "$" + i;
430          } else {
431            ClassSymbol sym = ((JCTree.JCClassDecl) classBody).sym;
432            String s = sym == null ? "" : sym.toString();
433            if (s.startsWith("<anonymous ")) {
434              int i = counters.pop();
435              counters.push(++i);
436              className = s.substring(11, s.length()-1);
437            } else {
438              className = rec.className + "$" + name;
439            }
440          }
441          counters.push(0);
442          classBody.accept(this,
443              new ASTRecord(cut, className, null, null, ASTPath.empty()));
444          counters.pop();
445        }
446        return defaultAction(node, rec);
447      }
448
449      @Override
450      public Void visitLambdaExpression(LambdaExpressionTree node,
451          ASTRecord rec) {
452        Kind kind = node.getKind();
453        String outMethod = inMethod;
454        Iterable<? extends Tree> nodes = node.getParameters();
455        if (nodes != null) {
456          int i = 0;
457          for (Tree t : nodes) {
458            ASTRecord newRec = rec.extend(kind, ASTPath.PARAMETER, i++);
459            Tree.Kind newKind = t.getKind();
460            if (newKind == Tree.Kind.VARIABLE) {
461              VariableTree vt = (VariableTree) t;
462              save(vt.getType(), newRec, newKind, ASTPath.TYPE);
463              save(vt.getInitializer(), newRec, newKind, ASTPath.INITIALIZER);
464              defaultAction(vt, newRec);
465            } else {
466              t.accept(this, rec.extend(kind, ASTPath.PARAMETER, i++));
467            }
468          }
469        }
470        save(node.getBody(), rec, kind, ASTPath.BODY);
471        inMethod = outMethod;
472        return defaultAction(node, rec);
473      }
474
475      @Override
476      public Void visitParenthesized(ParenthesizedTree node,
477          ASTRecord rec) {
478        save(node.getExpression(), rec, node.getKind(), ASTPath.EXPRESSION);
479        return defaultAction(node, rec);
480      }
481
482      @Override
483      public Void visitReturn(ReturnTree node, ASTRecord rec) {
484        save(node.getExpression(), rec, node.getKind(), ASTPath.EXPRESSION);
485        return defaultAction(node, rec);
486      }
487
488      @Override
489      public Void visitMemberSelect(MemberSelectTree node,
490          ASTRecord rec) {
491        save(node.getExpression(), rec, node.getKind(), ASTPath.EXPRESSION);
492        return defaultAction(node, rec);
493      }
494
495      @Override
496      public Void visitMemberReference(MemberReferenceTree node,
497          ASTRecord rec) {
498        Kind kind = node.getKind();
499        save(node.getQualifierExpression(), rec, kind,
500            ASTPath.QUALIFIER_EXPRESSION);
501        saveAll(node.getTypeArguments(), rec, kind, ASTPath.TYPE_ARGUMENT);
502        return defaultAction(node, rec);
503      }
504
505      @Override
506      public Void visitSwitch(SwitchTree node, ASTRecord rec) {
507        Kind kind = node.getKind();
508        save(node.getExpression(), rec, kind, ASTPath.EXPRESSION);
509        saveAll(node.getCases(), rec, kind, ASTPath.CASE);
510        return defaultAction(node, rec);
511      }
512
513      @Override
514      public Void visitSynchronized(SynchronizedTree node,
515          ASTRecord rec) {
516        Kind kind = node.getKind();
517        save(node.getExpression(), rec, kind, ASTPath.EXPRESSION);
518        save(node.getBlock(), rec, kind, ASTPath.BLOCK);
519        return defaultAction(node, rec);
520      }
521
522      @Override
523      public Void visitThrow(ThrowTree node, ASTRecord rec) {
524        save(node.getExpression(), rec, node.getKind(), ASTPath.EXPRESSION);
525        return defaultAction(node, rec);
526      }
527
528      @Override
529      public Void visitCompilationUnit(CompilationUnitTree node,
530          ASTRecord rec) {
531        for (Tree tree : node.getTypeDecls()) {
532          if (ASTPath.isClassEquiv(tree.getKind())) {
533            saveClass((ClassTree) tree);
534          }
535        }
536        return null;
537      }
538
539      @Override
540      public Void visitTry(TryTree node, ASTRecord rec) {
541        Kind kind = node.getKind();
542        saveAll(node.getResources(), rec, kind, ASTPath.RESOURCE);
543        save(node.getBlock(), rec, kind, ASTPath.BLOCK);
544        saveAll(node.getCatches(), rec, kind, ASTPath.CATCH);
545        save(node.getFinallyBlock(), rec, kind, ASTPath.FINALLY_BLOCK);
546        return defaultAction(node, rec);
547      }
548
549      @Override
550      public Void visitParameterizedType(ParameterizedTypeTree node,
551          ASTRecord rec) {
552        Kind kind = node.getKind();
553        save(node.getType(), rec, kind, ASTPath.TYPE);
554        saveAll(node.getTypeArguments(), rec, kind, ASTPath.TYPE_ARGUMENT);
555        return defaultAction(node, rec);
556      }
557
558      @Override
559      public Void visitUnionType(UnionTypeTree node, ASTRecord rec) {
560        saveAll(node.getTypeAlternatives(), rec, node.getKind(),
561            ASTPath.TYPE_ALTERNATIVE);
562        return defaultAction(node, rec);
563      }
564
565      @Override
566      public Void visitIntersectionType(IntersectionTypeTree node,
567          ASTRecord rec) {
568        saveAll(node.getBounds(), rec, node.getKind(), ASTPath.BOUND);
569        return defaultAction(node, rec);
570      }
571
572      @Override
573      public Void visitArrayType(ArrayTypeTree node, ASTRecord rec) {
574        save(node.getType(), rec, node.getKind(), ASTPath.TYPE);
575        return defaultAction(node, rec);
576      }
577
578      @Override
579      public Void visitTypeCast(TypeCastTree node, ASTRecord rec) {
580        Kind kind = node.getKind();
581        save(node.getType(), rec, kind, ASTPath.TYPE);
582        save(node.getExpression(), rec, kind, ASTPath.EXPRESSION);
583        return defaultAction(node, rec);
584      }
585
586      @Override
587      public Void visitTypeParameter(TypeParameterTree node,
588          ASTRecord rec) {
589        saveAll(node.getBounds(), rec, node.getKind(), ASTPath.BOUND);
590        return defaultAction(node, rec);
591      }
592
593      @Override
594      public Void visitInstanceOf(InstanceOfTree node, ASTRecord rec) {
595        Kind kind = node.getKind();
596        save(node.getExpression(), rec, kind, ASTPath.EXPRESSION);
597        save(node.getType(), rec, kind, ASTPath.TYPE);
598        return defaultAction(node, rec);
599      }
600
601      @Override
602      public Void visitUnary(UnaryTree node, ASTRecord rec) {
603        save(node.getExpression(), rec, node.getKind(), ASTPath.EXPRESSION);
604        return defaultAction(node, rec);
605      }
606
607      @Override
608      public Void visitVariable(VariableTree node, ASTRecord rec) {
609        Kind kind = node.getKind();
610        if (rec.methodName == null) {  // member field
611          rec = new ASTRecord(cut, rec.className, rec.methodName,
612              ((VariableTree) node).getName().toString(), rec.astPath);
613        }
614        save(node.getType(), rec, kind, ASTPath.TYPE);
615        save(node.getInitializer(), rec, kind, ASTPath.INITIALIZER);
616        return defaultAction(node, rec);
617      }
618
619      @Override
620      public Void visitWhileLoop(WhileLoopTree node, ASTRecord rec) {
621        Kind kind = node.getKind();
622        save(node.getCondition(), rec, kind, ASTPath.CONDITION);
623        save(node.getStatement(), rec, kind, ASTPath.STATEMENT);
624        return defaultAction(node, rec);
625      }
626
627      @Override
628      public Void visitWildcard(WildcardTree node, ASTRecord rec) {
629        save(node.getBound(), rec, node.getKind(), ASTPath.BOUND);
630        return defaultAction(node, rec);
631      }
632    }, null);
633  }
634
635  public static ASTRecord getASTPath(CompilationUnitTree cut, Tree node) {
636    return indexOf(cut).get(node);
637  }
638
639  public static TreePath getTreePath(CompilationUnitTree cut, ASTRecord rec) {
640    Tree node = getNode(cut, rec);
641    return node == null ? null : TreePath.getPath(cut, node);
642  }
643
644  public static Tree getNode(CompilationUnitTree cut, ASTRecord rec) {
645    Map<Tree, ASTRecord> fwdIndex = ((ASTIndex) indexOf(cut)).back;
646    Map<ASTRecord, Tree> revIndex =
647        ((BiMap<Tree, ASTRecord>) fwdIndex).inverse();
648    ExpressionTree et = cut.getPackageName();
649    String pkg = et == null ? "" : et.toString();
650    if (!pkg.isEmpty() && rec.className.indexOf('.') < 0) {
651      rec = new ASTRecord(cut, pkg + "." + rec.className,
652          rec.methodName, rec.varName, rec.astPath);
653    }
654    return revIndex.get(rec);
655  }
656
657  public static String getParameterName(CompilationUnitTree cut,
658      String className, String methodName, int index) {
659    try {
660      ASTIndex ai = (ASTIndex) ASTIndex.indexOf(cut);
661      return ai.formals.get(className).get(methodName).get(index);
662    } catch (NullPointerException ex) {
663      return null;
664    }
665  }
666
667  public static Integer getParameterIndex(CompilationUnitTree cut,
668      String className, String methodName, String varName) {
669    if (cut != null && className != null
670        && methodName != null && varName != null) {
671      // if it's already a number, return it
672      try {
673        return Integer.valueOf(varName);
674      } catch (NumberFormatException ex) {}
675      // otherwise, look through parameter list for string
676      try {
677        ASTIndex ai = (ASTIndex) ASTIndex.indexOf(cut);
678        List<String> names =
679            ai.formals.get(className).get(methodName);
680        int i = 0;
681        for (String name : names) {
682          if (varName.equals(name)) { return i; }
683          ++i;
684        }
685      } catch (NullPointerException ex) {}
686    }
687    // not found
688    return null;
689  }
690
691  @Override
692  public String toString() {
693    StringBuilder sb = new StringBuilder();
694    for (Map.Entry<Tree, ASTRecord> entry : entrySet()) {
695      sb.append(entry.getKey().toString().replaceAll("\\s+", " "))
696        .append(" # ").append(entry.getValue()).append("\n");
697    }
698    return sb.toString();
699  }
700}
701