Home | History | Annotate | Download | only in io
      1 package annotations.io;
      2 
      3 import java.util.ArrayDeque;
      4 import java.util.ArrayList;
      5 import java.util.Deque;
      6 import java.util.HashMap;
      7 import java.util.List;
      8 import java.util.Map;
      9 
     10 import javax.lang.model.element.Name;
     11 
     12 import com.google.common.collect.BiMap;
     13 import com.google.common.collect.HashBiMap;
     14 import com.sun.source.tree.AnnotatedTypeTree;
     15 import com.sun.source.tree.AnnotationTree;
     16 import com.sun.source.tree.ArrayAccessTree;
     17 import com.sun.source.tree.ArrayTypeTree;
     18 import com.sun.source.tree.AssertTree;
     19 import com.sun.source.tree.AssignmentTree;
     20 import com.sun.source.tree.BinaryTree;
     21 import com.sun.source.tree.BlockTree;
     22 import com.sun.source.tree.CaseTree;
     23 import com.sun.source.tree.CatchTree;
     24 import com.sun.source.tree.ClassTree;
     25 import com.sun.source.tree.CompilationUnitTree;
     26 import com.sun.source.tree.CompoundAssignmentTree;
     27 import com.sun.source.tree.ConditionalExpressionTree;
     28 import com.sun.source.tree.DoWhileLoopTree;
     29 import com.sun.source.tree.EnhancedForLoopTree;
     30 import com.sun.source.tree.ExpressionStatementTree;
     31 import com.sun.source.tree.ExpressionTree;
     32 import com.sun.source.tree.ForLoopTree;
     33 import com.sun.source.tree.IfTree;
     34 import com.sun.source.tree.InstanceOfTree;
     35 import com.sun.source.tree.IntersectionTypeTree;
     36 import com.sun.source.tree.LabeledStatementTree;
     37 import com.sun.source.tree.LambdaExpressionTree;
     38 import com.sun.source.tree.MemberReferenceTree;
     39 import com.sun.source.tree.MemberSelectTree;
     40 import com.sun.source.tree.MethodInvocationTree;
     41 import com.sun.source.tree.MethodTree;
     42 import com.sun.source.tree.ModifiersTree;
     43 import com.sun.source.tree.NewArrayTree;
     44 import com.sun.source.tree.NewClassTree;
     45 import com.sun.source.tree.ParameterizedTypeTree;
     46 import com.sun.source.tree.ParenthesizedTree;
     47 import com.sun.source.tree.ReturnTree;
     48 import com.sun.source.tree.SwitchTree;
     49 import com.sun.source.tree.SynchronizedTree;
     50 import com.sun.source.tree.ThrowTree;
     51 import com.sun.source.tree.Tree;
     52 import com.sun.source.tree.Tree.Kind;
     53 import com.sun.source.tree.TryTree;
     54 import com.sun.source.tree.TypeCastTree;
     55 import com.sun.source.tree.TypeParameterTree;
     56 import com.sun.source.tree.UnaryTree;
     57 import com.sun.source.tree.UnionTypeTree;
     58 import com.sun.source.tree.VariableTree;
     59 import com.sun.source.tree.WhileLoopTree;
     60 import com.sun.source.tree.WildcardTree;
     61 import com.sun.source.util.SimpleTreeVisitor;
     62 import com.sun.source.util.TreePath;
     63 import com.sun.tools.javac.code.Symbol.ClassSymbol;
     64 import com.sun.tools.javac.tree.JCTree;
     65 
     66 import annotations.util.JVMNames;
     67 import 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  */
     74 public 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