1 package annotator.find; 2 3 import java.io.IOException; 4 import java.util.ArrayList; 5 import java.util.Collection; 6 import java.util.HashMap; 7 import java.util.Iterator; 8 import java.util.List; 9 import java.util.Map; 10 import java.util.regex.Matcher; 11 import java.util.regex.Pattern; 12 13 import javax.lang.model.element.ElementKind; 14 import javax.lang.model.element.Modifier; 15 import javax.lang.model.element.Name; 16 import javax.lang.model.type.NullType; 17 18 import com.google.common.collect.LinkedHashMultimap; 19 import com.google.common.collect.Multimaps; 20 import com.google.common.collect.SetMultimap; 21 import com.sun.source.tree.AnnotatedTypeTree; 22 import com.sun.source.tree.AnnotationTree; 23 import com.sun.source.tree.ArrayTypeTree; 24 import com.sun.source.tree.ClassTree; 25 import com.sun.source.tree.CompilationUnitTree; 26 import com.sun.source.tree.ExpressionTree; 27 import com.sun.source.tree.IdentifierTree; 28 import com.sun.source.tree.InstanceOfTree; 29 import com.sun.source.tree.MemberSelectTree; 30 import com.sun.source.tree.MethodTree; 31 import com.sun.source.tree.ModifiersTree; 32 import com.sun.source.tree.NewArrayTree; 33 import com.sun.source.tree.NewClassTree; 34 import com.sun.source.tree.ParameterizedTypeTree; 35 import com.sun.source.tree.PrimitiveTypeTree; 36 import com.sun.source.tree.Tree; 37 import com.sun.source.tree.TypeCastTree; 38 import com.sun.source.tree.TypeParameterTree; 39 import com.sun.source.tree.VariableTree; 40 import com.sun.source.tree.WildcardTree; 41 import com.sun.source.util.TreePath; 42 import com.sun.source.util.TreeScanner; 43 44 import com.sun.tools.javac.code.Flags; 45 import com.sun.tools.javac.code.Symbol; 46 import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntry; 47 import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntryKind; 48 import com.sun.tools.javac.code.Types; 49 import com.sun.tools.javac.tree.JCTree; 50 import com.sun.tools.javac.tree.JCTree.JCAnnotatedType; 51 import com.sun.tools.javac.tree.JCTree.JCAnnotation; 52 import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree; 53 import com.sun.tools.javac.tree.JCTree.JCBlock; 54 import com.sun.tools.javac.tree.JCTree.JCClassDecl; 55 import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; 56 import com.sun.tools.javac.tree.JCTree.JCExpression; 57 import com.sun.tools.javac.tree.JCTree.JCFieldAccess; 58 import com.sun.tools.javac.tree.JCTree.JCIdent; 59 import com.sun.tools.javac.tree.JCTree.JCMethodDecl; 60 import com.sun.tools.javac.tree.JCTree.JCModifiers; 61 import com.sun.tools.javac.tree.JCTree.JCNewArray; 62 import com.sun.tools.javac.tree.JCTree.JCNewClass; 63 import com.sun.tools.javac.tree.JCTree.JCTypeApply; 64 import com.sun.tools.javac.tree.JCTree.JCTypeParameter; 65 import com.sun.tools.javac.tree.JCTree.JCVariableDecl; 66 import com.sun.tools.javac.tree.JCTree.JCWildcard; 67 import com.sun.tools.javac.util.Position; 68 69 import annotations.io.ASTIndex; 70 import annotations.io.ASTPath; 71 import annotations.io.ASTRecord; 72 import annotations.io.DebugWriter; 73 import annotator.Main; 74 import annotator.scanner.CommonScanner; 75 import annotator.specification.IndexFileSpecification; 76 77 import plume.Pair; 78 79 import type.DeclaredType; 80 import type.Type; 81 82 /** 83 * A {@link TreeScanner} that is able to locate program elements in an 84 * AST based on {@code Criteria}. {@link #getInsertionsByPosition(JCTree.JCCompilationUnit,List)} 85 * scans a tree and returns a 86 * mapping of source positions (as character offsets) to insertion text. 87 */ 88 public class TreeFinder extends TreeScanner<Void, List<Insertion>> { 89 public static final DebugWriter dbug = new DebugWriter(); 90 public static final DebugWriter stak = new DebugWriter(); 91 public static final DebugWriter warn = new DebugWriter(); 92 93 /** 94 * String representation of regular expression matching a comment in 95 * Java code. The part before {@code |} matches a single-line 96 * comment, and the part after matches a multi-line comment, which 97 * breaks down as follows (adapted from 98 * <a href="http://perldoc.perl.org/perlfaq6.html#How-do-I-use-a-regular-expression-to-strip-C-style-comments-from-a-file%3f">Perl FAQ</a>): 99 * <pre> 100 * /\* ## Start of comment 101 * [^*]*\*+ ## Non-* followed by 1-or-more *s 102 * ( 103 * [^/*][^*]*\*+ 104 * )* ## 0 or more things which don't start with / 105 * ## but do end with '*' 106 * / ## End of comment 107 * </pre> 108 * Note: Care must be taken to avoid false comment matches starting 109 * inside a string literal. Ensuring that the code segment being 110 * matched starts at an AST node boundary is sufficient to prevent 111 * this complication. 112 */ 113 private final static String comment = 114 "//.*$|/\\*[^*]*+\\*++(?:[^*/][^*]*+\\*++)*+/"; 115 116 /** 117 * Regular expression matching a character or string literal. 118 */ 119 private final static String literal = 120 "'(?:(?:\\\\(?:'|[^']*+))|[^\\\\'])'|\"(?:\\\\.|[^\\\\\"])*\""; 121 122 /** 123 * Regular expression matching a non-commented instance of {@code /} 124 * that is not part of a comment-starting delimiter. 125 */ 126 private final static String nonDelimSlash = "/(?=[^*/])"; 127 128 /** 129 * Returns regular expression matching "anything but" {@code c}: a 130 * single comment, character or string literal, or non-{@code c} 131 * character. 132 */ otherThan(char c)133 private final static String otherThan(char c) { 134 String cEscaped; 135 136 // escape if necessary for use in character class 137 switch (c) { 138 case '/': 139 case '"': 140 case '\'': 141 cEscaped = ""; break; // already present in class defn 142 case '\\': 143 case '[': 144 case ']': 145 cEscaped = "\\" + c; break; // escape! 146 default: 147 cEscaped = "" + c; 148 } 149 150 return "[^/'" + cEscaped + "\"]|" + "|" + literal + "|" + comment 151 + (c == '/' ? "" : nonDelimSlash); 152 } 153 154 // If this code location is not an array type, return null. Otherwise, 155 // starting at an array type, walk up the AST as long as still an array, 156 // and stop at the largest containing array (with nothing but arrays in 157 // between). largestContainingArray(TreePath p)158 public static TreePath largestContainingArray(TreePath p) { 159 if (p.getLeaf().getKind() != Tree.Kind.ARRAY_TYPE) { 160 return null; 161 } 162 while (p.getParentPath().getLeaf().getKind() == Tree.Kind.ARRAY_TYPE) { 163 p = p.getParentPath(); 164 } 165 assert p.getLeaf().getKind() == Tree.Kind.ARRAY_TYPE; 166 return p; 167 } 168 169 /** 170 * Returns the position of the first (non-commented) instance of a 171 * character at or after the given position. (Assumes position is not 172 * inside a comment.) 173 * 174 * @see #getNthInstanceBetween(char, int, int, int, CompilationUnitTree) 175 */ getFirstInstanceAfter(char c, int i)176 private int getFirstInstanceAfter(char c, int i) { 177 return getNthInstanceInRange(c, i, Integer.MAX_VALUE, 1); 178 } 179 180 /** 181 * Returns the position of the {@code n}th (non-commented, non-quoted) 182 * instance of a character between the given positions, or the last 183 * instance if {@code n==0}. (Assumes position is not inside a 184 * comment.) 185 * 186 * @param c the character being sought 187 * @param start position at which the search starts (inclusive) 188 * @param end position at which the search ends (exclusive) 189 * @param n number of repetitions, or 0 for last occurrence 190 * @return position of match in {@code tree}, or 191 * {@link Position.NOPOS} if match not found 192 */ getNthInstanceInRange(char c, int start, int end, int n)193 private int getNthInstanceInRange(char c, int start, int end, int n) { 194 if (end < 0) { 195 throw new IllegalArgumentException("negative end position"); 196 } 197 if (n < 0) { 198 throw new IllegalArgumentException("negative count"); 199 } 200 201 try { 202 CharSequence s = tree.getSourceFile().getCharContent(true); 203 int count = n; 204 int pos = Position.NOPOS; 205 int stop = Math.min(end, s.length()); 206 String cQuoted = c == '/' ? nonDelimSlash : Pattern.quote("" + c); 207 String regex = "(?:" + otherThan(c) + ")*+" + cQuoted; 208 Pattern p = Pattern.compile(regex, Pattern.MULTILINE); 209 Matcher m = p.matcher(s).region(start, stop); 210 211 // using n==0 for "last" ensures that {@code (--n == 0)} is always 212 // false, (reasonably) assuming no underflow 213 while (m.find()) { 214 pos = m.end() - 1; 215 if (--count == 0) { break; } 216 } 217 // positive count means search halted before nth instance was found 218 return count > 0 ? Position.NOPOS : pos; 219 } catch (IOException e) { 220 throw new RuntimeException(e); 221 } 222 } 223 224 // Find a node's parent in the current source tree. parent(Tree node)225 private Tree parent(Tree node) { 226 return getPath(node).getParentPath().getLeaf(); 227 } 228 229 /** 230 * An alternative to TreePath.getPath(CompilationUnitTree,Tree) that 231 * caches its results. 232 */ getPath(Tree target)233 public TreePath getPath(Tree target) { 234 if (treePathCache.containsKey(target)) { 235 return treePathCache.get(target); 236 } 237 TreePath result = TreePath.getPath(tree, target); 238 treePathCache.put(target, result); 239 return result; 240 } 241 242 Map<Tree, TreePath> treePathCache = new HashMap<Tree, TreePath>(); 243 astRecord(Tree node)244 private ASTRecord astRecord(Tree node) { 245 Map<Tree, ASTRecord> index = ASTIndex.indexOf(tree); 246 return index.get(node); 247 } 248 249 /** 250 * Determines the insertion position for type annotations on various 251 * elements. For instance, type annotations for a declaration should be 252 * placed before the type rather than the variable name. 253 */ 254 private class TypePositionFinder 255 extends TreeScanner<Pair<ASTRecord, Integer>, Insertion> { pathAndPos(JCTree t)256 private Pair<ASTRecord, Integer> pathAndPos(JCTree t) { 257 return Pair.of(astRecord(t), t.pos); 258 } 259 pathAndPos(JCTree t, int i)260 private Pair<ASTRecord, Integer> pathAndPos(JCTree t, int i) { 261 return Pair.of(astRecord(t), i); 262 } 263 264 /** @param t an expression for a type */ getBaseTypePosition(JCTree t)265 private Pair<ASTRecord, Integer> getBaseTypePosition(JCTree t) { 266 while (true) { 267 switch (t.getKind()) { 268 case IDENTIFIER: 269 case PRIMITIVE_TYPE: 270 return pathAndPos(t); 271 case MEMBER_SELECT: 272 JCTree exp = t; 273 do { // locate pkg name, if any 274 JCFieldAccess jfa = (JCFieldAccess) exp; 275 exp = jfa.getExpression(); 276 if (jfa.sym.isStatic()) { 277 return pathAndPos(exp, 278 getFirstInstanceAfter('.', 279 exp.getEndPosition(tree.endPositions)) + 1); 280 } 281 } while (exp instanceof JCFieldAccess 282 && ((JCFieldAccess) exp).sym.getKind() != ElementKind.PACKAGE); 283 if (exp != null) { 284 if (exp.getKind() == Tree.Kind.IDENTIFIER) { 285 Symbol sym = ((JCIdent) exp).sym; 286 if (!(sym.isStatic() || sym.getKind() == ElementKind.PACKAGE)) { 287 return pathAndPos(t, t.getStartPosition()); 288 } 289 } 290 t = exp; 291 } 292 return pathAndPos(t, 293 getFirstInstanceAfter('.', 294 t.getEndPosition(tree.endPositions)) + 1); 295 case ARRAY_TYPE: 296 t = ((JCArrayTypeTree) t).elemtype; 297 break; 298 case PARAMETERIZED_TYPE: 299 return pathAndPos(t, t.getStartPosition()); 300 case EXTENDS_WILDCARD: 301 case SUPER_WILDCARD: 302 t = ((JCWildcard) t).inner; 303 break; 304 case UNBOUNDED_WILDCARD: 305 // This is "?" as in "List<?>". ((JCWildcard) t).inner is null. 306 // There is nowhere to attach the annotation, so for now return 307 // the "?" tree itself. 308 return pathAndPos(t); 309 case ANNOTATED_TYPE: 310 // If this type already has annotations on it, get the underlying 311 // type, without annotations. 312 t = ((JCAnnotatedType) t).underlyingType; 313 break; 314 default: 315 throw new RuntimeException(String.format("Unrecognized type (kind=%s, class=%s): %s", t.getKind(), t.getClass(), t)); 316 } 317 } 318 } 319 320 @Override visitVariable(VariableTree node, Insertion ins)321 public Pair<ASTRecord, Integer> visitVariable(VariableTree node, Insertion ins) { 322 Name name = node.getName(); 323 JCVariableDecl jn = (JCVariableDecl) node; 324 JCTree jt = jn.getType(); 325 Criteria criteria = ins.getCriteria(); 326 dbug.debug("visitVariable: %s %s%n", jt, jt.getClass()); 327 if (name != null && criteria.isOnFieldDeclaration()) { 328 return Pair.of(astRecord(node), jn.getStartPosition()); 329 } 330 if (jt instanceof JCTypeApply) { 331 JCExpression type = ((JCTypeApply) jt).clazz; 332 return pathAndPos(type); 333 } 334 return Pair.of(astRecord(node), jn.pos); 335 } 336 337 // When a method is visited, it is visited for the receiver, not the 338 // return value and not the declaration itself. 339 @Override visitMethod(MethodTree node, Insertion ins)340 public Pair<ASTRecord, Integer> visitMethod(MethodTree node, Insertion ins) { 341 dbug.debug("TypePositionFinder.visitMethod%n"); 342 super.visitMethod(node, ins); 343 344 JCMethodDecl jcnode = (JCMethodDecl) node; 345 JCVariableDecl jcvar = (JCVariableDecl) node.getReceiverParameter(); 346 if (jcvar != null) { return pathAndPos(jcvar); } 347 348 int pos = Position.NOPOS; 349 ASTRecord astPath = astRecord(jcnode) 350 .extend(Tree.Kind.METHOD, ASTPath.PARAMETER, -1); 351 352 if (node.getParameters().isEmpty()) { 353 // no parameters; find first (uncommented) '(' after method name 354 pos = findMethodName(jcnode); 355 if (pos >= 0) { pos = getFirstInstanceAfter('(', pos); } 356 if (++pos <= 0) { 357 throw new RuntimeException("Couldn't find param opening paren for: " 358 + jcnode); 359 } 360 } else { 361 pos = ((JCTree) node.getParameters().get(0)).getStartPosition(); 362 } 363 return Pair.of(astPath, pos); 364 } 365 366 @Override visitIdentifier(IdentifierTree node, Insertion ins)367 public Pair<ASTRecord, Integer> visitIdentifier(IdentifierTree node, Insertion ins) { 368 dbug.debug("TypePositionFinder.visitIdentifier(%s)%n", node); 369 // for arrays, need to indent inside array, not right before type 370 ASTRecord rec = ASTIndex.indexOf(tree).get(node); 371 ASTPath astPath = ins.getCriteria().getASTPath(); 372 Tree parent = parent(node); 373 Integer i = null; 374 JCIdent jcnode = (JCIdent) node; 375 376 // ASTPathEntry.type _n_ is a special case because it does not 377 // correspond to a node in the AST. 378 if (parent.getKind() == Tree.Kind.NEW_ARRAY) { // NewArrayTree) 379 ASTPath.ASTEntry entry; 380 dbug.debug("TypePositionFinder.visitIdentifier: recognized array%n"); 381 if (astPath == null) { 382 entry = new ASTPath.ASTEntry(Tree.Kind.NEW_ARRAY, ASTPath.TYPE, 0); 383 astPath = astRecord(parent).extend(entry).astPath; 384 } else { 385 entry = astPath.get(astPath.size() - 1); // kind is NewArray 386 } 387 if (entry.childSelectorIs(ASTPath.TYPE)) { 388 int n = entry.getArgument(); 389 i = jcnode.getStartPosition(); 390 if (n < getDimsSize((JCExpression) parent)) { // else n == #dims 391 i = getNthInstanceInRange('[', i, 392 ((JCNewArray) parent).getEndPosition(tree.endPositions), n+1); 393 } 394 } 395 if (i == null) { 396 i = jcnode.getEndPosition(tree.endPositions); 397 } 398 } else if (parent.getKind() == Tree.Kind.NEW_CLASS) { // NewClassTree) 399 dbug.debug("TypePositionFinder.visitIdentifier: recognized class%n"); 400 JCNewClass nc = (JCNewClass) parent; 401 dbug.debug( 402 "TypePositionFinder.visitIdentifier: clazz %s (%d) constructor %s%n", 403 nc.clazz, nc.clazz.getPreferredPosition(), nc.constructor); 404 i = nc.clazz.getPreferredPosition(); 405 if (astPath == null) { 406 astPath = astRecord(node).astPath; 407 } 408 } else { 409 ASTRecord astRecord = astRecord(node); 410 astPath = astRecord.astPath; 411 i = ((JCIdent) node).pos; 412 } 413 414 dbug.debug("visitIdentifier(%s) => %d where parent (%s) = %s%n", 415 node, i, parent.getClass(), parent); 416 return Pair.of(rec.replacePath(astPath), i); 417 } 418 419 @Override visitMemberSelect(MemberSelectTree node, Insertion ins)420 public Pair<ASTRecord, Integer> visitMemberSelect(MemberSelectTree node, Insertion ins) { 421 dbug.debug("TypePositionFinder.visitMemberSelect(%s)%n", node); 422 JCFieldAccess raw = (JCFieldAccess) node; 423 return Pair.of(astRecord(node), 424 raw.getEndPosition(tree.endPositions) - raw.name.length()); 425 } 426 427 @Override visitTypeParameter(TypeParameterTree node, Insertion ins)428 public Pair<ASTRecord, Integer> visitTypeParameter(TypeParameterTree node, Insertion ins) { 429 JCTypeParameter tp = (JCTypeParameter) node; 430 return Pair.of(astRecord(node), tp.getStartPosition()); 431 } 432 433 @Override visitWildcard(WildcardTree node, Insertion ins)434 public Pair<ASTRecord, Integer> visitWildcard(WildcardTree node, Insertion ins) { 435 JCWildcard wc = (JCWildcard) node; 436 return Pair.of(astRecord(node), wc.getStartPosition()); 437 } 438 439 @Override visitPrimitiveType(PrimitiveTypeTree node, Insertion ins)440 public Pair<ASTRecord, Integer> visitPrimitiveType(PrimitiveTypeTree node, Insertion ins) { 441 dbug.debug("TypePositionFinder.visitPrimitiveType(%s)%n", node); 442 return pathAndPos((JCTree) node); 443 } 444 445 @Override visitParameterizedType(ParameterizedTypeTree node, Insertion ins)446 public Pair<ASTRecord, Integer> visitParameterizedType(ParameterizedTypeTree node, Insertion ins) { 447 Tree parent = parent(node); 448 dbug.debug("TypePositionFinder.visitParameterizedType %s parent=%s%n", 449 node, parent); 450 Integer pos = getBaseTypePosition(((JCTypeApply) node).getType()).b; 451 return Pair.of(astRecord(node), pos); 452 } 453 454 /** 455 * Returns the number of array levels that are in the given array type tree, 456 * or 0 if the given node is not an array type tree. 457 */ arrayLevels(com.sun.tools.javac.code.Type t)458 private int arrayLevels(com.sun.tools.javac.code.Type t) { 459 return t.accept(new Types.SimpleVisitor<Integer, Integer>() { 460 @Override 461 public Integer visitArrayType(com.sun.tools.javac.code.Type.ArrayType t, 462 Integer i) { 463 return t.elemtype.accept(this, i+1); 464 } 465 @Override 466 public Integer visitType(com.sun.tools.javac.code.Type t, Integer i) { 467 return i; 468 } 469 }, 0); 470 } 471 arrayLevels(Tree node)472 private int arrayLevels(Tree node) { 473 int result = 0; 474 while (node.getKind() == Tree.Kind.ARRAY_TYPE) { 475 result++; 476 node = ((ArrayTypeTree) node).getType(); 477 } 478 return result; 479 } 480 arrayContentType(JCArrayTypeTree att)481 private JCTree arrayContentType(JCArrayTypeTree att) { 482 JCTree node = att; 483 do { 484 node = ((JCArrayTypeTree) node).getType(); 485 } while (node.getKind() == Tree.Kind.ARRAY_TYPE); 486 return node; 487 } 488 largestContainingArray(Tree node)489 private ArrayTypeTree largestContainingArray(Tree node) { 490 TreePath p = getPath(node); 491 Tree result = TreeFinder.largestContainingArray(p).getLeaf(); 492 assert result.getKind() == Tree.Kind.ARRAY_TYPE; 493 return (ArrayTypeTree) result; 494 } 495 496 @Override visitArrayType(ArrayTypeTree node, Insertion ins)497 public Pair<ASTRecord, Integer> visitArrayType(ArrayTypeTree node, Insertion ins) { 498 dbug.debug("TypePositionFinder.visitArrayType(%s)%n", node); 499 JCArrayTypeTree att = (JCArrayTypeTree) node; 500 dbug.debug("TypePositionFinder.visitArrayType(%s) preferred = %s%n", 501 node, att.getPreferredPosition()); 502 // If the code has a type like "String[][][]", then this gets called 503 // three times: for String[][][], String[][], and String[] 504 // respectively. For each of the three, call String[][][] "largest". 505 ArrayTypeTree largest = largestContainingArray(node); 506 int largestLevels = arrayLevels(largest); 507 int levels = arrayLevels(node); 508 int start = arrayContentType(att).getPreferredPosition() + 1; 509 int end = att.getEndPosition(tree.endPositions); 510 int pos = arrayInsertPos(start, end); 511 512 dbug.debug(" levels=%d largestLevels=%d%n", levels, largestLevels); 513 for (int i=levels; i<largestLevels; i++) { 514 pos = getFirstInstanceAfter('[', pos+1); 515 dbug.debug(" pos %d at i=%d%n", pos, i); 516 } 517 return Pair.of(astRecord(node), pos); 518 } 519 520 /** 521 * Find position in source code where annotation is to be inserted. 522 * 523 * @param start beginning of range to be matched 524 * @param end end of range to be matched 525 * 526 * @return position for annotation insertion 527 */ arrayInsertPos(int start, int end)528 private int arrayInsertPos(int start, int end) { 529 try { 530 CharSequence s = tree.getSourceFile().getCharContent(true); 531 int pos = getNthInstanceInRange('[', start, end, 1); 532 533 if (pos < 0) { 534 // no "[", so check for "..." 535 String nonDot = otherThan('.'); 536 String regex = "(?:(?:\\.\\.?)?" + nonDot + ")*(\\.\\.\\.)"; 537 Pattern p = Pattern.compile(regex, Pattern.MULTILINE); 538 Matcher m = p.matcher(s).region(start, end); 539 540 if (m.find()) { 541 pos = m.start(1); 542 } 543 if (pos < 0) { // should never happen 544 throw new RuntimeException("no \"[\" or \"...\" in array type"); 545 } 546 } 547 return pos; 548 } catch (IOException e) { 549 throw new RuntimeException(e); 550 } 551 } 552 553 @Override visitCompilationUnit(CompilationUnitTree node, Insertion ins)554 public Pair<ASTRecord, Integer> visitCompilationUnit(CompilationUnitTree node, Insertion ins) { 555 dbug.debug("TypePositionFinder.visitCompilationUnit%n"); 556 JCCompilationUnit cu = (JCCompilationUnit) node; 557 return Pair.of(astRecord(node), cu.getStartPosition()); 558 } 559 560 @Override visitClass(ClassTree node, Insertion ins)561 public Pair<ASTRecord, Integer> visitClass(ClassTree node, Insertion ins) { 562 dbug.debug("TypePositionFinder.visitClass%n"); 563 JCClassDecl cd = (JCClassDecl) node; 564 JCTree t = cd.mods == null ? cd : cd.mods; 565 return Pair.of(astRecord(cd), t.getPreferredPosition()); 566 } 567 568 // There are three types of array initializers: 569 // /*style 1*/ String[] names1 = new String[12]; 570 // /*style 2*/ String[] names2 = { "Alice", "Bob" }; 571 // /*style 3*/ String[] names3 = new String[] { "Alice", "Bob" }; 572 // (Can the styles be combined?) 573 // 574 // For style 1, we can just find the location of the 575 // dimensionality expression, and then locate the bracket before it. 576 // For style 2, annotations are impossible. 577 // For style 3, we need to count the brackets and get to the right one. 578 // 579 // The AST depth of the initializer is correct unless all arrays are 580 // empty, in which case it is arbitary. This is legal: 581 // String[][][][][] names4 = new String[][][][][] { { { } } }; 582 // 583 // Array initializers can also be multi-dimensional, but this is not 584 // relevant to us: 585 // int[][] pascalsTriangle = { { 1 }, { 1,1 }, { 1,2,1 } }; 586 // int[][] pascalsTriangle = new int[][] { { 1 }, { 1,1 }, { 1,2,1 } }; 587 588 // structure stolen from javac's Pretty.java getDimsSize(JCExpression tree)589 private int getDimsSize(JCExpression tree) { 590 if (tree instanceof JCNewArray) { 591 JCNewArray na = (JCNewArray) tree; 592 if (na.dims.size() != 0) { 593 // when not all dims are given, na.dims.size() gives wrong answer 594 return arrayLevels(na.type); 595 } 596 if (na.elemtype != null) { 597 return getDimsSize(na.elemtype) + 1; 598 } 599 assert na.elems != null; 600 int maxDimsSize = 0; 601 for (JCExpression elem : na.elems) { 602 if (elem instanceof JCNewArray) { 603 int elemDimsSize = getDimsSize((JCNewArray)elem); 604 maxDimsSize = Math.max(maxDimsSize, elemDimsSize); 605 } else if (elem instanceof JCArrayTypeTree) { 606 // Does this ever happen? javac's Pretty.java handles it. 607 System.out.printf("JCArrayTypeTree: %s%n", elem); 608 } 609 } 610 return maxDimsSize + 1; 611 } else if (tree instanceof JCAnnotatedType) { 612 return getDimsSize(((JCAnnotatedType) tree).underlyingType); 613 } else if (tree instanceof JCArrayTypeTree) { 614 return 1 + getDimsSize(((JCArrayTypeTree) tree).elemtype); 615 } else { 616 return 0; 617 } 618 } 619 620 621 // Visit an expression of one of these forms: 622 // new int[5][10] 623 // new int[][] {...} 624 // { ... } -- as in: String[] names2 = { "Alice", "Bob" }; 625 @Override visitNewArray(NewArrayTree node, Insertion ins)626 public Pair<ASTRecord, Integer> visitNewArray(NewArrayTree node, Insertion ins) { 627 dbug.debug("TypePositionFinder.visitNewArray%n"); 628 JCNewArray na = (JCNewArray) node; 629 GenericArrayLocationCriterion galc = 630 ins.getCriteria().getGenericArrayLocation(); 631 ASTRecord rec = ASTIndex.indexOf(tree).get(node); 632 ASTPath astPath = ins.getCriteria().getASTPath(); 633 String childSelector = null; 634 // Invariant: na.dims.size() == 0 or na.elems == null (but not both) 635 // If na.dims.size() != 0, na.elemtype is non-null. 636 // If na.dims.size() == 0, na.elemtype may be null or non-null. 637 int dimsSize = getDimsSize(na); 638 int dim = galc == null ? 0 : galc.getLocation().size(); 639 640 if (astPath == null) { 641 astPath = astRecord(node).astPath.extendNewArray(dim); 642 childSelector = ASTPath.TYPE; 643 } else { 644 ASTPath.ASTEntry lastEntry = null; 645 int n = astPath.size(); 646 int i = n; 647 // find matching node = last path entry w/kind NEW_ARRAY 648 while (--i >= 0) { 649 lastEntry = astPath.get(i); 650 if (lastEntry.getTreeKind() == Tree.Kind.NEW_ARRAY) { break; } 651 } 652 assert i >= 0 : "no matching path entry (kind=NEW_ARRAY)"; 653 if (n > i+1) { 654 // find correct node further down and visit if present 655 assert dim + 1 == dimsSize; 656 Tree typeTree = na.elemtype; 657 int j = i + dim + 1; 658 while (--dim >= 0) { 659 typeTree = ((ArrayTypeTree) typeTree).getType(); 660 } 661 loop: 662 while (j < n) { 663 ASTPath.ASTEntry entry = astPath.get(j); 664 switch (entry.getTreeKind()) { 665 case ANNOTATED_TYPE: 666 typeTree = ((AnnotatedTypeTree) typeTree).getUnderlyingType(); 667 continue; // no increment 668 case ARRAY_TYPE: 669 typeTree = ((ArrayTypeTree) typeTree).getType(); 670 break; 671 case MEMBER_SELECT: 672 if (typeTree instanceof JCTree.JCFieldAccess) { 673 JCTree.JCFieldAccess jfa = (JCTree.JCFieldAccess) typeTree; 674 typeTree = jfa.getExpression(); 675 // if just a qualifier, don't increment loop counter 676 if (jfa.sym.getKind() == ElementKind.PACKAGE) { continue; } 677 break; 678 } 679 break loop; 680 case PARAMETERIZED_TYPE: 681 if (entry.childSelectorIs(ASTPath.TYPE_ARGUMENT)) { 682 int arg = entry.getArgument(); 683 List<? extends Tree> typeArgs = 684 ((ParameterizedTypeTree) typeTree).getTypeArguments(); 685 typeTree = typeArgs.get(arg); 686 } else { // ASTPath.TYPE 687 typeTree = ((ParameterizedTypeTree) typeTree).getType(); 688 } 689 break; 690 default: 691 break loop; 692 } 693 ++j; 694 } 695 if (j < n) { 696 // sought node is absent, so return default; insertion can 697 // be applied only as an inner of some TypedInsertion anyway 698 return getBaseTypePosition(na); 699 } 700 return typeTree.accept(this, ins); 701 } 702 703 childSelector = lastEntry.getChildSelector(); 704 if (dim > 0 && ASTPath.TYPE.equals(childSelector)) { 705 // rebuild path with current value of dim 706 ASTPath newPath = ASTPath.empty(); 707 int j = 0; 708 dim += lastEntry.getArgument(); 709 while (j < i) { // [0,i) 710 newPath = newPath.extend(astPath.get(j)); 711 j++; 712 } 713 lastEntry = new ASTPath.ASTEntry(Tree.Kind.NEW_ARRAY, 714 ASTPath.TYPE, dim); // i 715 newPath = newPath.extend(lastEntry); 716 while (j < n) { // [i,n) 717 newPath = newPath.extend(astPath.get(j)); 718 j++; 719 } 720 astPath = newPath; 721 } else { 722 dim = lastEntry.getArgument(); 723 } 724 } 725 726 if (ASTPath.TYPE.equals(childSelector)) { 727 if (na.toString().startsWith("{")) { 728 if (ins.getKind() == Insertion.Kind.ANNOTATION) { 729 TreePath parentPath = TreePath.getPath(tree, na).getParentPath(); 730 if (parentPath != null) { 731 Tree parent = parentPath.getLeaf(); 732 if (parent.getKind() == Tree.Kind.VARIABLE) { 733 AnnotationInsertion ai = (AnnotationInsertion) ins; 734 JCTree typeTree = ((JCVariableDecl) parent).getType(); 735 ai.setType(typeTree.toString()); 736 return Pair.of(rec.replacePath(astPath), na.getStartPosition()); 737 } 738 } 739 System.err.println("WARNING: array initializer " + node + 740 " has no explicit type; skipping insertion " + ins); 741 return null; 742 } else { 743 return Pair.of(rec.replacePath(astPath), na.getStartPosition()); 744 } 745 } 746 if (dim == dimsSize) { 747 if (na.elemtype == null) { 748 System.err.println("WARNING: array initializer " + node + 749 " has no explicit type; skipping insertion " + ins); 750 return null; 751 } 752 return getBaseTypePosition(na.elemtype); 753 } 754 if (na.dims.size() != 0) { 755 int startPos = na.getStartPosition(); 756 int endPos = na.getEndPosition(tree.endPositions); 757 int pos = getNthInstanceInRange('[', startPos, endPos, dim + 1); 758 return Pair.of(rec.replacePath(astPath), pos); 759 } 760 // In a situation like 761 // node=new String[][][][][]{{{}}} 762 // Also see Pretty.printBrackets. 763 if (dim == 0) { 764 if (na.elemtype == null) { 765 return Pair.of(rec.replacePath(astPath), na.getStartPosition()); 766 } 767 // na.elemtype.getPreferredPosition(); seems to be at the end, 768 // after the brackets. 769 // na.elemtype.getStartPosition(); is before the type name itself. 770 int startPos = na.elemtype.getStartPosition(); 771 return Pair.of(rec.replacePath(astPath), 772 getFirstInstanceAfter('[', startPos+1)); 773 } else if (dim == dimsSize) { 774 return Pair.of(rec.replacePath(astPath), 775 na.getType().pos().getStartPosition()); 776 } else { 777 JCArrayTypeTree jcatt = (JCArrayTypeTree) na.elemtype; 778 for (int i=1; i<dim; i++) { 779 JCTree elem = jcatt.elemtype; 780 if (elem.hasTag(JCTree.Tag.ANNOTATED_TYPE)) { 781 elem = ((JCAnnotatedType) elem).underlyingType; 782 } 783 assert elem.hasTag(JCTree.Tag.TYPEARRAY); 784 jcatt = (JCArrayTypeTree) elem; 785 } 786 return Pair.of(rec.replacePath(astPath), 787 jcatt.pos().getPreferredPosition()); 788 } 789 } else if (ASTPath.DIMENSION.equals(childSelector)) { 790 List<JCExpression> inits = na.getInitializers(); 791 if (dim < inits.size()) { 792 JCExpression expr = inits.get(dim); 793 return Pair.of(astRecord(expr), expr.getStartPosition()); 794 } 795 return null; 796 } else if (ASTPath.INITIALIZER.equals(childSelector)) { 797 JCExpression expr = na.getDimensions().get(dim); 798 return Pair.of(astRecord(expr), expr.getStartPosition()); 799 } else { 800 assert false : "Unexpected child selector in AST path: " 801 + (childSelector == null ? "null" : "\"" + childSelector + "\""); 802 return null; 803 } 804 } 805 806 @Override visitNewClass(NewClassTree node, Insertion ins)807 public Pair<ASTRecord, Integer> visitNewClass(NewClassTree node, Insertion ins) { 808 JCNewClass na = (JCNewClass) node; 809 JCExpression className = na.clazz; 810 // System.out.printf("classname %s (%s)%n", className, className.getClass()); 811 while (! (className.getKind() == Tree.Kind.IDENTIFIER)) { // IdentifierTree 812 if (className instanceof JCAnnotatedType) { 813 className = ((JCAnnotatedType) className).underlyingType; 814 } else if (className instanceof JCTypeApply) { 815 className = ((JCTypeApply) className).clazz; 816 } else if (className instanceof JCFieldAccess) { 817 // This occurs for fully qualified names, e.g. "new java.lang.Object()". 818 // I'm not quite sure why the field "selected" is taken, but "name" would 819 // be a type mismatch. It seems to work, see NewPackage test case. 820 className = ((JCFieldAccess) className).selected; 821 } else { 822 throw new Error(String.format("unrecognized JCNewClass.clazz (%s): %s%n" + 823 " surrounding new class tree: %s%n", className.getClass(), className, node)); 824 } 825 // System.out.printf("classname %s (%s)%n", className, className.getClass()); 826 } 827 828 return visitIdentifier((IdentifierTree) className, ins); 829 } 830 } 831 832 /** 833 * Determine the insertion position for declaration annotations on 834 * various elements. For instance, method declaration annotations should 835 * be placed before all the other modifiers and annotations. 836 */ 837 private class DeclarationPositionFinder extends TreeScanner<Integer, Void> { 838 839 // When a method is visited, it is visited for the declaration itself. 840 @Override 841 public Integer visitMethod(MethodTree node, Void p) { 842 super.visitMethod(node, p); 843 844 // System.out.printf("DeclarationPositionFinder.visitMethod()%n"); 845 846 ModifiersTree mt = node.getModifiers(); 847 848 // actually List<JCAnnotation>. 849 List<? extends AnnotationTree> annos = mt.getAnnotations(); 850 // Set<Modifier> flags = mt.getFlags(); 851 852 JCTree before; 853 if (annos.size() > 1) { 854 before = (JCAnnotation) annos.get(0); 855 } else if (node.getReturnType() != null) { 856 before = (JCTree) node.getReturnType(); 857 } else { 858 // if we're a constructor, we have null return type, so we use the constructor's position 859 // rather than the return type's position 860 before = (JCTree) node; 861 } 862 int declPos = before.getStartPosition(); 863 864 // There is no source code location information for Modifiers, so 865 // cannot iterate through the modifiers. But we don't have to. 866 int modsPos = ((JCModifiers)mt).pos().getStartPosition(); 867 if (modsPos != Position.NOPOS) { 868 declPos = Math.min(declPos, modsPos); 869 } 870 871 return declPos; 872 } 873 874 @Override 875 public Integer visitCompilationUnit(CompilationUnitTree node, Void p) { 876 JCCompilationUnit cu = (JCCompilationUnit) node; 877 return cu.getStartPosition(); 878 } 879 880 @Override 881 public Integer visitClass(ClassTree node, Void p) { 882 JCClassDecl cd = (JCClassDecl) node; 883 int result = -1; 884 if (cd.mods != null 885 && (cd.mods.flags != 0 || cd.mods.annotations.size() > 0)) { 886 result = cd.mods.getPreferredPosition(); 887 } 888 if (result < 0) { 889 result = cd.getPreferredPosition(); 890 } 891 assert result >= 0 || cd.name.isEmpty() 892 : String.format("%d %d %d%n", cd.getStartPosition(), 893 cd.getPreferredPosition(), cd.pos); 894 return result < 0 ? null : result; 895 } 896 897 } 898 899 private final TypePositionFinder tpf; 900 private final DeclarationPositionFinder dpf; 901 private final JCCompilationUnit tree; 902 private final SetMultimap<Pair<Integer, ASTPath>, Insertion> insertions; 903 private final SetMultimap<ASTRecord, Insertion> astInsertions; 904 905 /** 906 * Creates a {@code TreeFinder} from a source tree. 907 * 908 * @param tree the source tree to search 909 */ 910 public TreeFinder(JCCompilationUnit tree) { 911 this.tree = tree; 912 this.insertions = LinkedHashMultimap.create(); 913 this.astInsertions = LinkedHashMultimap.create(); 914 this.tpf = new TypePositionFinder(); 915 this.dpf = new DeclarationPositionFinder(); 916 } 917 918 // which nodes are possible insertion sites 919 boolean handled(Tree node) { 920 switch (node.getKind()) { 921 case ANNOTATION: 922 case ARRAY_TYPE: 923 case CLASS: 924 case COMPILATION_UNIT: 925 case ENUM: 926 case EXPRESSION_STATEMENT: 927 case EXTENDS_WILDCARD: 928 case IDENTIFIER: 929 case INTERFACE: 930 case METHOD: 931 case NEW_ARRAY: 932 case NEW_CLASS: 933 case PARAMETERIZED_TYPE: 934 case PRIMITIVE_TYPE: 935 case SUPER_WILDCARD: 936 case TYPE_PARAMETER: 937 case UNBOUNDED_WILDCARD: 938 case VARIABLE: 939 return true; 940 default: 941 return node instanceof ExpressionTree; 942 } 943 } 944 945 /** 946 * Determines if the last {@link TypePathEntry} in the given list is a 947 * {@link TypePathEntryKind#WILDCARD}. 948 * 949 * @param location the list to check 950 * @return {@code true} if the last {@link TypePathEntry} is a 951 * {@link TypePathEntryKind#WILDCARD}, {@code false} otherwise. 952 */ 953 private boolean wildcardLast(List<TypePathEntry> location) { 954 return location.get(location.size() - 1).tag == TypePathEntryKind.WILDCARD; 955 } 956 957 /** 958 * Scans this tree, using the list of insertions to generate the source 959 * position to insertion text mapping. Insertions are removed from the 960 * list when positions are found for them. 961 * 962 * @param node AST node being considered for annotation insertions 963 * @param p list of insertions not yet placed 964 * <p> 965 * When a match is found, this routine removes the insertion from p and 966 * adds it to the insertions map as a value, with a key that is a pair. 967 * On return, p contains only the insertions for which no match was found. 968 */ 969 @Override 970 public Void scan(Tree node, List<Insertion> p) { 971 if (node == null) { 972 return null; 973 } 974 975 dbug.debug("SCANNING: %s %s%n", node.getKind(), node); 976 if (! handled(node)) { 977 dbug.debug("Not handled, skipping (%s): %s%n", node.getClass(), node); 978 // nothing to do 979 return super.scan(node, p); 980 } 981 982 TreePath path = getPath(node); 983 assert path == null || path.getLeaf() == node : 984 String.format("Mismatch: '%s' '%s' '%s'%n", 985 path, path.getLeaf(), node); 986 987 // To avoid annotating existing annotations right before 988 // the element you wish to annotate, skip anything inside of 989 // an annotation. 990 if (path != null) { 991 for (Tree t : path) { 992 if (t.getKind() == Tree.Kind.PARAMETERIZED_TYPE) { 993 // We started with something within a parameterized type and 994 // should not look for any further annotations. 995 // TODO: does this work on multiple nested levels? 996 break; 997 } 998 if (t.getKind() == Tree.Kind.ANNOTATION) { 999 return super.scan(node, p); 1000 } 1001 } 1002 } 1003 1004 for (Iterator<Insertion> it = p.iterator(); it.hasNext(); ) { 1005 Insertion i = it.next(); 1006 if (i.getInserted()) { 1007 // Skip this insertion if it has already been inserted. See 1008 // the ReceiverInsertion class for details. 1009 it.remove(); 1010 continue; 1011 } 1012 dbug.debug("Considering insertion at tree:%n"); 1013 dbug.debug(" Insertion: %s%n", i); 1014 dbug.debug(" First line of node: %s%n", Main.firstLine(node.toString())); 1015 dbug.debug(" Type of node: %s%n", node.getClass()); 1016 if (!i.getCriteria().isSatisfiedBy(path, node)) { 1017 dbug.debug(" ... not satisfied%n"); 1018 continue; 1019 } else { 1020 dbug.debug(" ... satisfied!%n"); 1021 dbug.debug(" First line of node: %s%n", Main.firstLine(node.toString())); 1022 dbug.debug(" Type of node: %s%n", node.getClass()); 1023 1024 ASTPath astPath = i.getCriteria().getASTPath(); 1025 Integer pos = astPath == null ? findPosition(path, i) 1026 : Main.convert_jaifs ? null // already in correct form 1027 : findPositionByASTPath(astPath, path, i); 1028 if (pos != null) { 1029 dbug.debug(" ... satisfied! at %d for node of type %s: %s%n", 1030 pos, node.getClass(), Main.treeToString(node)); 1031 insertions.put(Pair.of(pos, astPath), i); 1032 } 1033 } 1034 it.remove(); 1035 } 1036 return super.scan(node, p); 1037 } 1038 1039 // Find insertion position for Insertion whose criteria matched the 1040 // given TreePath. 1041 // If no position is found, report an error and return null. 1042 Integer findPosition(TreePath path, Insertion i) { 1043 Tree node = path.getLeaf(); 1044 try { 1045 // As per the JSR308 specification, receiver parameters are not allowed 1046 // on method declarations of anonymous inner classes. 1047 if (i.getCriteria().isOnReceiver() 1048 && path.getParentPath().getParentPath().getLeaf().getKind() == Tree.Kind.NEW_CLASS) { 1049 warn.debug("WARNING: Cannot insert a receiver parameter " 1050 + "on a method declaration of an anonymous inner class. " 1051 + "This insertion will be skipped.%n Insertion: %s%n", i); 1052 return null; 1053 } 1054 1055 // TODO: Find a more fine-grained replacement for the 2nd conjunct below. 1056 // The real issue is whether the insertion will add non-annotation code, 1057 // which is only sometimes the case for a TypedInsertion. 1058 if (alreadyPresent(path, i) && !(i instanceof TypedInsertion)) { 1059 // Don't insert a duplicate if this particular annotation is already 1060 // present at this location. 1061 return null; 1062 } 1063 1064 if (i.getKind() == Insertion.Kind.CONSTRUCTOR) { 1065 ConstructorInsertion cons = (ConstructorInsertion) i; 1066 if (node.getKind() == Tree.Kind.METHOD) { 1067 JCMethodDecl method = (JCMethodDecl) node; 1068 // TODO: account for the following situation in matching phase instead 1069 if (method.sym.owner.isAnonymous()) { return null; } 1070 if ((method.mods.flags & Flags.GENERATEDCONSTR) != 0) { 1071 addConstructor(path, cons, method); 1072 } else { 1073 cons.setAnnotationsOnly(true); 1074 cons.setInserted(true); 1075 i = cons.getReceiverInsertion(); 1076 if (i == null) { return null; } 1077 } 1078 } else { 1079 cons.setAnnotationsOnly(true); 1080 } 1081 } 1082 1083 if (i.getKind() == Insertion.Kind.RECEIVER && node.getKind() == Tree.Kind.METHOD) { 1084 ReceiverInsertion receiver = (ReceiverInsertion) i; 1085 MethodTree method = (MethodTree) node; 1086 VariableTree rcv = method.getReceiverParameter(); 1087 1088 if (rcv == null) { 1089 addReceiverType(path, receiver, method); 1090 } 1091 } 1092 1093 if (i.getKind() == Insertion.Kind.NEW && node.getKind() == Tree.Kind.NEW_ARRAY) { 1094 NewInsertion neu = (NewInsertion) i; 1095 NewArrayTree newArray = (NewArrayTree) node; 1096 1097 if (newArray.toString().startsWith("{")) { 1098 addNewType(path, neu, newArray); 1099 } 1100 } 1101 1102 // If this is a method, then it might have been selected because of 1103 // the receiver, or because of the return value. Distinguish those. 1104 // One way would be to set a global variable here. Another would be 1105 // to look for a particular different node. I will do the latter. 1106 Integer pos = Position.NOPOS; 1107 1108 // The insertion location is at or below the matched location 1109 // in the source tree. For example, a receiver annotation 1110 // matches on the method and inserts on the (possibly newly 1111 // created) receiver. 1112 Map<Tree, ASTRecord> astIndex = ASTIndex.indexOf(tree); 1113 ASTRecord insertRecord = astIndex.get(node); 1114 dbug.debug("TreeFinder.scan: node=%s%n critera=%s%n", 1115 node, i.getCriteria()); 1116 1117 if (CommonScanner.hasClassKind(node) 1118 && i.getCriteria().isOnTypeDeclarationExtendsClause() 1119 && ((ClassTree) node).getExtendsClause() == null) { 1120 return implicitClassBoundPosition((JCClassDecl) node, i); 1121 } 1122 if (node.getKind() == Tree.Kind.METHOD 1123 && i.getCriteria().isOnReturnType()) { 1124 JCMethodDecl jcnode = (JCMethodDecl) node; 1125 Tree returnType = jcnode.getReturnType(); 1126 insertRecord = insertRecord.extend(Tree.Kind.METHOD, ASTPath.TYPE); 1127 if (returnType == null) { 1128 // find constructor name instead 1129 pos = findMethodName(jcnode); 1130 if (pos < 0) { // skip -- inserted w/generated constructor 1131 return null; 1132 } 1133 dbug.debug("pos = %d at constructor name: %s%n", 1134 pos, jcnode.sym.toString()); 1135 } else { 1136 Pair<ASTRecord, Integer> pair = tpf.scan(returnType, i); 1137 insertRecord = pair.a; 1138 pos = pair.b; 1139 assert handled(node); 1140 dbug.debug("pos = %d at return type node: %s%n", 1141 pos, returnType.getClass()); 1142 } 1143 } else if (node.getKind() == Tree.Kind.TYPE_PARAMETER 1144 && i.getCriteria().onBoundZero() 1145 && (((TypeParameterTree) node).getBounds().isEmpty() 1146 || (((JCExpression) ((TypeParameterTree) node) 1147 .getBounds().get(0))).type.tsym.isInterface()) 1148 || (node instanceof WildcardTree 1149 && ((WildcardTree) node).getBound() == null 1150 && wildcardLast(i.getCriteria() 1151 .getGenericArrayLocation().getLocation()))) { 1152 Pair<ASTRecord, Integer> pair = tpf.scan(node, i); 1153 insertRecord = pair.a; 1154 pos = pair.b; 1155 1156 if (i.getKind() == Insertion.Kind.ANNOTATION) { 1157 if (node.getKind() == Tree.Kind.TYPE_PARAMETER 1158 && !((TypeParameterTree) node).getBounds().isEmpty()) { 1159 Tree bound = ((TypeParameterTree) node).getBounds().get(0); 1160 pos = ((JCExpression) bound).getStartPosition(); 1161 ((AnnotationInsertion) i).setGenerateBound(true); 1162 } else { 1163 int limit = ((JCTree) parent(node)).getEndPosition(tree.endPositions); 1164 Integer nextpos1 = getNthInstanceInRange(',', pos+1, limit, 1); 1165 Integer nextpos2 = getNthInstanceInRange('>', pos+1, limit, 1); 1166 pos = (nextpos1 != Position.NOPOS && nextpos1 < nextpos2) ? nextpos1 : nextpos2; 1167 ((AnnotationInsertion) i).setGenerateExtends(true); 1168 } 1169 } 1170 } else if (i.getKind() == Insertion.Kind.CAST) { 1171 Type t = ((CastInsertion) i).getType(); 1172 JCTree jcTree = (JCTree) node; 1173 pos = jcTree.getStartPosition(); 1174 if (t.getKind() == Type.Kind.DECLARED) { 1175 DeclaredType dt = (DeclaredType) t; 1176 if (dt.getName().isEmpty()) { 1177 dt.setName(jcTree.type instanceof NullType ? "Object" 1178 : jcTree.type.toString()); 1179 } 1180 } 1181 } else if (i.getKind() == Insertion.Kind.CLOSE_PARENTHESIS) { 1182 JCTree jcTree = (JCTree) node; 1183 pos = jcTree.getEndPosition(tree.endPositions); 1184 } else { 1185 boolean typeScan = true; 1186 if (node.getKind() == Tree.Kind.METHOD) { // MethodTree 1187 // looking for the receiver or the declaration 1188 typeScan = i.getCriteria().isOnReceiver(); 1189 } else if (CommonScanner.hasClassKind(node)) { // ClassTree 1190 typeScan = ! i.getSeparateLine(); // hacky check 1191 } 1192 if (typeScan) { 1193 // looking for the type 1194 dbug.debug("Calling tpf.scan(%s: %s)%n", node.getClass(), node); 1195 Pair<ASTRecord, Integer> pair = tpf.scan(node, i); 1196 insertRecord = pair.a; 1197 pos = pair.b; 1198 assert handled(node); 1199 dbug.debug("pos = %d at type: %s (%s)%n", pos, 1200 node.toString(), node.getClass()); 1201 } else if (node.getKind() == Tree.Kind.METHOD 1202 && i.getKind() == Insertion.Kind.CONSTRUCTOR 1203 && (((JCMethodDecl) node).mods.flags & Flags.GENERATEDCONSTR) != 0) { 1204 Tree parent = path.getParentPath().getLeaf(); 1205 pos = ((JCClassDecl) parent).getEndPosition(tree.endPositions) - 1; 1206 insertRecord = null; // TODO 1207 } else { 1208 // looking for the declaration 1209 pos = dpf.scan(node, null); 1210 insertRecord = astRecord(node); 1211 dbug.debug("pos = %s at declaration: %s%n", pos, node.getClass()); 1212 } 1213 } 1214 1215 if (pos != null) { 1216 assert pos >= 0 : 1217 String.format("pos: %s%nnode: %s%ninsertion: %s%n", pos, node, i); 1218 astInsertions.put(insertRecord, i); 1219 } 1220 return pos; 1221 } catch (Throwable e) { 1222 reportInsertionError(i, e); 1223 return null; 1224 } 1225 } 1226 1227 // Find insertion position for Insertion whose criteria (including one 1228 // for the ASTPath) matched the given TreePath. 1229 // If no position is found, report an error and return null. 1230 Integer findPositionByASTPath(ASTPath astPath, TreePath path, Insertion i) { 1231 Tree node = path.getLeaf(); 1232 try { 1233 ASTPath.ASTEntry entry = astPath.get(-1); 1234 // As per the JSR308 specification, receiver parameters are not allowed 1235 // on method declarations of anonymous inner classes. 1236 if (entry.getTreeKind() == Tree.Kind.METHOD 1237 && entry.childSelectorIs(ASTPath.PARAMETER) 1238 && entry.getArgument() == -1 1239 && path.getParentPath().getParentPath().getLeaf().getKind() 1240 == Tree.Kind.NEW_CLASS) { 1241 warn.debug("WARNING: Cannot insert a receiver parameter " 1242 + "on a method declaration of an anonymous inner class. " 1243 + "This insertion will be skipped.%n Insertion: %s%n", i); 1244 return null; 1245 } 1246 1247 if (alreadyPresent(path, i)) { 1248 // Don't insert a duplicate if this particular annotation is already 1249 // present at this location. 1250 return null; 1251 } 1252 1253 if (i.getKind() == Insertion.Kind.CONSTRUCTOR) { 1254 ConstructorInsertion cons = (ConstructorInsertion) i; 1255 1256 if (node.getKind() == Tree.Kind.METHOD) { 1257 JCMethodDecl method = (JCMethodDecl) node; 1258 if ((method.mods.flags & Flags.GENERATEDCONSTR) != 0) { 1259 addConstructor(path, cons, method); 1260 } else { 1261 cons.setAnnotationsOnly(true); 1262 cons.setInserted(true); 1263 i = cons.getReceiverInsertion(); 1264 if (i == null) { return null; } 1265 } 1266 } else { 1267 cons.setAnnotationsOnly(true); 1268 } 1269 } 1270 1271 if (i.getKind() == Insertion.Kind.RECEIVER && node.getKind() == Tree.Kind.METHOD) { 1272 ReceiverInsertion receiver = (ReceiverInsertion) i; 1273 MethodTree method = (MethodTree) node; 1274 1275 if (method.getReceiverParameter() == null) { 1276 addReceiverType(path, receiver, method); 1277 } 1278 } 1279 1280 if (i.getKind() == Insertion.Kind.NEW && node.getKind() == Tree.Kind.NEW_ARRAY) { 1281 NewInsertion neu = (NewInsertion) i; 1282 NewArrayTree newArray = (NewArrayTree) node; 1283 1284 if (newArray.toString().startsWith("{")) { 1285 addNewType(path, neu, newArray); 1286 } 1287 } 1288 1289 // If this is a method, then it might have been selected because of 1290 // the receiver, or because of the return value. Distinguish those. 1291 // One way would be to set a global variable here. Another would be 1292 // to look for a particular different node. I will do the latter. 1293 Integer pos = Position.NOPOS; 1294 1295 // The insertion location is at or below the matched location 1296 // in the source tree. For example, a receiver annotation 1297 // matches on the method and inserts on the (possibly newly 1298 // created) receiver. 1299 Map<Tree, ASTRecord> astIndex = ASTIndex.indexOf(tree); 1300 ASTRecord insertRecord = astIndex.get(node); 1301 dbug.debug("TreeFinder.scan: node=%s%n criteria=%s%n", 1302 node, i.getCriteria()); 1303 1304 if (CommonScanner.hasClassKind(node) 1305 && entry.childSelectorIs(ASTPath.BOUND) 1306 && entry.getArgument() < 0 1307 && ((ClassTree) node).getExtendsClause() == null) { 1308 return implicitClassBoundPosition((JCClassDecl) node, i); 1309 } 1310 if (node.getKind() == Tree.Kind.METHOD 1311 && i.getCriteria().isOnMethod("<init>()V") 1312 && entry.childSelectorIs(ASTPath.PARAMETER) 1313 && entry.getArgument() < 0) { 1314 if (i.getKind() != Insertion.Kind.CONSTRUCTOR) { return null; } 1315 Tree parent = path.getParentPath().getLeaf(); 1316 insertRecord = insertRecord.extend(Tree.Kind.METHOD, ASTPath.PARAMETER, -1); 1317 pos = ((JCTree) parent).getEndPosition(tree.endPositions) - 1; 1318 } else if (node.getKind() == Tree.Kind.METHOD 1319 && entry.childSelectorIs(ASTPath.TYPE)) { 1320 JCMethodDecl jcnode = (JCMethodDecl) node; 1321 Tree returnType = jcnode.getReturnType(); 1322 insertRecord = insertRecord.extend(Tree.Kind.METHOD, ASTPath.TYPE); 1323 if (returnType == null) { 1324 // find constructor name instead 1325 pos = findMethodName(jcnode); 1326 if (pos < 0) { // skip -- inserted w/generated constructor 1327 return null; 1328 } 1329 dbug.debug("pos = %d at constructor name: %s%n", 1330 pos, jcnode.sym.toString()); 1331 } else { 1332 Pair<ASTRecord, Integer> pair = tpf.scan(returnType, i); 1333 insertRecord = pair.a; 1334 pos = pair.b; 1335 assert handled(node); 1336 dbug.debug("pos = %d at return type node: %s%n", 1337 pos, returnType.getClass()); 1338 } 1339 } else if (node.getKind() == Tree.Kind.TYPE_PARAMETER 1340 && entry.getTreeKind() == Tree.Kind.TYPE_PARAMETER // TypeParameter.bound 1341 && (((TypeParameterTree) node).getBounds().isEmpty() 1342 || (((JCExpression) ((TypeParameterTree) node) 1343 .getBounds().get(0))).type.tsym.isInterface()) 1344 || ASTPath.isWildcard(node.getKind()) 1345 && (entry.getTreeKind() == Tree.Kind.TYPE_PARAMETER 1346 || ASTPath.isWildcard(entry.getTreeKind())) 1347 && entry.childSelectorIs(ASTPath.BOUND) 1348 && (!entry.hasArgument() || entry.getArgument() == 0)) { 1349 Pair<ASTRecord, Integer> pair = tpf.scan(node, i); 1350 insertRecord = pair.a; 1351 pos = pair.b; 1352 1353 if (i.getKind() == Insertion.Kind.ANNOTATION) { 1354 if (node.getKind() == Tree.Kind.TYPE_PARAMETER 1355 && !((TypeParameterTree) node).getBounds().isEmpty()) { 1356 Tree bound = ((TypeParameterTree) node).getBounds().get(0); 1357 pos = ((JCExpression) bound).getStartPosition(); 1358 ((AnnotationInsertion) i).setGenerateBound(true); 1359 } else { 1360 int limit = ((JCTree) parent(node)).getEndPosition(tree.endPositions); 1361 Integer nextpos1 = getNthInstanceInRange(',', pos+1, limit, 1); 1362 Integer nextpos2 = getNthInstanceInRange('>', pos+1, limit, 1); 1363 pos = (nextpos1 != Position.NOPOS && nextpos1 < nextpos2) ? nextpos1 : nextpos2; 1364 ((AnnotationInsertion) i).setGenerateExtends(true); 1365 } 1366 } 1367 } else if (i.getKind() == Insertion.Kind.CAST) { 1368 Type t = ((CastInsertion) i).getType(); 1369 JCTree jcTree = (JCTree) node; 1370 if (jcTree.getKind() == Tree.Kind.VARIABLE && !astPath.isEmpty() 1371 && astPath.get(-1).childSelectorIs(ASTPath.INITIALIZER)) { 1372 node = ((JCVariableDecl) node).getInitializer(); 1373 if (node == null) { return null; } 1374 jcTree = (JCTree) node; 1375 } 1376 pos = jcTree.getStartPosition(); 1377 if (t.getKind() == Type.Kind.DECLARED) { 1378 DeclaredType dt = (DeclaredType) t; 1379 if (dt.getName().isEmpty()) { 1380 if (jcTree.type instanceof NullType) { 1381 dt.setName("Object"); 1382 } else { 1383 t = Insertions.TypeTree.conv(jcTree.type); 1384 t.setAnnotations(dt.getAnnotations()); 1385 ((CastInsertion) i).setType(t); 1386 } 1387 } 1388 } 1389 } else if (i.getKind() == Insertion.Kind.CLOSE_PARENTHESIS) { 1390 JCTree jcTree = (JCTree) node; 1391 if (jcTree.getKind() == Tree.Kind.VARIABLE && !astPath.isEmpty() 1392 && astPath.get(-1).childSelectorIs(ASTPath.INITIALIZER)) { 1393 node = ((JCVariableDecl) node).getInitializer(); 1394 if (node == null) { return null; } 1395 jcTree = (JCTree) node; 1396 } 1397 pos = jcTree.getEndPosition(tree.endPositions); 1398 } else { 1399 boolean typeScan = true; 1400 if (node.getKind() == Tree.Kind.METHOD) { // MethodTree 1401 // looking for the receiver or the declaration 1402 typeScan = IndexFileSpecification.isOnReceiver(i.getCriteria()); 1403 } else if (node.getKind() == Tree.Kind.CLASS) { // ClassTree 1404 typeScan = ! i.getSeparateLine(); // hacky check 1405 } 1406 if (typeScan) { 1407 // looking for the type 1408 dbug.debug("Calling tpf.scan(%s: %s)%n", node.getClass(), node); 1409 Pair<ASTRecord, Integer> pair = tpf.scan(node, i); 1410 insertRecord = pair.a; 1411 pos = pair.b; 1412 assert handled(node); 1413 dbug.debug("pos = %d at type: %s (%s)%n", 1414 pos, node.toString(), node.getClass()); 1415 } else if (node.getKind() == Tree.Kind.METHOD 1416 && i.getKind() == Insertion.Kind.CONSTRUCTOR 1417 && (((JCMethodDecl) node).mods.flags & Flags.GENERATEDCONSTR) != 0) { 1418 Tree parent = path.getParentPath().getLeaf(); 1419 pos = ((JCClassDecl) parent).getEndPosition(tree.endPositions) - 1; 1420 insertRecord = null; // TODO 1421 } else { 1422 // looking for the declaration 1423 pos = dpf.scan(node, null); 1424 insertRecord = astRecord(node); 1425 assert pos != null; 1426 dbug.debug("pos = %d at declaration: %s%n", pos, node.getClass()); 1427 } 1428 } 1429 1430 if (pos != null) { 1431 assert pos >= 0 : 1432 String.format("pos: %s%nnode: %s%ninsertion: %s%n", pos, node, i); 1433 astInsertions.put(insertRecord, i); 1434 } 1435 return pos; 1436 } catch (Throwable e) { 1437 reportInsertionError(i, e); 1438 return null; 1439 } 1440 } 1441 1442 private Integer implicitClassBoundPosition(JCClassDecl cd, Insertion i) { 1443 Integer pos; 1444 if (cd.sym == null || cd.sym.isAnonymous() 1445 || i.getKind() != Insertion.Kind.ANNOTATION) { 1446 return null; 1447 } 1448 JCModifiers mods = cd.getModifiers(); 1449 String name = cd.getSimpleName().toString(); 1450 if (cd.typarams == null || cd.typarams.isEmpty()) { 1451 int start = cd.getStartPosition(); 1452 int offset = Math.max(start, 1453 mods.getEndPosition(tree.endPositions) + 1); 1454 String s = cd.toString().substring(offset - start); 1455 Pattern p = Pattern.compile("(?:\\s|" + comment 1456 + ")*+class(?:\\s|" + comment 1457 + ")++" + Pattern.quote(name) + "\\b"); 1458 Matcher m = p.matcher(s); 1459 if (!m.find() || m.start() != 0) { return null; } 1460 pos = offset + m.end() - 1; 1461 } else { // generic class 1462 JCTypeParameter param = cd.typarams.get(cd.typarams.length()-1); 1463 int start = param.getEndPosition(tree.endPositions); 1464 pos = getFirstInstanceAfter('>', start) + 1; 1465 } 1466 ((AnnotationInsertion) i).setGenerateExtends(true); 1467 return pos; 1468 } 1469 1470 /** 1471 * Returns the start position of the method's name. In particular, 1472 * works properly for constructors, for which the name field in the 1473 * AST is always "<init>" instead of the name from the source. 1474 * 1475 * @param node AST node of method declaration 1476 * @return position of method name (from {@link JCMethodDecl#sym}) in source 1477 */ 1478 private int findMethodName(JCMethodDecl node) { 1479 String sym = node.sym.toString(); 1480 String name = sym.substring(0, sym.indexOf('(')); 1481 JCModifiers mods = node.getModifiers(); 1482 JCBlock body = node.body; 1483 if ((mods.flags & Flags.GENERATEDCONSTR) != 0) { return Position.NOPOS; } 1484 int nodeStart = node.getStartPosition(); 1485 int nodeEnd = node.getEndPosition(tree.endPositions); 1486 int nodeLength = nodeEnd - nodeStart; 1487 int modsLength = mods.getEndPosition(tree.endPositions) 1488 - mods.getStartPosition(); // can't trust string length! 1489 int bodyLength = body == null ? 1 1490 : body.getEndPosition(tree.endPositions) - body.getStartPosition(); 1491 int start = nodeStart + modsLength; 1492 int end = nodeStart + nodeLength - bodyLength; 1493 int angle = name.lastIndexOf('>'); // check for type params 1494 if (angle >= 0) { name = name.substring(angle + 1); } 1495 1496 try { 1497 CharSequence s = tree.getSourceFile().getCharContent(true); 1498 String regex = "\\b" + Pattern.quote(name) + "\\b"; // sufficient? 1499 Pattern pat = Pattern.compile(regex, Pattern.MULTILINE); 1500 Matcher mat = pat.matcher(s).region(start, end); 1501 return mat.find() ? mat.start() : Position.NOPOS; 1502 } catch (IOException e) { 1503 throw new RuntimeException(e); 1504 } 1505 } 1506 1507 /** 1508 * Determines if the annotation in the given insertion is already present 1509 * at the given location in the AST. 1510 * 1511 * @param path the location in the AST to check for the annotation 1512 * @param ins the annotation to check for 1513 * @return {@code true} if the given annotation is already at the given 1514 * location in the AST, {@code false} otherwise. 1515 */ 1516 private boolean alreadyPresent(TreePath path, Insertion ins) { 1517 List<? extends AnnotationTree> alreadyPresent = null; 1518 if (path != null) { 1519 for (Tree n : path) { 1520 if (n.getKind() == Tree.Kind.CLASS) { 1521 alreadyPresent = ((ClassTree) n).getModifiers().getAnnotations(); 1522 break; 1523 } else if (n.getKind() == Tree.Kind.METHOD) { 1524 alreadyPresent = ((MethodTree) n).getModifiers().getAnnotations(); 1525 break; 1526 } else if (n.getKind() == Tree.Kind.VARIABLE) { 1527 alreadyPresent = ((VariableTree) n).getModifiers().getAnnotations(); 1528 break; 1529 } else if (n.getKind() == Tree.Kind.TYPE_CAST) { 1530 Tree type = ((TypeCastTree) n).getType(); 1531 if (type.getKind() == Tree.Kind.ANNOTATED_TYPE) { 1532 alreadyPresent = ((AnnotatedTypeTree) type).getAnnotations(); 1533 } 1534 break; 1535 } else if (n.getKind() == Tree.Kind.INSTANCE_OF) { 1536 Tree type = ((InstanceOfTree) n).getType(); 1537 if (type.getKind() == Tree.Kind.ANNOTATED_TYPE) { 1538 alreadyPresent = ((AnnotatedTypeTree) type).getAnnotations(); 1539 } 1540 break; 1541 } else if (n.getKind() == Tree.Kind.NEW_CLASS) { 1542 JCNewClass nc = (JCNewClass) n; 1543 if (nc.clazz.getKind() == Tree.Kind.ANNOTATED_TYPE) { 1544 alreadyPresent = ((AnnotatedTypeTree) nc.clazz).getAnnotations(); 1545 } 1546 break; 1547 } else if (n.getKind() == Tree.Kind.PARAMETERIZED_TYPE) { 1548 // If we pass through a parameterized type, stop, otherwise we 1549 // mix up annotations on the outer type. 1550 break; 1551 } else if (n.getKind() == Tree.Kind.ARRAY_TYPE) { 1552 Tree type = ((ArrayTypeTree) n).getType(); 1553 if (type.getKind() == Tree.Kind.ANNOTATED_TYPE) { 1554 alreadyPresent = ((AnnotatedTypeTree) type).getAnnotations(); 1555 } 1556 break; 1557 } else if (n.getKind() == Tree.Kind.ANNOTATED_TYPE) { 1558 alreadyPresent = ((AnnotatedTypeTree) n).getAnnotations(); 1559 break; 1560 } 1561 // TODO: don't add cast insertion if it's already present. 1562 } 1563 } 1564 1565 if (alreadyPresent != null) { 1566 for (AnnotationTree at : alreadyPresent) { 1567 // Compare the to-be-inserted annotation to the existing 1568 // annotation, ignoring its arguments (duplicate annotations are 1569 // never allowed even if they differ in arguments). If we did 1570 // have to compare our arguments, we'd have to deal with enum 1571 // arguments potentially being fully qualified or not: 1572 // @Retention(java.lang.annotation.RetentionPolicy.CLASS) vs 1573 // @Retention(RetentionPolicy.CLASS) 1574 String ann = at.getAnnotationType().toString(); 1575 // strip off leading @ along w/any leading or trailing whitespace 1576 String text = ins.getText(); 1577 String iann = Main.removeArgs(text).a.trim() 1578 .substring(text.startsWith("@") ? 1 : 0); 1579 String iannNoPackage = Insertion.removePackage(iann).b; 1580 // System.out.printf("Comparing: %s %s %s%n", ann, iann, iannNoPackage); 1581 if (ann.equals(iann) || ann.equals(iannNoPackage)) { 1582 dbug.debug("Already present, not reinserting: %s%n", ann); 1583 return true; 1584 } 1585 } 1586 } 1587 return false; 1588 } 1589 1590 /** 1591 * Reports an error inserting an insertion to {@code System.err}. 1592 * @param i the insertion that caused the error 1593 * @param e the error. If there's a message it will be printed. 1594 */ 1595 public static void reportInsertionError(Insertion i, Throwable e) { 1596 System.err.println("Error processing insertion:"); 1597 System.err.println("\t" + i); 1598 if (e.getMessage() != null) { 1599 // If the message has multiple lines, indent them so it's easier to read. 1600 System.err.println("\tError: " + e.getMessage().replace("\n", "\n\t\t")); 1601 } 1602 if (dbug.or(stak).isEnabled()) { 1603 e.printStackTrace(); 1604 } else { 1605 System.err.println("\tRun with --print_error_stack to see the stack trace."); 1606 } 1607 System.err.println("\tThis insertion will be skipped."); 1608 } 1609 1610 /** 1611 * Modifies the given receiver insertion so that it contains the type 1612 * information necessary to insert a full method declaration receiver 1613 * parameter. This is for receiver insertions where a receiver does not 1614 * already exist in the source code. This will also add the annotations to be 1615 * inserted to the correct part of the receiver type. 1616 * 1617 * @param path the location in the AST to insert the receiver 1618 * @param receiver details of the receiver to insert 1619 * @param method the method the receiver is being inserted into 1620 */ 1621 private void addReceiverType(TreePath path, ReceiverInsertion receiver, 1622 MethodTree method) { 1623 // Find the name of the class 1624 // with type parameters to create the receiver. Walk up the tree and 1625 // pick up class names to add to the receiver type. Since we're 1626 // starting from the innermost class, the classes we get to at earlier 1627 // iterations of the loop are inside of the classes we get to at later 1628 // iterations. 1629 TreePath parent = path; 1630 Tree leaf = parent.getLeaf(); 1631 Tree.Kind kind = leaf.getKind(); 1632 // This is the outermost type, currently containing only the 1633 // annotation to add to the receiver. 1634 Type outerType = receiver.getType(); 1635 DeclaredType baseType = receiver.getBaseType(); 1636 // This holds the inner types as they're being read in. 1637 DeclaredType innerTypes = null; 1638 DeclaredType staticType = null; 1639 // For an inner class constructor, the receiver comes from the 1640 // superclass, so skip past the first type definition. 1641 boolean isCon = ((MethodTree) parent.getLeaf()).getReturnType() == null; 1642 boolean skip = isCon; 1643 1644 while (kind != Tree.Kind.COMPILATION_UNIT 1645 && kind != Tree.Kind.NEW_CLASS) { 1646 if (kind == Tree.Kind.CLASS 1647 || kind == Tree.Kind.INTERFACE 1648 || kind == Tree.Kind.ENUM 1649 || kind == Tree.Kind.ANNOTATION_TYPE) { 1650 ClassTree clazz = (ClassTree) leaf; 1651 String className = clazz.getSimpleName().toString(); 1652 boolean isStatic = kind == Tree.Kind.INTERFACE 1653 || kind == Tree.Kind.ENUM 1654 || clazz.getModifiers().getFlags().contains(Modifier.STATIC); 1655 skip &= !isStatic; 1656 if (skip) { 1657 skip = false; 1658 receiver.setQualifyType(true); 1659 } else if (!className.isEmpty()) { 1660 // className will be empty for the CLASS node directly inside an 1661 // anonymous inner class NEW_CLASS node. 1662 DeclaredType inner = new DeclaredType(className); 1663 if (staticType == null) { 1664 // Only include type parameters on the classes to the right of and 1665 // including the rightmost static class. 1666 for (TypeParameterTree tree : clazz.getTypeParameters()) { 1667 inner.addTypeParameter(new DeclaredType(tree.getName().toString())); 1668 } 1669 } 1670 if (staticType == null && isStatic) { 1671 // If this is the first static class then move the annotations here. 1672 inner.setAnnotations(outerType.getAnnotations()); 1673 outerType.clearAnnotations(); 1674 staticType = inner; 1675 } 1676 if (innerTypes == null) { 1677 // This is the first type we've read in, so set it as the 1678 // innermost type. 1679 innerTypes = inner; 1680 } else { 1681 // inner (the type just read in this iteration) is outside of 1682 // innerTypes (the types already read in previous iterations). 1683 inner.setInnerType(innerTypes); 1684 innerTypes = inner; 1685 } 1686 } 1687 } 1688 parent = parent.getParentPath(); 1689 leaf = parent.getLeaf(); 1690 kind = leaf.getKind(); 1691 } 1692 if (isCon && innerTypes == null) { 1693 throw new IllegalArgumentException( 1694 "can't annotate (non-existent) receiver of non-inner constructor"); 1695 } 1696 1697 // Merge innerTypes into outerType: outerType only has the annotations 1698 // on the receiver, while innerTypes has everything else. innerTypes can 1699 // have the annotations if it is a static class. 1700 baseType.setName(innerTypes.getName()); 1701 baseType.setTypeParameters(innerTypes.getTypeParameters()); 1702 baseType.setInnerType(innerTypes.getInnerType()); 1703 if (staticType != null && !innerTypes.getAnnotations().isEmpty()) { 1704 outerType.setAnnotations(innerTypes.getAnnotations()); 1705 } 1706 1707 Type type = (staticType == null) ? baseType : staticType; 1708 Insertion.decorateType(receiver.getInnerTypeInsertions(), type, 1709 receiver.getCriteria().getASTPath()); 1710 1711 // If the method doesn't have parameters, don't add a comma. 1712 receiver.setAddComma(method.getParameters().size() > 0); 1713 } 1714 1715 private void addNewType(TreePath path, NewInsertion neu, 1716 NewArrayTree newArray) { 1717 DeclaredType baseType = neu.getBaseType(); 1718 if (baseType.getName().isEmpty()) { 1719 List<String> annotations = neu.getType().getAnnotations(); 1720 Type newType = Insertions.TypeTree.conv( 1721 ((JCTree.JCNewArray) newArray).type); 1722 for (String ann : annotations) { 1723 newType.addAnnotation(ann); 1724 } 1725 neu.setType(newType); 1726 } 1727 Insertion.decorateType(neu.getInnerTypeInsertions(), neu.getType(), 1728 neu.getCriteria().getASTPath()); 1729 } 1730 1731 private void addConstructor(TreePath path, ConstructorInsertion cons, 1732 MethodTree method) { 1733 ReceiverInsertion recv = cons.getReceiverInsertion(); 1734 MethodTree leaf = (MethodTree) path.getLeaf(); 1735 ClassTree parent = (ClassTree) path.getParentPath().getLeaf(); 1736 DeclaredType baseType = cons.getBaseType(); 1737 if (baseType.getName().isEmpty()) { 1738 List<String> annotations = baseType.getAnnotations(); 1739 String className = parent.getSimpleName().toString(); 1740 Type newType = new DeclaredType(className); 1741 cons.setType(newType); 1742 for (String ann : annotations) { 1743 newType.addAnnotation(ann); 1744 } 1745 } 1746 if (recv != null) { 1747 Iterator<Insertion> iter = cons.getInnerTypeInsertions().iterator(); 1748 List<Insertion> recvInner = new ArrayList<Insertion>(); 1749 addReceiverType(path, recv, leaf); 1750 while (iter.hasNext()) { 1751 Insertion i = iter.next(); 1752 if (i.getCriteria().isOnReceiver()) { 1753 recvInner.add(i); 1754 iter.remove(); 1755 } 1756 } 1757 Insertion.decorateType(recvInner, recv.getType(), 1758 cons.getCriteria().getASTPath()); 1759 } 1760 Insertion.decorateType(cons.getInnerTypeInsertions(), cons.getType(), 1761 cons.getCriteria().getASTPath()); 1762 } 1763 1764 public SetMultimap<ASTRecord, Insertion> getPaths() { 1765 return Multimaps.unmodifiableSetMultimap(astInsertions); 1766 } 1767 1768 /** 1769 * Scans the given tree with the given insertion list and returns the 1770 * mapping from source position to insertion text. The positions are sorted 1771 * in decreasing order of index, so that inserting one doesn't throw 1772 * off the index for a subsequent one. 1773 * 1774 * <p> 1775 * <i>N.B.:</i> This method calls {@code scan()} internally. 1776 * </p> 1777 * 1778 * @param node the tree to scan 1779 * @param p the list of insertion criteria 1780 * @return the source position to insertion text mapping 1781 */ 1782 public SetMultimap<Pair<Integer, ASTPath>, Insertion> 1783 getInsertionsByPosition(JCCompilationUnit node, List<Insertion> p) { 1784 List<Insertion> uninserted = new ArrayList<Insertion>(p); 1785 this.scan(node, uninserted); 1786 // There may be many extra annotations in a .jaif file. For instance, 1787 // the .jaif file may be for an entire library, but its compilation 1788 // units are processed one by one. 1789 // However, we should warn about any insertions that were within the 1790 // given compilation unit but still didn't get inserted. 1791 List<? extends Tree> typeDecls = node.getTypeDecls(); 1792 for (Insertion i : uninserted) { 1793 InClassCriterion c = i.getCriteria().getInClass(); 1794 if (c == null) { 1795 continue; 1796 } 1797 for (Tree t : typeDecls) { 1798 if (c.isSatisfiedBy(TreePath.getPath(node, t))) { 1799 // Avoid warnings about synthetic generated methods. 1800 // This test is too coarse, but is good enough for now. 1801 // There are also synthetic local variables; maybe suppress 1802 // warnings about them, too. 1803 if (! (i.getCriteria().isOnMethod("<init>()V") 1804 || i.getCriteria().isOnLocalVariable())) { 1805 // Should be made more user-friendly 1806 System.err.printf("Found class %s, but unable to insert %s:%n %s%n", c.className, i.getText(), i); 1807 } 1808 } 1809 } 1810 } 1811 if (dbug.isEnabled()) { 1812 // Output every insertion that was not given a position: 1813 for (Insertion i : uninserted) { 1814 System.err.println("Unable to insert: " + i); 1815 } 1816 } 1817 dbug.debug("getPositions => %d positions%n", insertions.size()); 1818 return Multimaps.unmodifiableSetMultimap(insertions); 1819 } 1820 1821 /** 1822 * Scans the given tree with the given {@link Insertions} and returns 1823 * the mapping from source position to insertion text. 1824 * 1825 * <p> 1826 * <i>N.B.:</i> This method calls {@code scan()} internally. 1827 * </p> 1828 * 1829 * @param node the tree to scan 1830 * @param insertions the insertion criteria 1831 * @return the source position to insertion text mapping 1832 */ 1833 public SetMultimap<Pair<Integer, ASTPath>, Insertion> 1834 getPositions(JCCompilationUnit node, Insertions insertions) { 1835 List<Insertion> list = new ArrayList<Insertion>(); 1836 treePathCache.clear(); 1837 list.addAll(insertions.forOuterClass(node, "")); 1838 for (JCTree decl : node.getTypeDecls()) { 1839 if (decl.getTag() == JCTree.Tag.CLASSDEF) { 1840 String name = ((JCClassDecl) decl).sym.className(); 1841 Collection<Insertion> forClass = insertions.forOuterClass(node, name); 1842 list.addAll(forClass); 1843 } 1844 } 1845 return getInsertionsByPosition(node, list); 1846 } 1847 } 1848