Java程序  |  701行  |  25.01 KB

package annotations.io;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.lang.model.element.Name;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.sun.source.tree.AnnotatedTypeTree;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.ArrayTypeTree;
import com.sun.source.tree.AssertTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.CaseTree;
import com.sun.source.tree.CatchTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ConditionalExpressionTree;
import com.sun.source.tree.DoWhileLoopTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.ForLoopTree;
import com.sun.source.tree.IfTree;
import com.sun.source.tree.InstanceOfTree;
import com.sun.source.tree.IntersectionTypeTree;
import com.sun.source.tree.LabeledStatementTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ParameterizedTypeTree;
import com.sun.source.tree.ParenthesizedTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.SwitchTree;
import com.sun.source.tree.SynchronizedTree;
import com.sun.source.tree.ThrowTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.TryTree;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.UnaryTree;
import com.sun.source.tree.UnionTypeTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.tree.WhileLoopTree;
import com.sun.source.tree.WildcardTree;
import com.sun.source.util.SimpleTreeVisitor;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.tree.JCTree;

import annotations.util.JVMNames;
import annotations.util.coll.WrapperMap;

/**
 * Cache of {@code ASTPath} data for the nodes of a compilation unit tree.
 *
 * @author dbro
 */
public class ASTIndex extends WrapperMap<Tree, ASTRecord> {
  // single-item cache
  private static Tree cachedRoot = null;
  private static Map<Tree, ASTRecord> cachedIndex = null;
  private static final int EXPECTED_SIZE = 128;

  private final CompilationUnitTree cut;
  private final Map<String, Map<String, List<String>>> formals;

  /**
   * Maps source trees in compilation unit to corresponding AST paths.
   *
   * @param root compilation unit to be indexed
   * @return map of trees in compilation unit to AST paths
   */
  public static Map<Tree, ASTRecord> indexOf(CompilationUnitTree root) {
    if (cachedRoot == null || !cachedRoot.equals(root)) {
      cachedRoot = root;
      cachedIndex = new ASTIndex(root);
    }
    return cachedIndex;
  }

