Main.java revision 553a9a9a17ce582f701841b59edfadb491ed54ad
1package annotator; 2 3import java.io.File; 4import java.io.FileNotFoundException; 5import java.io.FileOutputStream; 6import java.io.IOException; 7import java.io.OutputStream; 8import java.util.ArrayList; 9import java.util.Collection; 10import java.util.Collections; 11import java.util.Comparator; 12import java.util.HashMap; 13import java.util.LinkedHashSet; 14import java.util.List; 15import java.util.Map; 16import java.util.Set; 17import java.util.TreeSet; 18import java.util.regex.Matcher; 19import java.util.regex.Pattern; 20 21import plume.FileIOException; 22import plume.Option; 23import plume.OptionGroup; 24import plume.Options; 25import plume.Pair; 26import plume.UtilMDE; 27import type.Type; 28import annotations.Annotation; 29import annotations.el.ABlock; 30import annotations.el.AClass; 31import annotations.el.ADeclaration; 32import annotations.el.AElement; 33import annotations.el.AExpression; 34import annotations.el.AField; 35import annotations.el.AMethod; 36import annotations.el.AScene; 37import annotations.el.ATypeElement; 38import annotations.el.ATypeElementWithType; 39import annotations.el.AnnotationDef; 40import annotations.el.DefException; 41import annotations.el.ElementVisitor; 42import annotations.el.LocalLocation; 43import annotations.io.ASTIndex; 44import annotations.io.ASTPath; 45import annotations.io.ASTRecord; 46import annotations.io.DebugWriter; 47import annotations.io.IndexFileParser; 48import annotations.io.IndexFileWriter; 49import annotations.util.coll.VivifyingMap; 50import annotator.find.AnnotationInsertion; 51import annotator.find.CastInsertion; 52import annotator.find.ConstructorInsertion; 53import annotator.find.Criteria; 54import annotator.find.GenericArrayLocationCriterion; 55import annotator.find.Insertion; 56import annotator.find.Insertions; 57import annotator.find.NewInsertion; 58import annotator.find.ReceiverInsertion; 59import annotator.find.TreeFinder; 60import annotator.find.TypedInsertion; 61import annotator.scanner.LocalVariableScanner; 62import annotator.specification.IndexFileSpecification; 63 64import com.google.common.collect.LinkedHashMultimap; 65import com.google.common.collect.Multimap; 66import com.google.common.collect.SetMultimap; 67import com.sun.source.tree.CompilationUnitTree; 68import com.sun.source.tree.ExpressionTree; 69import com.sun.source.tree.Tree; 70import com.sun.source.util.TreePath; 71import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntry; 72import com.sun.tools.javac.main.CommandLine; 73import com.sun.tools.javac.tree.JCTree; 74 75/** 76 * This is the main class for the annotator, which inserts annotations in 77 * Java source code. You can call it as <tt>java annotator.Main</tt> or by 78 * using the shell script <tt>insert-annotations-to-source</tt>. 79 * <p> 80 * 81 * It takes as input 82 * <ul> 83 * <li>annotation (index) files, which indicate the annotations to insert</li> 84 * <li>Java source files, into which the annotator inserts annotations</li> 85 * </ul> 86 * Annotations that are not for the specified Java files are ignored. 87 * <p> 88 * 89 * The <a name="command-line-options">command-line options</a> are as follows: 90 * <!-- start options doc (DO NOT EDIT BY HAND) --> 91 * <ul> 92 * <li id="optiongroup:General-options">General options 93 * <ul> 94 * <li id="option:outdir"><b>-d</b> <b>--outdir=</b><i>directory</i>. Directory in which output files are written. [default annotated/]</li> 95 * <li id="option:in-place"><b>-i</b> <b>--in-place=</b><i>boolean</i>. If true, overwrite original source files (making a backup first). 96 * Furthermore, if the backup files already exist, they are used instead 97 * of the .java files. This behavior permits a user to tweak the .jaif 98 * file and re-run the annotator. 99 * <p> 100 * 101 * Note that if the user runs the annotator with --in-place, makes edits, 102 * and then re-runs the annotator with this --in-place option, those 103 * edits are lost. Similarly, if the user runs the annotator twice in a 104 * row with --in-place, only the last set of annotations will appear in 105 * the codebase at the end. 106 * <p> 107 * 108 * To preserve changes when using the --in-place option, first remove the 109 * backup files. Or, use the <tt>-d .</tt> option, which makes (and 110 * reads) no backup, instead of --in-place. [default false]</li> 111 * <li id="option:abbreviate"><b>-a</b> <b>--abbreviate=</b><i>boolean</i>. Abbreviate annotation names [default true]</li> 112 * <li id="option:comments"><b>-c</b> <b>--comments=</b><i>boolean</i>. Insert annotations in comments [default false]</li> 113 * <li id="option:omit-annotation"><b>-o</b> <b>--omit-annotation=</b><i>string</i>. Omit given annotation</li> 114 * <li id="option:nowarn"><b>--nowarn=</b><i>boolean</i>. Suppress warnings about disallowed insertions [default false]</li> 115 * <li id="option:convert-jaifs"><b>--convert-jaifs=</b><i>boolean</i>. Convert JAIFs to new format [default false]</li> 116 * <li id="option:help"><b>-h</b> <b>--help=</b><i>boolean</i>. Print usage information and exit [default false]</li> 117 * </ul> 118 * </li> 119 * <li id="optiongroup:Debugging-options">Debugging options 120 * <ul> 121 * <li id="option:verbose"><b>-v</b> <b>--verbose=</b><i>boolean</i>. Verbose (print progress information) [default false]</li> 122 * <li id="option:debug"><b>--debug=</b><i>boolean</i>. Debug (print debug information) [default false]</li> 123 * <li id="option:print-error-stack"><b>--print-error-stack=</b><i>boolean</i>. Print error stack [default false]</li> 124 * </ul> 125 * </li> 126 * </ul> 127 * <!-- end options doc --> 128 */ 129public class Main { 130 131 /** Directory in which output files are written. */ 132 @OptionGroup("General options") 133 @Option("-d <directory> Directory in which output files are written") 134 public static String outdir = "annotated/"; 135 136 /** 137 * If true, overwrite original source files (making a backup first). 138 * Furthermore, if the backup files already exist, they are used instead 139 * of the .java files. This behavior permits a user to tweak the .jaif 140 * file and re-run the annotator. 141 * <p> 142 * 143 * Note that if the user runs the annotator with --in-place, makes edits, 144 * and then re-runs the annotator with this --in-place option, those 145 * edits are lost. Similarly, if the user runs the annotator twice in a 146 * row with --in-place, only the last set of annotations will appear in 147 * the codebase at the end. 148 * <p> 149 * 150 * To preserve changes when using the --in-place option, first remove the 151 * backup files. Or, use the <tt>-d .</tt> option, which makes (and 152 * reads) no backup, instead of --in-place. 153 */ 154 @Option("-i Overwrite original source files") 155 public static boolean in_place = false; 156 157 @Option("-a Abbreviate annotation names") 158 public static boolean abbreviate = true; 159 160 @Option("-c Insert annotations in comments") 161 public static boolean comments = false; 162 163 @Option("-o Omit given annotation") 164 public static String omit_annotation; 165 166 @Option("Suppress warnings about disallowed insertions") 167 public static boolean nowarn; 168 169 // Instead of doing insertions, create new JAIFs using AST paths 170 // extracted from existing JAIFs and source files they match 171 @Option("Convert JAIFs to AST Path format") 172 public static boolean convert_jaifs = false; 173 174 @Option("-h Print usage information and exit") 175 public static boolean help = false; 176 177 // Debugging options go below here. 178 179 @OptionGroup("Debugging options") 180 @Option("-v Verbose (print progress information)") 181 public static boolean verbose; 182 183 @Option("Debug (print debug information)") 184 public static boolean debug = false; 185 186 @Option("Print error stack") 187 public static boolean print_error_stack = false; 188 189 private static ElementVisitor<Void, AElement> classFilter = 190 new ElementVisitor<Void, AElement>() { 191 <K, V extends AElement> 192 Void filter(VivifyingMap<K, V> vm0, VivifyingMap<K, V> vm1) { 193 for (Map.Entry<K, V> entry : vm0.entrySet()) { 194 entry.getValue().accept(this, vm1.vivify(entry.getKey())); 195 } 196 return null; 197 } 198 199 @Override 200 public Void visitAnnotationDef(AnnotationDef def, AElement el) { 201 // not used, since package declarations not handled here 202 return null; 203 } 204 205 @Override 206 public Void visitBlock(ABlock el0, AElement el) { 207 ABlock el1 = (ABlock) el; 208 filter(el0.locals, el1.locals); 209 return visitExpression(el0, el); 210 } 211 212 @Override 213 public Void visitClass(AClass el0, AElement el) { 214 AClass el1 = (AClass) el; 215 filter(el0.methods, el1.methods); 216 filter(el0.fields, el1.fields); 217 filter(el0.fieldInits, el1.fieldInits); 218 filter(el0.staticInits, el1.staticInits); 219 filter(el0.instanceInits, el1.instanceInits); 220 return visitDeclaration(el0, el); 221 } 222 223 @Override 224 public Void visitDeclaration(ADeclaration el0, AElement el) { 225 ADeclaration el1 = (ADeclaration) el; 226 VivifyingMap<ASTPath, ATypeElement> insertAnnotations = 227 el1.insertAnnotations; 228 VivifyingMap<ASTPath, ATypeElementWithType> insertTypecasts = 229 el1.insertTypecasts; 230 for (Map.Entry<ASTPath, ATypeElement> entry : 231 el0.insertAnnotations.entrySet()) { 232 ASTPath p = entry.getKey(); 233 ATypeElement e = entry.getValue(); 234 insertAnnotations.put(p, e); 235 //visitTypeElement(e, insertAnnotations.vivify(p)); 236 } 237 for (Map.Entry<ASTPath, ATypeElementWithType> entry : 238 el0.insertTypecasts.entrySet()) { 239 ASTPath p = entry.getKey(); 240 ATypeElementWithType e = entry.getValue(); 241 type.Type type = e.getType(); 242 if (type instanceof type.DeclaredType 243 && ((type.DeclaredType) type).getName().isEmpty()) { 244 insertAnnotations.put(p, e); 245 //visitTypeElement(e, insertAnnotations.vivify(p)); 246 } else { 247 insertTypecasts.put(p, e); 248 //visitTypeElementWithType(e, insertTypecasts.vivify(p)); 249 } 250 } 251 return null; 252 } 253 254 @Override 255 public Void visitExpression(AExpression el0, AElement el) { 256 AExpression el1 = (AExpression) el; 257 filter(el0.typecasts, el1.typecasts); 258 filter(el0.instanceofs, el1.instanceofs); 259 filter(el0.news, el1.news); 260 return null; 261 } 262 263 @Override 264 public Void visitField(AField el0, AElement el) { 265 return visitDeclaration(el0, el); 266 } 267 268 @Override 269 public Void visitMethod(AMethod el0, AElement el) { 270 AMethod el1 = (AMethod) el; 271 filter(el0.bounds, el1.bounds); 272 filter(el0.parameters, el1.parameters); 273 filter(el0.throwsException, el1.throwsException); 274 el0.returnType.accept(this, el1.returnType); 275 el0.receiver.accept(this, el1.receiver); 276 el0.body.accept(this, el1.body); 277 return visitDeclaration(el0, el); 278 } 279 280 @Override 281 public Void visitTypeElement(ATypeElement el0, AElement el) { 282 ATypeElement el1 = (ATypeElement) el; 283 filter(el0.innerTypes, el1.innerTypes); 284 return null; 285 } 286 287 @Override 288 public Void visitTypeElementWithType(ATypeElementWithType el0, 289 AElement el) { 290 ATypeElementWithType el1 = (ATypeElementWithType) el; 291 el1.setType(el0.getType()); 292 return visitTypeElement(el0, el); 293 } 294 295 @Override 296 public Void visitElement(AElement el, AElement arg) { 297 return null; 298 } 299 }; 300 301 private static AScene filteredScene(final AScene scene) { 302 final AScene filtered = new AScene(); 303 filtered.packages.putAll(scene.packages); 304 filtered.imports.putAll(scene.imports); 305 for (Map.Entry<String, AClass> entry : scene.classes.entrySet()) { 306 String key = entry.getKey(); 307 AClass clazz0 = entry.getValue(); 308 AClass clazz1 = filtered.classes.vivify(key); 309 clazz0.accept(classFilter, clazz1); 310 } 311 filtered.prune(); 312 return filtered; 313 } 314 315 private static ATypeElement findInnerTypeElement(Tree t, 316 ASTRecord rec, ADeclaration decl, Type type, Insertion ins) { 317 ASTPath astPath = rec.astPath; 318 GenericArrayLocationCriterion galc = 319 ins.getCriteria().getGenericArrayLocation(); 320 assert astPath != null && galc != null; 321 List<TypePathEntry> tpes = galc.getLocation(); 322 ASTPath.ASTEntry entry; 323 for (TypePathEntry tpe : tpes) { 324 switch (tpe.tag) { 325 case ARRAY: 326 if (!astPath.isEmpty()) { 327 entry = astPath.get(-1); 328 if (entry.getTreeKind() == Tree.Kind.NEW_ARRAY 329 && entry.childSelectorIs(ASTPath.TYPE)) { 330 entry = new ASTPath.ASTEntry(Tree.Kind.NEW_ARRAY, 331 ASTPath.TYPE, entry.getArgument() + 1); 332 break; 333 } 334 } 335 entry = new ASTPath.ASTEntry(Tree.Kind.ARRAY_TYPE, 336 ASTPath.TYPE); 337 break; 338 case INNER_TYPE: 339 entry = new ASTPath.ASTEntry(Tree.Kind.MEMBER_SELECT, 340 ASTPath.EXPRESSION); 341 break; 342 case TYPE_ARGUMENT: 343 entry = new ASTPath.ASTEntry(Tree.Kind.PARAMETERIZED_TYPE, 344 ASTPath.TYPE_ARGUMENT, tpe.arg); 345 break; 346 case WILDCARD: 347 entry = new ASTPath.ASTEntry(Tree.Kind.UNBOUNDED_WILDCARD, 348 ASTPath.BOUND); 349 break; 350 default: 351 throw new IllegalArgumentException("unknown type tag " + tpe.tag); 352 } 353 astPath = astPath.extend(entry); 354 } 355 356 return decl.insertAnnotations.vivify(astPath); 357 } 358 359 private static void convertInsertion(String pkg, 360 JCTree.JCCompilationUnit tree, ASTRecord rec, Insertion ins, 361 AScene scene, Multimap<Insertion, Annotation> insertionSources) { 362 Collection<Annotation> annos = insertionSources.get(ins); 363 if (rec == null) { 364 if (ins.getCriteria().isOnPackage()) { 365 for (Annotation anno : annos) { 366 scene.packages.get(pkg).tlAnnotationsHere.add(anno); 367 } 368 } 369 } else if (scene != null && rec.className != null) { 370 AClass clazz = scene.classes.vivify(rec.className); 371 ADeclaration decl = null; // insertion target 372 if (ins.getCriteria().onBoundZero()) { 373 int n = rec.astPath.size(); 374 if (!rec.astPath.get(n-1).childSelectorIs(ASTPath.BOUND)) { 375 ASTPath astPath = ASTPath.empty(); 376 for (int i = 0; i < n; i++) { 377 astPath = astPath.extend(rec.astPath.get(i)); 378 } 379 astPath = astPath.extend( 380 new ASTPath.ASTEntry(Tree.Kind.TYPE_PARAMETER, 381 ASTPath.BOUND, 0)); 382 rec = rec.replacePath(astPath); 383 } 384 } 385 if (rec.methodName == null) { 386 decl = rec.varName == null ? clazz 387 : clazz.fields.vivify(rec.varName); 388 } else { 389 AMethod meth = clazz.methods.vivify(rec.methodName); 390 if (rec.varName == null) { 391 decl = meth; // ? 392 } else { 393 try { 394 int i = Integer.parseInt(rec.varName); 395 decl = i < 0 ? meth.receiver 396 : meth.parameters.vivify(i); 397 } catch (NumberFormatException e) { 398 TreePath path = ASTIndex.getTreePath(tree, rec); 399 JCTree.JCVariableDecl varTree = null; 400 JCTree.JCMethodDecl methTree = null; 401 JCTree.JCClassDecl classTree = null; 402 loop: 403 while (path != null) { 404 Tree leaf = path.getLeaf(); 405 switch (leaf.getKind()) { 406 case VARIABLE: 407 varTree = (JCTree.JCVariableDecl) leaf; 408 break; 409 case METHOD: 410 methTree = (JCTree.JCMethodDecl) leaf; 411 break; 412 case ANNOTATION: 413 case CLASS: 414 case ENUM: 415 case INTERFACE: 416 break loop; 417 default: 418 path = path.getParentPath(); 419 } 420 } 421 while (path != null) { 422 Tree leaf = path.getLeaf(); 423 Tree.Kind kind = leaf.getKind(); 424 if (kind == Tree.Kind.METHOD) { 425 methTree = (JCTree.JCMethodDecl) leaf; 426 int i = LocalVariableScanner.indexOfVarTree(path, 427 varTree, rec.varName); 428 int m = methTree.getStartPosition(); 429 int a = varTree.getStartPosition(); 430 int b = varTree.getEndPosition(tree.endPositions); 431 LocalLocation loc = new LocalLocation(i, a-m, b-a); 432 decl = meth.body.locals.vivify(loc); 433 break; 434 } 435 if (ASTPath.isClassEquiv(kind)) { 436 classTree = (JCTree.JCClassDecl) leaf; 437 // ??? 438 break; 439 } 440 path = path.getParentPath(); 441 } 442 } 443 } 444 } 445 if (decl != null) { 446 AElement el; 447 if (rec.astPath.isEmpty()) { 448 el = decl; 449 } else if (ins.getKind() == Insertion.Kind.CAST) { 450 annotations.el.ATypeElementWithType elem = 451 decl.insertTypecasts.vivify(rec.astPath); 452 elem.setType(((CastInsertion) ins).getType()); 453 el = elem; 454 } else { 455 el = decl.insertAnnotations.vivify(rec.astPath); 456 } 457 for (Annotation anno : annos) { 458 el.tlAnnotationsHere.add(anno); 459 } 460 if (ins instanceof TypedInsertion) { 461 TypedInsertion ti = (TypedInsertion) ins; 462 if (!rec.astPath.isEmpty()) { 463 //addInnerTypePaths(decl, rec, ti, insertionSources); 464 } 465 for (Insertion inner : ti.getInnerTypeInsertions()) { 466 Tree t = ASTIndex.getNode(tree, rec); 467 if (t != null) { 468 ATypeElement elem = findInnerTypeElement(t, 469 rec, decl, ti.getType(), inner); 470 for (Annotation a : insertionSources.get(inner)) { 471 elem.tlAnnotationsHere.add(a); 472 } 473 } 474 } 475 } 476 } 477 } 478 } 479 480 481 // Implementation details: 482 // 1. The annotator partially compiles source 483 // files using the compiler API (JSR-199), obtaining an AST. 484 // 2. The annotator reads the specification file, producing a set of 485 // annotator.find.Insertions. Insertions completely specify what to 486 // write (as a String, which is ultimately translated according to the 487 // keyword file) and how to write it (as annotator.find.Criteria). 488 // 3. It then traverses the tree, looking for nodes that satisfy the 489 // Insertion Criteria, translating the Insertion text against the 490 // keyword file, and inserting the annotations into the source file. 491 492 /** 493 * Runs the annotator, parsing the source and spec files and applying 494 * the annotations. 495 */ 496 public static void main(String[] args) throws IOException { 497 498 if (verbose) { 499 System.out.printf("insert-annotations-to-source (%s)", 500 annotations.io.classfile.ClassFileReader.INDEX_UTILS_VERSION); 501 } 502 503 Options options = new Options( 504 "Main [options] { ann-file | java-file | @arg-file } ...\n" 505 + "(Contents of argfiles are expanded into the argument list.)", 506 Main.class); 507 String[] file_args; 508 try { 509 String[] cl_args = CommandLine.parse(args); 510 file_args = options.parse_or_usage(cl_args); 511 } catch (IOException ex) { 512 System.err.println(ex); 513 System.err.println("(For non-argfile beginning with \"@\", use \"@@\" for initial \"@\"."); 514 System.err.println("Alternative for filenames: indicate directory, e.g. as './@file'."); 515 System.err.println("Alternative for flags: use '=', as in '-o=@Deprecated'.)"); 516 file_args = null; // Eclipse compiler issue workaround 517 System.exit(1); 518 } 519 520 DebugWriter dbug = new DebugWriter(); 521 DebugWriter verb = new DebugWriter(); 522 DebugWriter both = dbug.or(verb); 523 dbug.setEnabled(debug); 524 verb.setEnabled(verbose); 525 TreeFinder.warn.setEnabled(!nowarn); 526 TreeFinder.stak.setEnabled(print_error_stack); 527 TreeFinder.dbug.setEnabled(debug); 528 Criteria.dbug.setEnabled(debug); 529 530 if (help) { 531 options.print_usage(); 532 System.exit(0); 533 } 534 535 if (in_place && outdir != "annotated/") { // interned 536 options.print_usage("The --outdir and --in-place options are mutually exclusive."); 537 System.exit(1); 538 } 539 540 if (file_args.length < 2) { 541 options.print_usage("Supplied %d arguments, at least 2 needed%n", file_args.length); 542 System.exit(1); 543 } 544 545 // The insertions specified by the annotation files. 546 Insertions insertions = new Insertions(); 547 // The Java files into which to insert. 548 List<String> javafiles = new ArrayList<String>(); 549 // Imports required to resolve annotations (when abbreviate==true). 550 Set<String> imports = new LinkedHashSet<String>(); 551 552 // Indices to maintain insertion source traces. 553 Map<String, Multimap<Insertion, Annotation>> insertionIndex = 554 new HashMap<String, Multimap<Insertion, Annotation>>(); 555 Map<Insertion, String> insertionOrigins = new HashMap<Insertion, String>(); 556 Map<String, AScene> scenes = new HashMap<String, AScene>(); 557 558 IndexFileParser.setAbbreviate(abbreviate); 559 for (String arg : file_args) { 560 if (arg.endsWith(".java")) { 561 javafiles.add(arg); 562 } else if (arg.endsWith(".jaif") || 563 arg.endsWith(".jann")) { 564 IndexFileSpecification spec = new IndexFileSpecification(arg); 565 try { 566 List<Insertion> parsedSpec = spec.parse(); 567 AScene scene = spec.getScene(); 568 Collections.sort(parsedSpec, new Comparator<Insertion>() { 569 @Override 570 public int compare(Insertion i1, Insertion i2) { 571 ASTPath p1 = i1.getCriteria().getASTPath(); 572 ASTPath p2 = i2.getCriteria().getASTPath(); 573 return p1 == null 574 ? p2 == null ? 0 : -1 575 : p2 == null ? 1 : p1.compareTo(p2); 576 } 577 }); 578 if (convert_jaifs) { 579 scenes.put(arg, convert_jaifs ? filteredScene(scene) : scene); 580 for (Insertion ins : parsedSpec) { 581 insertionOrigins.put(ins, arg); 582 } 583 if (!insertionIndex.containsKey(arg)) { 584 insertionIndex.put(arg, 585 LinkedHashMultimap.<Insertion, Annotation>create()); 586 } 587 insertionIndex.get(arg).putAll(spec.insertionSources()); 588 } 589 if (abbreviate) { 590 Map<String, Set<String>> annotationImports = 591 spec.annotationImports(); 592 for (Set<String> set : annotationImports.values()) { 593 imports.addAll(set); 594 } 595 } 596 both.debug("Read %d annotations from %s%n", parsedSpec.size(), arg); 597 if (omit_annotation != null) { 598 List<Insertion> filtered = 599 new ArrayList<Insertion>(parsedSpec.size()); 600 for (Insertion insertion : parsedSpec) { 601 // TODO: this won't omit annotations if the insertion is more than 602 // just the annotation (such as if the insertion is a cast 603 // insertion or a 'this' parameter in a method declaration). 604 if (! omit_annotation.equals(insertion.getText())) { 605 filtered.add(insertion); 606 } 607 } 608 parsedSpec = filtered; 609 both.debug("After filtering: %d annotations from %s%n", 610 parsedSpec.size(), arg); 611 } 612 insertions.addAll(parsedSpec); 613 } catch (RuntimeException e) { 614 if (e.getCause() != null 615 && e.getCause() instanceof FileNotFoundException) { 616 System.err.println("File not found: " + arg); 617 System.exit(1); 618 } else { 619 throw e; 620 } 621 } catch (FileIOException e) { 622 // Add 1 to the line number since line numbers in text editors are usually one-based. 623 System.err.println("Error while parsing annotation file " + arg + " at line " 624 + (e.lineNumber + 1) + ":"); 625 if (e.getMessage() != null) { 626 System.err.println('\t' + e.getMessage()); 627 } 628 if (e.getCause() != null && e.getCause().getMessage() != null) { 629 System.err.println('\t' + e.getCause().getMessage()); 630 } 631 if (print_error_stack) { 632 e.printStackTrace(); 633 } 634 System.exit(1); 635 } 636 } else { 637 throw new Error("Unrecognized file extension: " + arg); 638 } 639 } 640 641 if (dbug.isEnabled()) { 642 dbug.debug("%d insertions, %d .java files%n", 643 insertions.size(), javafiles.size()); 644 dbug.debug("Insertions:%n"); 645 for (Insertion insertion : insertions) { 646 dbug.debug(" %s%n", insertion); 647 } 648 } 649 650 for (String javafilename : javafiles) { 651 verb.debug("Processing %s%n", javafilename); 652 653 File javafile = new File(javafilename); 654 File unannotated = new File(javafilename + ".unannotated"); 655 if (in_place) { 656 // It doesn't make sense to check timestamps; 657 // if the .java.unannotated file exists, then just use it. 658 // A user can rename that file back to just .java to cause the 659 // .java file to be read. 660 if (unannotated.exists()) { 661 verb.debug("Renaming %s to %s%n", unannotated, javafile); 662 boolean success = unannotated.renameTo(javafile); 663 if (! success) { 664 throw new Error(String.format("Failed renaming %s to %s", 665 unannotated, javafile)); 666 } 667 } 668 } 669 670 String fileSep = System.getProperty("file.separator"); 671 String fileLineSep = System.getProperty("line.separator"); 672 Source src; 673 // Get the source file, and use it to obtain parse trees. 674 try { 675 // fileLineSep is set here so that exceptions can be caught 676 fileLineSep = UtilMDE.inferLineSeparator(javafilename); 677 src = new Source(javafilename); 678 verb.debug("Parsed %s%n", javafilename); 679 } catch (Source.CompilerException e) { 680 e.printStackTrace(); 681 return; 682 } catch (IOException e) { 683 e.printStackTrace(); 684 return; 685 } 686 687 int num_insertions = 0; 688 String pkg = ""; 689 690 for (CompilationUnitTree cut : src.parse()) { 691 JCTree.JCCompilationUnit tree = (JCTree.JCCompilationUnit) cut; 692 ExpressionTree pkgExp = cut.getPackageName(); 693 pkg = pkgExp == null ? "" : pkgExp.toString(); 694 695 // Create a finder, and use it to get positions. 696 TreeFinder finder = new TreeFinder(tree); 697 SetMultimap<Pair<Integer, ASTPath>, Insertion> positions = 698 finder.getPositions(tree, insertions); 699 700 if (convert_jaifs) { 701 // program used only for JAIF conversion; execute following 702 // block and then skip remainder of loop 703 Multimap<ASTRecord, Insertion> astInsertions = 704 finder.getPaths(); 705 for (Map.Entry<ASTRecord, Collection<Insertion>> entry : 706 astInsertions.asMap().entrySet()) { 707 ASTRecord rec = entry.getKey(); 708 for (Insertion ins : entry.getValue()) { 709 if (ins.getCriteria().getASTPath() != null) { continue; } 710 String arg = insertionOrigins.get(ins); 711 AScene scene = scenes.get(arg); 712 Multimap<Insertion, Annotation> insertionSources = 713 insertionIndex.get(arg); 714 //String text = 715 // ins.getText(comments, abbreviate, false, 0, '\0'); 716 717 // TODO: adjust for missing end of path (?) 718 719 if (insertionSources.containsKey(ins)) { 720 convertInsertion(pkg, tree, rec, ins, scene, insertionSources); 721 } 722 } 723 } 724 continue; 725 } 726 727 // Apply the positions to the source file. 728 if (both.isEnabled()) { 729 System.err.printf( 730 "getPositions returned %d positions in tree for %s%n", 731 positions.size(), javafilename); 732 } 733 734 Set<Pair<Integer, ASTPath>> positionKeysUnsorted = 735 positions.keySet(); 736 Set<Pair<Integer, ASTPath>> positionKeysSorted = 737 new TreeSet<Pair<Integer, ASTPath>>( 738 new Comparator<Pair<Integer, ASTPath>>() { 739 @Override 740 public int compare(Pair<Integer, ASTPath> p1, 741 Pair<Integer, ASTPath> p2) { 742 int c = Integer.compare(p2.a, p1.a); 743 if (c == 0) { 744 c = p2.b == null ? p1.b == null ? 0 : -1 745 : p1.b == null ? 1 : p2.b.compareTo(p1.b); 746 } 747 return c; 748 } 749 }); 750 positionKeysSorted.addAll(positionKeysUnsorted); 751 for (Pair<Integer, ASTPath> pair : positionKeysSorted) { 752 boolean receiverInserted = false; 753 boolean newInserted = false; 754 boolean constructorInserted = false; 755 Set<String> seen = new TreeSet<String>(); 756 List<Insertion> toInsertList = new ArrayList<Insertion>(positions.get(pair)); 757 Collections.reverse(toInsertList); 758 dbug.debug("insertion pos: %d%n", pair.a); 759 assert pair.a >= 0 760 : "pos is negative: " + pair.a + " " + toInsertList.get(0) + " " + javafilename; 761 for (Insertion iToInsert : toInsertList) { 762 // Possibly add whitespace after the insertion 763 String trailingWhitespace = ""; 764 boolean gotSeparateLine = false; 765 int pos = pair.a; // reset each iteration in case of dyn adjustment 766 if (iToInsert.getSeparateLine()) { 767 // System.out.printf("getSeparateLine=true for insertion at pos %d: %s%n", pos, iToInsert); 768 int indentation = 0; 769 while ((pos - indentation != 0) 770 // horizontal whitespace 771 && (src.charAt(pos-indentation-1) == ' ' 772 || src.charAt(pos-indentation-1) == '\t')) { 773 // System.out.printf("src.charAt(pos-indentation-1 == %d-%d-1)='%s'%n", 774 // pos, indentation, src.charAt(pos-indentation-1)); 775 indentation++; 776 } 777 if ((pos - indentation == 0) 778 // horizontal whitespace 779 || (src.charAt(pos-indentation-1) == '\f' 780 || src.charAt(pos-indentation-1) == '\n' 781 || src.charAt(pos-indentation-1) == '\r')) { 782 trailingWhitespace = fileLineSep + src.substring(pos-indentation, pos); 783 gotSeparateLine = true; 784 } 785 } 786 787 char precedingChar; 788 if (pos != 0) { 789 precedingChar = src.charAt(pos - 1); 790 } else { 791 precedingChar = '\0'; 792 } 793 794 if (iToInsert.getKind() == Insertion.Kind.ANNOTATION) { 795 AnnotationInsertion ai = (AnnotationInsertion) iToInsert; 796 if (ai.isGenerateBound()) { // avoid multiple ampersands 797 try { 798 String s = src.substring(pos, pos+9); 799 if ("Object & ".equals(s)) { 800 ai.setGenerateBound(false); 801 precedingChar = '.'; // suppress leading space 802 } 803 } catch (StringIndexOutOfBoundsException e) {} 804 } 805 if (ai.isGenerateExtends()) { // avoid multiple "extends" 806 try { 807 String s = src.substring(pos, pos+9); 808 if (" extends ".equals(s)) { 809 ai.setGenerateExtends(false); 810 pos += 8; 811 } 812 } catch (StringIndexOutOfBoundsException e) {} 813 } 814 } else if (iToInsert.getKind() == Insertion.Kind.CAST) { 815 ((CastInsertion) iToInsert) 816 .setOnArrayLiteral(src.charAt(pos) == '{'); 817 } else if (iToInsert.getKind() == Insertion.Kind.RECEIVER) { 818 ReceiverInsertion ri = (ReceiverInsertion) iToInsert; 819 ri.setAnnotationsOnly(receiverInserted); 820 receiverInserted = true; 821 } else if (iToInsert.getKind() == Insertion.Kind.NEW) { 822 NewInsertion ni = (NewInsertion) iToInsert; 823 ni.setAnnotationsOnly(newInserted); 824 newInserted = true; 825 } else if (iToInsert.getKind() == Insertion.Kind.CONSTRUCTOR) { 826 ConstructorInsertion ci = (ConstructorInsertion) iToInsert; 827 if (constructorInserted) { ci.setAnnotationsOnly(true); } 828 constructorInserted = true; 829 } 830 831 String toInsert = iToInsert.getText(comments, abbreviate, 832 gotSeparateLine, pos, precedingChar) + trailingWhitespace; 833 if (seen.contains(toInsert)) { continue; } // eliminate duplicates 834 seen.add(toInsert); 835 if (abbreviate) { 836 Set<String> packageNames = iToInsert.getPackageNames(); 837 dbug.debug("Need import %s%n due to insertion %s%n", 838 packageNames, toInsert); 839 imports.addAll(packageNames); 840 } 841 842 // If it's already there, don't re-insert. This is a hack! 843 // Also, I think this is already checked when constructing the 844 // insertions. 845 int precedingTextPos = pos-toInsert.length()-1; 846 if (precedingTextPos >= 0) { 847 String precedingTextPlusChar 848 = src.getString().substring(precedingTextPos, pos); 849 if (toInsert.equals( 850 precedingTextPlusChar.substring(0, toInsert.length())) 851 || toInsert.equals(precedingTextPlusChar.substring(1))) { 852 dbug.debug( 853 "Inserting %s at %d in code of length %d with preceding text '%s'%n", 854 toInsert, pos, src.getString().length(), 855 precedingTextPlusChar); 856 dbug.debug("Already present, skipping%n"); 857 continue; 858 } 859 } 860 861 // TODO: Neither the above hack nor this check should be 862 // necessary. Find out why re-insertions still occur and 863 // fix properly. 864 if (iToInsert.getInserted()) { continue; } 865 src.insert(pos, toInsert); 866 if (verbose && !debug) { 867 System.out.print("."); 868 num_insertions++; 869 if ((num_insertions % 50) == 0) { 870 System.out.println(); // terminate the line that contains dots 871 } 872 } 873 dbug.debug("Post-insertion source: %n" + src.getString()); 874 } 875 } 876 } 877 878 if (convert_jaifs) { 879 for (Map.Entry<String, AScene> entry : scenes.entrySet()) { 880 String filename = entry.getKey(); 881 AScene scene = entry.getValue(); 882 try { 883 IndexFileWriter.write(scene, filename + ".converted"); 884 } catch (DefException e) { 885 System.err.println(filename + ": " + " format error in conversion"); 886 if (print_error_stack) { 887 e.printStackTrace(); 888 } 889 } 890 } 891 return; // done with conversion 892 } 893 894 if (verbose) { 895 if ((num_insertions % 50) != 0) { 896 System.out.println(); // terminate the line that contains dots 897 } 898 } 899 900 if (dbug.isEnabled()) { 901 dbug.debug("%d imports to insert%n", imports.size()); 902 for (String classname : imports) { 903 dbug.debug(" %s%n", classname); 904 } 905 } 906 907 // insert import statements 908 { 909 Pattern importPattern = Pattern.compile("(?m)^import\\b"); 910 Pattern packagePattern = Pattern.compile("(?m)^package\\b.*;(\\n|\\r\\n?)"); 911 int importIndex = 0; // default: beginning of file 912 String srcString = src.getString(); 913 Matcher m = importPattern.matcher(srcString); 914 if (m.find()) { 915 importIndex = m.start(); 916 } else { 917 // Debug.info("Didn't find import in " + srcString); 918 m = packagePattern.matcher(srcString); 919 if (m.find()) { 920 importIndex = m.end(); 921 } 922 } 923 for (String classname : imports) { 924 String toInsert = "import " + classname + ";" + fileLineSep; 925 src.insert(importIndex, toInsert); 926 importIndex += toInsert.length(); 927 } 928 } 929 930 // Write the source file. 931 File outfile = null; 932 try { 933 if (in_place) { 934 outfile = javafile; 935 if (verbose) { 936 System.out.printf("Renaming %s to %s%n", javafile, unannotated); 937 } 938 boolean success = javafile.renameTo(unannotated); 939 if (! success) { 940 throw new Error(String.format("Failed renaming %s to %s", 941 javafile, unannotated)); 942 } 943 } else { 944 if (pkg.isEmpty()) { 945 outfile = new File(outdir, javafile.getName()); 946 } else { 947 String[] pkgPath = pkg.split("\\."); 948 StringBuilder sb = new StringBuilder(outdir); 949 for (int i = 0 ; i < pkgPath.length ; i++) { 950 sb.append(fileSep).append(pkgPath[i]); 951 } 952 outfile = new File(sb.toString(), javafile.getName()); 953 } 954 outfile.getParentFile().mkdirs(); 955 } 956 OutputStream output = new FileOutputStream(outfile); 957 if (verbose) { 958 System.out.printf("Writing %s%n", outfile); 959 } 960 src.write(output); 961 output.close(); 962 } catch (IOException e) { 963 System.err.println("Problem while writing file " + outfile); 964 e.printStackTrace(); 965 System.exit(1); 966 } 967 } 968 } 969 970 public static String pathToString(TreePath path) { 971 if (path == null) 972 return "null"; 973 return treeToString(path.getLeaf()); 974 } 975 976 public static String treeToString(Tree node) { 977 String asString = node.toString(); 978 String oneLine = firstLine(asString); 979 return "\"" + oneLine + "\""; 980 } 981 982 /** 983 * Return the first non-empty line of the string, adding an ellipsis 984 * (...) if the string was truncated. 985 */ 986 public static String firstLine(String s) { 987 while (s.startsWith("\n")) { 988 s = s.substring(1); 989 } 990 int newlineIndex = s.indexOf('\n'); 991 if (newlineIndex == -1) { 992 return s; 993 } else { 994 return s.substring(0, newlineIndex) + "..."; 995 } 996 } 997 998 /** 999 * Separates the annotation class from its arguments. 1000 * 1001 * @return given <code>@foo(bar)</code> it returns the pair <code>{ @foo, (bar) }</code>. 1002 */ 1003 public static Pair<String,String> removeArgs(String s) { 1004 int pidx = s.indexOf("("); 1005 return (pidx == -1) ? 1006 Pair.of(s, (String)null) : 1007 Pair.of(s.substring(0, pidx), s.substring(pidx)); 1008 } 1009} 1010