1 package annotations.io; 2 3 import java.util.ArrayDeque; 4 import java.util.Deque; 5 6 import com.sun.source.tree.ClassTree; 7 import com.sun.source.tree.CompilationUnitTree; 8 import com.sun.source.tree.MethodTree; 9 import com.sun.source.tree.Tree; 10 import com.sun.source.tree.Tree.Kind; 11 import com.sun.source.tree.VariableTree; 12 import com.sun.source.util.TreePath; 13 14 /** 15 * Structure bundling an {@link ASTPath} with information about its 16 * starting point. Necessary because the {@link ASTPath} structure 17 * does not include the declaration from which it originates. 18 * 19 * @author dbro 20 */ 21 public class ASTRecord implements Comparable<ASTRecord> { 22 /** 23 * The AST to which this {@code ASTRecord} pertains. 24 */ 25 public final CompilationUnitTree ast; 26 27 /** 28 * Name of the enclosing class declaration. 29 */ 30 public final String className; 31 32 /** 33 * Name of the enclosing method declaration, or null if there is none. 34 */ 35 public final String methodName; 36 37 /** 38 * Name of the enclosing variable declaration, or null if there is none. 39 */ 40 public final String varName; 41 42 /** 43 * Path through AST, from specified declaration to descendant node. 44 */ 45 public final ASTPath astPath; 46 ASTRecord(CompilationUnitTree ast, String className, String methodName, String varName, ASTPath astPath)47 public ASTRecord(CompilationUnitTree ast, String className, 48 String methodName, String varName, ASTPath astPath) { 49 this.ast = ast; 50 this.className = className; 51 this.methodName = methodName; 52 this.varName = varName; 53 // FIXME: ensure path is canonical 54 if (varName != null) { 55 // TODO? 56 } else if (methodName != null) { 57 int n = astPath.size(); 58 if (n > 0 && astPath.get(0).getTreeKind() != Tree.Kind.METHOD 59 && astPath.get(0).getTreeKind() != Tree.Kind.VARIABLE) { 60 ASTPath bodyPath = ASTPath.empty().add( 61 new ASTPath.ASTEntry(Tree.Kind.METHOD, ASTPath.BODY)); 62 for (int i = 0; i < n; i++) { bodyPath = bodyPath.add(astPath.get(i)); } 63 astPath = bodyPath; 64 } 65 } 66 this.astPath = astPath; 67 } 68 newArrayLevel(int depth)69 public ASTRecord newArrayLevel(int depth) { 70 return new ASTRecord(ast, className, methodName, varName, 71 astPath.extendNewArray(depth)); 72 } 73 replacePath(ASTPath newPath)74 public ASTRecord replacePath(ASTPath newPath) { 75 return new ASTRecord(ast, className, methodName, varName, newPath); 76 } 77 78 @Override equals(Object o)79 public boolean equals(Object o) { 80 return o instanceof ASTRecord && equals((ASTRecord) o); 81 } 82 equals(ASTRecord astRecord)83 public boolean equals(ASTRecord astRecord) { 84 return compareTo(astRecord) == 0; 85 } 86 87 @Override compareTo(ASTRecord rec)88 public int compareTo(ASTRecord rec) { 89 int d = ast == null 90 ? rec.ast == null ? 0 : -1 91 : rec.ast == null ? 1 : Integer 92 .compare(ast.hashCode(), rec.ast.hashCode()); 93 if (d == 0) { 94 d = className == null 95 ? rec.className == null ? 0 : -1 96 : rec.className == null ? 1 : className.compareTo(rec.className); 97 if (d == 0) { 98 d = methodName == null 99 ? rec.methodName == null ? 0 : -1 100 : rec.methodName == null ? 1 : methodName.compareTo(rec.methodName); 101 if (d == 0) { 102 d = varName == null 103 ? rec.varName == null ? 0 : -1 104 : rec.varName == null ? 1 : varName.compareTo(rec.varName); 105 if (d == 0) { 106 d = astPath == null 107 ? rec.astPath == null ? 0 : -1 108 : rec.astPath == null ? 1 : astPath.compareTo(rec.astPath); 109 } 110 } 111 } 112 } 113 return d; 114 } 115 116 @Override hashCode()117 public int hashCode() { 118 return ast.hashCode() 119 ^ (className == null ? 0 120 : Integer.rotateRight(className.hashCode(), 3)) 121 ^ (methodName == null ? 0 122 : Integer.rotateRight(methodName.hashCode(), 6)) 123 ^ (varName == null ? 0 124 : Integer.rotateRight(varName.hashCode(), 9)) 125 ^ (astPath == null ? 0 126 : Integer.rotateRight(astPath.hashCode(), 12)); 127 } 128 129 /** 130 * Indicates whether this record identifies the given {@link TreePath}. 131 */ matches(TreePath treePath)132 public boolean matches(TreePath treePath) { 133 String clazz = null; 134 String meth = null; 135 String var = null; 136 boolean matchVars = false; // members only! 137 Deque<Tree> stack = new ArrayDeque<Tree>(); 138 for (Tree tree : treePath) { stack.push(tree); } 139 while (!stack.isEmpty()) { 140 Tree tree = stack.pop(); 141 switch (tree.getKind()) { 142 case CLASS: 143 case INTERFACE: 144 case ENUM: 145 case ANNOTATION_TYPE: 146 clazz = ((ClassTree) tree).getSimpleName().toString(); 147 meth = null; 148 var = null; 149 matchVars = true; 150 break; 151 case METHOD: 152 assert meth == null; 153 meth = ((MethodTree) tree).getName().toString(); 154 matchVars = false; 155 break; 156 case VARIABLE: 157 if (matchVars) { 158 assert var == null; 159 var = ((VariableTree) tree).getName().toString(); 160 matchVars = false; 161 } 162 break; 163 default: 164 matchVars = false; 165 continue; 166 } 167 } 168 return className.equals(clazz) 169 && (methodName == null ? meth == null : methodName.equals(meth)) 170 && (varName == null ? var == null : varName.equals(var)) 171 && astPath.matches(treePath); 172 } 173 174 @Override toString()175 public String toString() { 176 return new StringBuilder() 177 .append(className == null ? "" : className).append(":") 178 .append(methodName == null ? "" : methodName).append(":") 179 .append(varName == null ? "" : varName).append(":") 180 .append(astPath).toString(); 181 } 182 extend(ASTPath.ASTEntry entry)183 public ASTRecord extend(ASTPath.ASTEntry entry) { 184 return new ASTRecord(ast, className, methodName, varName, 185 astPath.extend(entry)); 186 } 187 extend(Kind kind, String sel)188 public ASTRecord extend(Kind kind, String sel) { 189 return extend(new ASTPath.ASTEntry(kind, sel)); 190 } 191 extend(Kind kind, String sel, int arg)192 public ASTRecord extend(Kind kind, String sel, int arg) { 193 return extend(new ASTPath.ASTEntry(kind, sel, arg)); 194 } 195 } 196