package annotations.io; import java.util.ArrayDeque; import java.util.Deque; import com.sun.source.tree.ClassTree; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; import com.sun.source.tree.Tree.Kind; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; /** * Structure bundling an {@link ASTPath} with information about its * starting point. Necessary because the {@link ASTPath} structure * does not include the declaration from which it originates. * * @author dbro */ public class ASTRecord implements Comparable { /** * The AST to which this {@code ASTRecord} pertains. */ public final CompilationUnitTree ast; /** * Name of the enclosing class declaration. */ public final String className; /** * Name of the enclosing method declaration, or null if there is none. */ public final String methodName; /** * Name of the enclosing variable declaration, or null if there is none. */ public final String varName; /** * Path through AST, from specified declaration to descendant node. */ public final ASTPath astPath; public ASTRecord(CompilationUnitTree ast, String className, String methodName, String varName, ASTPath astPath) { this.ast = ast; this.className = className; this.methodName = methodName; this.varName = varName; // FIXME: ensure path is canonical if (varName != null) { // TODO? } else if (methodName != null) { int n = astPath.size(); if (n > 0 && astPath.get(0).getTreeKind() != Tree.Kind.METHOD && astPath.get(0).getTreeKind() != Tree.Kind.VARIABLE) { ASTPath bodyPath = ASTPath.empty().add( new ASTPath.ASTEntry(Tree.Kind.METHOD, ASTPath.BODY)); for (int i = 0; i < n; i++) { bodyPath = bodyPath.add(astPath.get(i)); } astPath = bodyPath; } } this.astPath = astPath; } public ASTRecord newArrayLevel(int depth) { return new ASTRecord(ast, className, methodName, varName, astPath.extendNewArray(depth)); } public ASTRecord replacePath(ASTPath newPath) { return new ASTRecord(ast, className, methodName, varName, newPath); } @Override public boolean equals(Object o) { return o instanceof ASTRecord && equals((ASTRecord) o); } public boolean equals(ASTRecord astRecord) { return compareTo(astRecord) == 0; } @Override public int compareTo(ASTRecord rec) { int d = ast == null ? rec.ast == null ? 0 : -1 : rec.ast == null ? 1 : Integer .compare(ast.hashCode(), rec.ast.hashCode()); if (d == 0) { d = className == null ? rec.className == null ? 0 : -1 : rec.className == null ? 1 : className.compareTo(rec.className); if (d == 0) { d = methodName == null ? rec.methodName == null ? 0 : -1 : rec.methodName == null ? 1 : methodName.compareTo(rec.methodName); if (d == 0) { d = varName == null ? rec.varName == null ? 0 : -1 : rec.varName == null ? 1 : varName.compareTo(rec.varName); if (d == 0) { d = astPath == null ? rec.astPath == null ? 0 : -1 : rec.astPath == null ? 1 : astPath.compareTo(rec.astPath); } } } } return d; } @Override public int hashCode() { return ast.hashCode() ^ (className == null ? 0 : Integer.rotateRight(className.hashCode(), 3)) ^ (methodName == null ? 0 : Integer.rotateRight(methodName.hashCode(), 6)) ^ (varName == null ? 0 : Integer.rotateRight(varName.hashCode(), 9)) ^ (astPath == null ? 0 : Integer.rotateRight(astPath.hashCode(), 12)); } /** * Indicates whether this record identifies the given {@link TreePath}. */ public boolean matches(TreePath treePath) { String clazz = null; String meth = null; String var = null; boolean matchVars = false; // members only! Deque stack = new ArrayDeque(); for (Tree tree : treePath) { stack.push(tree); } while (!stack.isEmpty()) { Tree tree = stack.pop(); switch (tree.getKind()) { case CLASS: case INTERFACE: case ENUM: case ANNOTATION_TYPE: clazz = ((ClassTree) tree).getSimpleName().toString(); meth = null; var = null; matchVars = true; break; case METHOD: assert meth == null; meth = ((MethodTree) tree).getName().toString(); matchVars = false; break; case VARIABLE: if (matchVars) { assert var == null; var = ((VariableTree) tree).getName().toString(); matchVars = false; } break; default: matchVars = false; continue; } } return className.equals(clazz) && (methodName == null ? meth == null : methodName.equals(meth)) && (varName == null ? var == null : varName.equals(var)) && astPath.matches(treePath); } @Override public String toString() { return new StringBuilder() .append(className == null ? "" : className).append(":") .append(methodName == null ? "" : methodName).append(":") .append(varName == null ? "" : varName).append(":") .append(astPath).toString(); } public ASTRecord extend(ASTPath.ASTEntry entry) { return new ASTRecord(ast, className, methodName, varName, astPath.extend(entry)); } public ASTRecord extend(Kind kind, String sel) { return extend(new ASTPath.ASTEntry(kind, sel)); } public ASTRecord extend(Kind kind, String sel, int arg) { return extend(new ASTPath.ASTEntry(kind, sel, arg)); } }