  private ASTIndex(CompilationUnitTree root) {
    super(HashBiMap.<Tree, ASTRecord>create(EXPECTED_SIZE));
    cut = root;
    formals = new HashMap<String, Map<String, List<String>>>();

    // The visitor implementation is slightly complicated by the
    // inclusion of information from both parent and child nodes in each
    // ASTEntry.  The pattern for most node types is to call save() and
    // saveAll() as needed to handle the node's descendants and finally
    // to invoke defaultAction() to save the entry for the current node.
    // (If the JVM could take advantage of tail recursion, it would be
    // better to save the current node's entry first, at a small cost to
    // the clarity of the code.)
    cut.accept(new SimpleTreeVisitor<Void, ASTRecord>() {
      Deque<Integer> counters = new ArrayDeque<Integer>();
      String inMethod = null;

      private void save(Tree node, ASTRecord rec,
          Kind kind, String sel) {
        if (node != null) {
          node.accept(this, rec.extend(kind, sel));
        }
      }

      private void save(Tree node, ASTRecord rec,
          Kind kind, String sel, int arg) {
        if (node != null) {
          node.accept(this, rec.extend(kind, sel, arg));
        }
      }

      private void saveAll(Iterable<? extends Tree> nodes,
          ASTRecord rec, Kind kind, String sel) {
        if (nodes != null) {
          int i = 0;
          for (Tree node : nodes) {
            save(node, rec, kind, sel, i++);
          }
        }
      }

      private void saveClass(ClassTree node) {
        String className =
            ((JCTree.JCClassDecl) node).sym.flatname.toString();
        ASTRecord rec =
            new ASTRecord(cut, className, null, null, ASTPath.empty());
        counters.push(0);
        node.accept(this, rec);
        counters.pop();
      }

      @Override
      public Void defaultAction(Tree node, ASTRecord rec) {
        switch (node.getKind()) {
        case BREAK:
        case COMPILATION_UNIT:
        case CONTINUE:
        case IMPORT:
        case MODIFIERS:
          break;  // not handled
        default:
          put(node, rec);
        }
        return null;
      }

      @Override
      public Void visitAnnotatedType(AnnotatedTypeTree node,
          ASTRecord rec) {
        Kind kind = node.getKind();
        saveAll(node.getAnnotations(), rec, kind, ASTPath.ANNOTATION);
        save(node.getUnderlyingType(), rec, kind, ASTPath.UNDERLYING_TYPE);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitAnnotation(AnnotationTree node,
          ASTRecord rec) {
        Kind kind = node.getKind();
        save(node.getAnnotationType(), rec, kind, ASTPath.TYPE);
        saveAll(node.getArguments(), rec, kind, ASTPath.ARGUMENT);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitMethodInvocation(MethodInvocationTree node,
          ASTRecord rec) {
        Kind kind = node.getKind();
        saveAll(node.getTypeArguments(), rec, kind, ASTPath.TYPE_ARGUMENT);
        save(node.getMethodSelect(), rec, kind, ASTPath.METHOD_SELECT);
        saveAll(node.getArguments(), rec, kind, ASTPath.ARGUMENT);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitAssert(AssertTree node, ASTRecord rec) {
        Kind kind = node.getKind();
        save(node.getCondition(), rec, kind, ASTPath.CONDITION);
        save(node.getDetail(), rec, kind, ASTPath.DETAIL);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitAssignment(AssignmentTree node, ASTRecord rec) {
        Kind kind = node.getKind();
        save(node.getExpression(), rec, kind, ASTPath.EXPRESSION);
        save(node.getVariable(), rec, kind, ASTPath.VARIABLE);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitCompoundAssignment(CompoundAssignmentTree node,
          ASTRecord rec) {
        Kind kind = node.getKind();
        save(node.getExpression(), rec, kind, ASTPath.EXPRESSION);
        save(node.getVariable(), rec, kind, ASTPath.VARIABLE);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitBinary(BinaryTree node, ASTRecord rec) {
        Kind kind = node.getKind();
        save(node.getLeftOperand(), rec, kind, ASTPath.LEFT_OPERAND);
        save(node.getRightOperand(), rec, kind, ASTPath.RIGHT_OPERAND);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitBlock(BlockTree node, ASTRecord rec) {
        Iterable<? extends Tree> nodes = node.getStatements();
        if (nodes != null) {
          int i = 0;
          for (Tree stmt : nodes) {
            if (ASTPath.isClassEquiv(stmt.getKind())) {
              saveClass((ClassTree) stmt);
            } else {
              save(stmt, rec, node.getKind(), ASTPath.STATEMENT, i);
            }
            ++i;
          }
        }
        return defaultAction(node, rec);
      }

      @Override
      public Void visitCase(CaseTree node, ASTRecord rec) {
        Kind kind = node.getKind();
        save(node.getExpression(), rec, kind, ASTPath.EXPRESSION);
        saveAll(node.getStatements(), rec, kind, ASTPath.STATEMENT);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitCatch(CatchTree node, ASTRecord rec) {
        Kind kind = node.getKind();
        save(node.getBlock(), rec, kind, ASTPath.BLOCK);
        save(node.getParameter(), rec, kind, ASTPath.PARAMETER);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitClass(ClassTree node, ASTRecord rec) {
        Kind kind = Tree.Kind.CLASS;  // use for all class-equivalent kinds
        int i = 0;
        formals.put(rec.className, new HashMap<String, List<String>>());
        if (node.getSimpleName().length() > 0) {
          // don't save exts/impls for anonymous inner class
          save(node.getExtendsClause(), rec, kind, ASTPath.BOUND, -1);
          saveAll(node.getImplementsClause(), rec, kind, ASTPath.BOUND);
        }
        saveAll(node.getTypeParameters(), rec, kind, ASTPath.TYPE_PARAMETER);
        for (Tree member : node.getMembers()) {
          if (member.getKind() == Tree.Kind.BLOCK) {
            save(member, rec, kind, ASTPath.INITIALIZER, i++);
          } else if (ASTPath.isClassEquiv(member.getKind())) {
            String className =
                ((JCTree.JCClassDecl) member).sym.flatname.toString();
            member.accept(this,
                new ASTRecord(cut, className, null, null, ASTPath.empty()));
          } else {
            member.accept(this, rec);
          }
        }
        return defaultAction(node, rec);
      }

      @Override
      public Void visitConditionalExpression(ConditionalExpressionTree node,
          ASTRecord rec) {
        Kind kind = node.getKind();
        save(node.getCondition(), rec, kind, ASTPath.CONDITION);
        save(node.getFalseExpression(), rec, kind, ASTPath.FALSE_EXPRESSION);
        save(node.getTrueExpression(), rec, kind, ASTPath.TRUE_EXPRESSION);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitDoWhileLoop(DoWhileLoopTree node,
          ASTRecord rec) {
        Kind kind = node.getKind();
        save(node.getCondition(), rec, kind, ASTPath.CONDITION);
        save(node.getStatement(), rec, kind, ASTPath.STATEMENT);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitExpressionStatement(ExpressionStatementTree node,
          ASTRecord rec) {
        save(node.getExpression(), rec, node.getKind(), ASTPath.EXPRESSION);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitEnhancedForLoop(EnhancedForLoopTree node,
          ASTRecord rec) {
        Kind kind = node.getKind();
        save(node.getVariable(), rec, kind, ASTPath.VARIABLE);
        save(node.getExpression(), rec, kind, ASTPath.EXPRESSION);
        save(node.getStatement(), rec, kind, ASTPath.STATEMENT);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitForLoop(ForLoopTree node, ASTRecord rec) {
        Kind kind = node.getKind();
        saveAll(node.getInitializer(), rec, kind, ASTPath.INITIALIZER);
        save(node.getCondition(), rec, kind, ASTPath.CONDITION);
        save(node.getStatement(), rec, kind, ASTPath.STATEMENT);
        saveAll(node.getUpdate(), rec, kind, ASTPath.UPDATE);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitIf(IfTree node, ASTRecord rec) {
        Kind kind = node.getKind();
        save(node.getCondition(), rec, kind, ASTPath.CONDITION);
        save(node.getThenStatement(), rec, kind, ASTPath.THEN_STATEMENT);
        save(node.getElseStatement(), rec, kind, ASTPath.ELSE_STATEMENT);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitArrayAccess(ArrayAccessTree node,
          ASTRecord rec) {
        Kind kind = node.getKind();
        save(node.getExpression(), rec, kind, ASTPath.EXPRESSION);
        save(node.getIndex(), rec, kind, ASTPath.INDEX);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitLabeledStatement(LabeledStatementTree node,
          ASTRecord rec) {
        save(node.getStatement(), rec, node.getKind(), ASTPath.STATEMENT);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitMethod(MethodTree node, ASTRecord rec) {
        Kind kind = node.getKind();
        Tree rcvr = node.getReceiverParameter();
        ModifiersTree mods = node.getModifiers();
        List<? extends Tree> params = node.getParameters();
        String outMethod = inMethod;
        inMethod = JVMNames.getJVMMethodName(node);
        rec = new ASTRecord(cut, rec.className, inMethod, null,
            ASTPath.empty());
        if (mods != null) {
          save(mods, rec, kind, ASTPath.MODIFIERS);
        }
        if (rcvr != null) {
          rcvr.accept(this, rec.extend(kind, ASTPath.PARAMETER, -1));
        }
        if (params != null && !params.isEmpty()) {
          Map<String, List<String>> map = formals.get(rec.className);
          List<String> names = new ArrayList<String>(params.size());
          int i = 0;
          map.put(inMethod, names);
          for (Tree param : params) {
            if (param != null) {
              names.add(((VariableTree) param).getName().toString());
              param.accept(this,
                  rec.extend(Tree.Kind.METHOD, ASTPath.PARAMETER, i++));
            }
          }
        }
        save(node.getReturnType(), rec, kind, ASTPath.TYPE);
        saveAll(node.getTypeParameters(), rec, kind, ASTPath.TYPE_PARAMETER);
        // save(node.getReceiverParameter(), rec, kind, ASTPath.PARAMETER, -1);
        // saveAll(node.getParameters(), rec, kind, ASTPath.PARAMETER);
        saveAll(node.getThrows(), rec, kind, ASTPath.THROWS);
        save(node.getBody(), rec, kind, ASTPath.BODY);
        inMethod = outMethod;
        return defaultAction(node, rec);
      }

      @Override
      public Void visitModifiers(ModifiersTree node, ASTRecord rec) {
        Kind kind = node.getKind();
        saveAll(node.getAnnotations(), rec, kind, ASTPath.ANNOTATION);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitNewArray(NewArrayTree node, ASTRecord rec) {
        Kind kind = node.getKind();
        Tree type = node.getType();
        int n = node.getDimensions().size();
        do {
          save(type, rec, kind, ASTPath.TYPE, n);
        } while (--n > 0);
        saveAll(node.getDimensions(), rec, kind, ASTPath.DIMENSION);
        saveAll(node.getInitializers(), rec, kind, ASTPath.INITIALIZER);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitNewClass(NewClassTree node, ASTRecord rec) {
        JCTree.JCClassDecl classBody =
            (JCTree.JCClassDecl) node.getClassBody();
        Kind kind = node.getKind();
        save(node.getEnclosingExpression(), rec, kind,
            ASTPath.ENCLOSING_EXPRESSION);
        saveAll(node.getTypeArguments(), rec, kind, ASTPath.TYPE_ARGUMENT);
        save(node.getIdentifier(), rec, kind, ASTPath.IDENTIFIER);
        saveAll(node.getArguments(), rec, kind, ASTPath.ARGUMENT);
        if (classBody != null) {
          Name name = classBody.getSimpleName();
          String className = null;
          if (name == null || name.toString().isEmpty()) {
            int i = counters.pop();
            counters.push(++i);
            className = rec.className + "$" + i;
          } else {
            ClassSymbol sym = ((JCTree.JCClassDecl) classBody).sym;
            String s = sym == null ? "" : sym.toString();
            if (s.startsWith("<anonymous ")) {
              int i = counters.pop();
              counters.push(++i);
              className = s.substring(11, s.length()-1);
            } else {
              className = rec.className + "$" + name;
            }
          }
          counters.push(0);
          classBody.accept(this,
              new ASTRecord(cut, className, null, null, ASTPath.empty()));
          counters.pop();
        }
        return defaultAction(node, rec);
      }

      @Override
      public Void visitLambdaExpression(LambdaExpressionTree node,
          ASTRecord rec) {
        Kind kind = node.getKind();
        String outMethod = inMethod;
        Iterable<? extends Tree> nodes = node.getParameters();
        if (nodes != null) {
          int i = 0;
          for (Tree t : nodes) {
            ASTRecord newRec = rec.extend(kind, ASTPath.PARAMETER, i++);
            Tree.Kind newKind = t.getKind();
            if (newKind == Tree.Kind.VARIABLE) {
              VariableTree vt = (VariableTree) t;
              save(vt.getType(), newRec, newKind, ASTPath.TYPE);
              save(vt.getInitializer(), newRec, newKind, ASTPath.INITIALIZER);
              defaultAction(vt, newRec);
            } else {
              t.accept(this, rec.extend(kind, ASTPath.PARAMETER, i++));
            }
          }
        }
        save(node.getBody(), rec, kind, ASTPath.BODY);
        inMethod = outMethod;
        return defaultAction(node, rec);
      }

      @Override
      public Void visitParenthesized(ParenthesizedTree node,
          ASTRecord rec) {
        save(node.getExpression(), rec, node.getKind(), ASTPath.EXPRESSION);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitReturn(ReturnTree node, ASTRecord rec) {
        save(node.getExpression(), rec, node.getKind(), ASTPath.EXPRESSION);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitMemberSelect(MemberSelectTree node,
          ASTRecord rec) {
        save(node.getExpression(), rec, node.getKind(), ASTPath.EXPRESSION);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitMemberReference(MemberReferenceTree node,
          ASTRecord rec) {
        Kind kind = node.getKind();
        save(node.getQualifierExpression(), rec, kind,
            ASTPath.QUALIFIER_EXPRESSION);
        saveAll(node.getTypeArguments(), rec, kind, ASTPath.TYPE_ARGUMENT);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitSwitch(SwitchTree node, ASTRecord rec) {
        Kind kind = node.getKind();
        save(node.getExpression(), rec, kind, ASTPath.EXPRESSION);
        saveAll(node.getCases(), rec, kind, ASTPath.CASE);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitSynchronized(SynchronizedTree node,
          ASTRecord rec) {
        Kind kind = node.getKind();
        save(node.getExpression(), rec, kind, ASTPath.EXPRESSION);
        save(node.getBlock(), rec, kind, ASTPath.BLOCK);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitThrow(ThrowTree node, ASTRecord rec) {
        save(node.getExpression(), rec, node.getKind(), ASTPath.EXPRESSION);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitCompilationUnit(CompilationUnitTree node,
          ASTRecord rec) {
        for (Tree tree : node.getTypeDecls()) {
          if (ASTPath.isClassEquiv(tree.getKind())) {
            saveClass((ClassTree) tree);
          }
        }
        return null;
      }

      @Override
      public Void visitTry(TryTree node, ASTRecord rec) {
        Kind kind = node.getKind();
        saveAll(node.getResources(), rec, kind, ASTPath.RESOURCE);
        save(node.getBlock(), rec, kind, ASTPath.BLOCK);
        saveAll(node.getCatches(), rec, kind, ASTPath.CATCH);
        save(node.getFinallyBlock(), rec, kind, ASTPath.FINALLY_BLOCK);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitParameterizedType(ParameterizedTypeTree node,
          ASTRecord rec) {
        Kind kind = node.getKind();
        save(node.getType(), rec, kind, ASTPath.TYPE);
        saveAll(node.getTypeArguments(), rec, kind, ASTPath.TYPE_ARGUMENT);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitUnionType(UnionTypeTree node, ASTRecord rec) {
        saveAll(node.getTypeAlternatives(), rec, node.getKind(),
            ASTPath.TYPE_ALTERNATIVE);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitIntersectionType(IntersectionTypeTree node,
          ASTRecord rec) {
        saveAll(node.getBounds(), rec, node.getKind(), ASTPath.BOUND);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitArrayType(ArrayTypeTree node, ASTRecord rec) {
        save(node.getType(), rec, node.getKind(), ASTPath.TYPE);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitTypeCast(TypeCastTree node, ASTRecord rec) {
        Kind kind = node.getKind();
        save(node.getType(), rec, kind, ASTPath.TYPE);
        save(node.getExpression(), rec, kind, ASTPath.EXPRESSION);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitTypeParameter(TypeParameterTree node,
          ASTRecord rec) {
        saveAll(node.getBounds(), rec, node.getKind(), ASTPath.BOUND);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitInstanceOf(InstanceOfTree node, ASTRecord rec) {
        Kind kind = node.getKind();
        save(node.getExpression(), rec, kind, ASTPath.EXPRESSION);
        save(node.getType(), rec, kind, ASTPath.TYPE);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitUnary(UnaryTree node, ASTRecord rec) {
        save(node.getExpression(), rec, node.getKind(), ASTPath.EXPRESSION);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitVariable(VariableTree node, ASTRecord rec) {
        Kind kind = node.getKind();
        if (rec.methodName == null) {  // member field
          rec = new ASTRecord(cut, rec.className, rec.methodName,
              ((VariableTree) node).getName().toString(), rec.astPath);
        }
        save(node.getType(), rec, kind, ASTPath.TYPE);
        save(node.getInitializer(), rec, kind, ASTPath.INITIALIZER);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitWhileLoop(WhileLoopTree node, ASTRecord rec) {
        Kind kind = node.getKind();
        save(node.getCondition(), rec, kind, ASTPath.CONDITION);
        save(node.getStatement(), rec, kind, ASTPath.STATEMENT);
        return defaultAction(node, rec);
      }

      @Override
      public Void visitWildcard(WildcardTree node, ASTRecord rec) {
        save(node.getBound(), rec, node.getKind(), ASTPath.BOUND);
        return defaultAction(node, rec);
      }
    }, null);
  }

  public static ASTRecord getASTPath(CompilationUnitTree cut, Tree node) {
    return indexOf(cut).get(node);
  }

  public static TreePath getTreePath(CompilationUnitTree cut, ASTRecord rec) {
    Tree node = getNode(cut, rec);
    return node == null ? null : TreePath.getPath(cut, node);
  }

  public static Tree getNode(CompilationUnitTree cut, ASTRecord rec) {
    Map<Tree, ASTRecord> fwdIndex = ((ASTIndex) indexOf(cut)).back;
    Map<ASTRecord, Tree> revIndex =
        ((BiMap<Tree, ASTRecord>) fwdIndex).inverse();
    ExpressionTree et = cut.getPackageName();
    String pkg = et == null ? "" : et.toString();
    if (!pkg.isEmpty() && rec.className.indexOf('.') < 0) {
      rec = new ASTRecord(cut, pkg + "." + rec.className,
          rec.methodName, rec.varName, rec.astPath);
    }
    return revIndex.get(rec);
  }

  public static String getParameterName(CompilationUnitTree cut,
      String className, String methodName, int index) {
    try {
      ASTIndex ai = (ASTIndex) ASTIndex.indexOf(cut);
      return ai.formals.get(className).get(methodName).get(index);
    } catch (NullPointerException ex) {
      return null;
    }
  }

  public static Integer getParameterIndex(CompilationUnitTree cut,
      String className, String methodName, String varName) {
    if (cut != null && className != null
        && methodName != null && varName != null) {
      // if it's already a number, return it
      try {
        return Integer.valueOf(varName);
      } catch (NumberFormatException ex) {}
      // otherwise, look through parameter list for string
      try {
        ASTIndex ai = (ASTIndex) ASTIndex.indexOf(cut);
        List<String> names =
            ai.formals.get(className).get(methodName);
        int i = 0;
        for (String name : names) {
          if (varName.equals(name)) { return i; }
          ++i;
        }
      } catch (NullPointerException ex) {}
    }
    // not found
    return null;
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    for (Map.Entry<Tree, ASTRecord> entry : entrySet()) {
      sb.append(entry.getKey().toString().replaceAll("\\s+", " "))
        .append(" # ").append(entry.getValue()).append("\n");
    }
    return sb.toString();
  }
}