Main.java revision e67b0ef921bf74296a1c601b85e2bdcca1e1a86c
110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Alipackage annotator;
210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Aliimport java.io.*;
410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Aliimport java.util.*;
510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Aliimport java.util.regex.*;
6e67b0ef921bf74296a1c601b85e2bdcca1e1a86cMichael Ernstimport plume.*;
710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Aliimport annotator.find.Insertion;
910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Aliimport annotator.find.TreeFinder;
1010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Aliimport annotator.Source;
1110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Aliimport annotator.Source.CompilerException;
1210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Aliimport annotator.specification.IndexFileSpecification;
1310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Aliimport annotator.specification.Specification;
1410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
15f8955bfb5d46ff31634832d5143a95f2faaa14beMichael Ernstimport com.sun.source.tree.*;
165534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernstimport com.sun.source.util.TreePath;
1710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
1857ea23519ad96aca616e5fe41b4a1db896b91096Michael Ernstimport com.google.common.collect.*;
1957ea23519ad96aca616e5fe41b4a1db896b91096Michael Ernst
2010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali/**
2110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali * This is the main class for the annotator, which inserts annotations in
2210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali * Java source code.  It takes as input
2310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali * <ul>
2410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali *   <li>annotation (index) files, which indcate the annotations to insert</li>
2510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali *   <li>Java source files, into which the annotator inserts annotations</li>
2610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali * </ul>
2710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali * Use the --help option for full usage details.
2810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali * <p>
2910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali *
3010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali * Annotations that are not for the specified Java files are ignored.
3110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali */
3210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Alipublic class Main {
3310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
3410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  public static final String INDEX_UTILS_VERSION =
355f7cd572aafcf0cc8181bb099e80ef5cc2874628Michael Ernst    "Annotation file utilities: insert-annotations-to-source v3.0";
3610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
3710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  /** Directory in which output files are written. */
3810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  @Option("-d <directory> Directory in which output files are written")
3910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  public static String outdir = "annotated/";
4010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
41fd37e8455f846ac7d34c53ebe1079797599ebd4fMichael Ernst  /**
42fd37e8455f846ac7d34c53ebe1079797599ebd4fMichael Ernst   * If true, overwrite original source files (making a backup first).
43fd37e8455f846ac7d34c53ebe1079797599ebd4fMichael Ernst   * Furthermore, if the backup files already exist, they are used instead
44fd37e8455f846ac7d34c53ebe1079797599ebd4fMichael Ernst   * of the .java files.  This behavior permits a user to tweak the .jaif
45fd37e8455f846ac7d34c53ebe1079797599ebd4fMichael Ernst   * file and re-run the annotator.
46fd37e8455f846ac7d34c53ebe1079797599ebd4fMichael Ernst   * <p>
47fd37e8455f846ac7d34c53ebe1079797599ebd4fMichael Ernst   *
48fd37e8455f846ac7d34c53ebe1079797599ebd4fMichael Ernst   * Note that if the user runs the annotator with --in-place, makes edits,
49fd37e8455f846ac7d34c53ebe1079797599ebd4fMichael Ernst   * and then re-runs the annotator with this --in-place option, those
50fd37e8455f846ac7d34c53ebe1079797599ebd4fMichael Ernst   * edits are lost.  Similarly, if the user runs the annotator twice in a
51fd37e8455f846ac7d34c53ebe1079797599ebd4fMichael Ernst   * row with --in-place, only the last set of annotations will appear in
52fd37e8455f846ac7d34c53ebe1079797599ebd4fMichael Ernst   * the codebase at the end.
53fd37e8455f846ac7d34c53ebe1079797599ebd4fMichael Ernst   * <p>
54fd37e8455f846ac7d34c53ebe1079797599ebd4fMichael Ernst   *
55fd37e8455f846ac7d34c53ebe1079797599ebd4fMichael Ernst   * To preserve changes when using the --in-place option, first remove the
56fd37e8455f846ac7d34c53ebe1079797599ebd4fMichael Ernst   * backup files.  Or, use the <tt>-d .</tt> option, which makes (and
57fd37e8455f846ac7d34c53ebe1079797599ebd4fMichael Ernst   * reads) no backup, instead of --in-place.
58fd37e8455f846ac7d34c53ebe1079797599ebd4fMichael Ernst   */
59f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst  @Option("-i Overwrite original source files")
60f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst  public static boolean in_place = false;
61f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst
6210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  @Option("-h Print usage information and exit")
6310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  public static boolean help = false;
6410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
6510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  @Option("-a Abbreviate annotation names")
6610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  public static boolean abbreviate = true;
6710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
6810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  @Option("-c Insert annotations in comments")
6910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  public static boolean comments = false;
7010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
7110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  @Option("-v Verbose (print progress information)")
7210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  public static boolean verbose;
7310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
7410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  @Option("Debug (print debug information)")
7510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  public static boolean debug = false;
7610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
7710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  // Implementation details:
7810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  //  1. The annotator partially compiles source
7910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  //     files using the compiler API (JSR-199), obtaining an AST.
8010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  //  2. The annotator reads the specification file, producing a set of
8110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  //     annotator.find.Insertions.  Insertions completely specify what to
8210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  //     write (as a String, which is ultimately translated according to the
8310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  //     keyword file) and how to write it (as annotator.find.Criteria).
8410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  //  3. It then traverses the tree, looking for nodes that satisfy the
8510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  //     Insertion Criteria, translating the Insertion text against the
8610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  //     keyword file, and inserting the annotations into the source file.
8710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
8810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  /**
8910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali   * Runs the annotator, parsing the source and spec files and applying
9010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali   * the annotations.
9110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali   */
9210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  public static void main(String[] args) {
9310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
9410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    if (verbose) {
9510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      System.out.println(INDEX_UTILS_VERSION);
9610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    }
9710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
98f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst    Options options = new Options("Main [options] ann-file... java-file...", Main.class);
99f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst    String[] file_args = options.parse_and_usage (args);
100f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst
1011220f0e5dd7642f90aab557b3bda8e177ce06316Michael Ernst    if (debug) {
1021220f0e5dd7642f90aab557b3bda8e177ce06316Michael Ernst      TreeFinder.debug = true;
1031220f0e5dd7642f90aab557b3bda8e177ce06316Michael Ernst    }
1041220f0e5dd7642f90aab557b3bda8e177ce06316Michael Ernst
10510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    if (help) {
10610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      options.print_usage();
10710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      System.exit(0);
10810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    }
10910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
110f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst    if (in_place && outdir != "annotated/") { // interned
111f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst      options.print_usage("The --outdir and --in-place options are mutually exclusive.");
112f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst      System.exit(1);
113f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst    }
114f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst
11510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    if (file_args.length < 2) {
116f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst      options.print_usage("Supplied %d arguments, at least 2 needed%n", file_args.length);
11710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      System.exit(1);
11810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    }
11910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
12010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    // The insertions specified by the annotation files.
12110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    List<Insertion> insertions = new ArrayList<Insertion>();
12210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    // The Java files into which to insert.
12310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    List<String> javafiles = new ArrayList<String>();
12410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
12510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    for (String arg : file_args) {
12610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      if (arg.endsWith(".java")) {
12710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        javafiles.add(arg);
12810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      } else if (arg.endsWith(".jaif")) {
12910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        try {
13010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          Specification spec = new IndexFileSpecification(arg);
13110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          List<Insertion> parsedSpec = spec.parse();
13210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          insertions.addAll(parsedSpec);
1334735bdd95fe3025e721476ae821d0aca6127f80aMichael Ernst          if (verbose || debug) {
13410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali            System.out.printf("Read %d annotations from %s%n",
13510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali                              parsedSpec.size(), arg);
13610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          }
1372c4067b440b23911687e4a353584b088eccf17fbMichael Ernst        } catch (RuntimeException e) {
1382c4067b440b23911687e4a353584b088eccf17fbMichael Ernst          if (e.getCause() != null
1392c4067b440b23911687e4a353584b088eccf17fbMichael Ernst              && e.getCause() instanceof FileNotFoundException) {
1402c4067b440b23911687e4a353584b088eccf17fbMichael Ernst            System.err.println("File not found: " + arg);
1412c4067b440b23911687e4a353584b088eccf17fbMichael Ernst            System.exit(1);
1422c4067b440b23911687e4a353584b088eccf17fbMichael Ernst          } else {
1432c4067b440b23911687e4a353584b088eccf17fbMichael Ernst            throw e;
1442c4067b440b23911687e4a353584b088eccf17fbMichael Ernst          }
14510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        } catch (FileIOException e) {
14610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          System.err.println("Error while parsing annotation file " + arg);
14710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          if (e.getMessage() != null) {
14810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali            System.err.println(e.getMessage());
14910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          }
15010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          e.printStackTrace();
15110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          System.exit(1);
15210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        }
15310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      } else {
15410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        throw new Error("Unrecognized file extension: " + arg);
15510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      }
15610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    }
15710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
15810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    if (debug) {
159b3ca4989984b4c0c85719bbac77ec93477099b32Michael Ernst      System.out.printf("%d insertions, %d .java files%n", insertions.size(), javafiles.size());
16010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    }
16110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    if (debug) {
162b3ca4989984b4c0c85719bbac77ec93477099b32Michael Ernst      System.out.printf("Insertions:%n");
16310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      for (Insertion insertion : insertions) {
164b3ca4989984b4c0c85719bbac77ec93477099b32Michael Ernst        System.out.printf("  %s%n", insertion);
16510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      }
16610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    }
16710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
16810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    for (String javafilename : javafiles) {
16910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
17010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      if (verbose) {
17110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        System.out.println("Processing " + javafilename);
17210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      }
17310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
174f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst      File javafile = new File(javafilename);
175f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst
176f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst      File outfile;
177f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst      File unannotated = new File(javafilename + ".unannotated");
178f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst      if (in_place) {
179f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        // It doesn't make sense to check timestamps;
180f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        // if the .java.unannotated file exists, then just use it.
181f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        // A user can rename that file back to just .java to cause the
182f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        // .java file to be read.
183f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        if (unannotated.exists()) {
184f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          if (verbose) {
185f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst            System.out.printf("Renaming %s to %s%n", unannotated, javafile);
186f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          }
187f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          boolean success = unannotated.renameTo(javafile);
188f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          if (! success) {
189f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst            throw new Error(String.format("Failed renaming %s to %s",
190f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst                                          unannotated, javafile));
191f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          }
192f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        }
193f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        outfile = javafile;
194f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst      } else {
195f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        String baseName;
196f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        if (javafile.isAbsolute()) {
197f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          baseName = javafile.getName();
198f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        } else {
199f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          baseName = javafile.getPath();
200f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        }
201f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        outfile = new File(outdir, baseName);
202f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst      }
203f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst
204f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst      Set<String> imports = new LinkedHashSet<String>();
205f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst
20610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      String fileLineSep = System.getProperty("line.separator");
20710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      Source src;
20810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      // Get the source file, and use it to obtain parse trees.
20910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      try {
21010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        // fileLineSep is set here so that exceptions can be caught
21110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        fileLineSep = UtilMDE.inferLineSeparator(javafilename);
21210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        src = new Source(javafilename);
21310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      } catch (CompilerException e) {
21410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        e.printStackTrace();
21510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        return;
21610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      } catch (IOException e) {
21710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        e.printStackTrace();
21810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        return;
21910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      }
22010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
22110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      for (CompilationUnitTree tree : src.parse()) {
22210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
22310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        // Create a finder, and use it to get positions.
22410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        TreeFinder finder = new TreeFinder(tree);
2255c6791fb104c246b6a37144b59dbfaad1f800de6Michael Ernst        if (debug) {
2265c6791fb104c246b6a37144b59dbfaad1f800de6Michael Ernst          finder.debug = true;
2275c6791fb104c246b6a37144b59dbfaad1f800de6Michael Ernst        }
228ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst        SetMultimap<Integer, Insertion> positions = finder.getPositions(tree, insertions);
22910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
23010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        // Apply the positions to the source file.
23110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        if (debug) {
23210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          System.err.printf("%d positions in tree for %s%n", positions.size(), javafilename);
23310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        }
23410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
23557ea23519ad96aca616e5fe41b4a1db896b91096Michael Ernst        Set<Integer> positionKeysUnsorted = positions.keySet();
23657ea23519ad96aca616e5fe41b4a1db896b91096Michael Ernst        Set<Integer> positionKeysSorted = new TreeSet<Integer>(new TreeFinder.ReverseIntegerComparator());
23757ea23519ad96aca616e5fe41b4a1db896b91096Michael Ernst        positionKeysSorted.addAll(positionKeysUnsorted);
23857ea23519ad96aca616e5fe41b4a1db896b91096Michael Ernst        for (Integer pos : positionKeysSorted) {
239ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst          List<Insertion> toInsertList = new ArrayList<Insertion>(positions.get(pos));
240ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst          Collections.reverse(toInsertList);
241d2c419e0399881f6e63361a637ccb90cc4a898bdMichael Ernst          assert pos >= 0
242d2c419e0399881f6e63361a637ccb90cc4a898bdMichael Ernst            : "pos is negative: " + pos + " " + toInsertList.get(0) + " " + javafilename;
243ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst          for (Insertion iToInsert : toInsertList) {
244ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst            String toInsert = iToInsert.getText();
245ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst            if (! toInsert.startsWith("@")) {
246ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst              throw new Error("Insertion doesn't start with '@': " + toInsert);
24700623041550b198d6a389410ea3a34687cddced5Michael Ernst            }
248ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst            if (abbreviate) {
2493f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst              Pair<String,String> ps = removePackage(toInsert);
2503f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst              if (ps.a != null) {
2513f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst                imports.add(ps.a);
252ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst              }
2533f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst              toInsert = ps.b;
254ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst            }
255ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst            if (comments) {
256ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst              toInsert = "/*" + toInsert + "*/";
25710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali            }
25810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
259ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst            // Possibly add whitespace after the insertion
260ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst            boolean gotSeparateLine = false;
261ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst            if (iToInsert.getSeparateLine()) {
262ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst              int indentation = 0;
263ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst              while ((pos - indentation != 0)
264ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst                     // horizontal whitespace
265ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst                     && (src.charAt(pos-indentation-1) == ' '
266ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst                         || src.charAt(pos-indentation-1) == '\t')) {
267ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst                indentation++;
268ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst              }
269ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst              if ((pos - indentation == 0)
270ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst                  // horizontal whitespace
271ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst                  || (src.charAt(pos-indentation-1) == '\f'
272ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst                      || src.charAt(pos-indentation-1) == '\n'
273ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst                      || src.charAt(pos-indentation-1) == '\r')) {
274ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst                toInsert = toInsert + fileLineSep + src.substring(pos-indentation, pos);
275ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst                gotSeparateLine = true;
276ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst              }
277ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst            }
278c71d4e21017f8f4412a2d399676e358858999623Michael Ernst
279ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst            // Possibly add a leading space before the insertion
280ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst            if ((! gotSeparateLine) && (pos != 0)) {
281ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst              char precedingChar = src.charAt(pos-1);
282ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst              if (! (Character.isWhitespace(precedingChar)
283ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst                     // No space if it's the first formal or generic parameter
284ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst                     || precedingChar == '('
285ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst                     || precedingChar == '<')) {
286ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst                toInsert = " " + toInsert;
287ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst              }
2884735bdd95fe3025e721476ae821d0aca6127f80aMichael Ernst            }
289ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst
290ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst            // If it's already there, don't re-insert.  This is a hack!
291ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst            // Also, I think this is already checked when constructing the
292ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst            // innertions.
293ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst            int precedingTextPos = pos-toInsert.length()-1;
294ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst            if (precedingTextPos >= 0) {
295ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst              String precedingTextPlusChar
296ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst                = src.getString().substring(precedingTextPos, pos);
297ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst              // System.out.println("Inserting " + toInsert + " at " + pos + " in code of length " + src.getString().length() + " with preceding text '" + precedingTextPlusChar + "'");
298ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst              if (toInsert.equals(precedingTextPlusChar.substring(0, toInsert.length()))
299ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst                  || toInsert.equals(precedingTextPlusChar.substring(1))) {
300ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst                if (debug) {
301ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst                  System.out.println("Already present, skipping");
302ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst                }
303ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst                continue;
3044735bdd95fe3025e721476ae821d0aca6127f80aMichael Ernst              }
30510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali            }
306ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst            // add trailing whitespace
307ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst            if (! gotSeparateLine) {
308ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst              toInsert = toInsert + " ";
309ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst            }
310ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst            src.insert(pos, toInsert);
311ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst            if (debug) {
312ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst              System.out.println("Post-insertion source: " + src.getString());
313ed468b7eb950854ef28a3407e1887dfec12fee67Michael Ernst            }
31410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          }
31510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        }
31610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      }
31710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
31810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      // insert import statements
31910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      {
32010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        if (debug) {
32110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          System.out.println(imports.size() + " imports to insert");
32257ea23519ad96aca616e5fe41b4a1db896b91096Michael Ernst          for (String classname : imports) {
32357ea23519ad96aca616e5fe41b4a1db896b91096Michael Ernst            System.out.println("  " + classname);
32457ea23519ad96aca616e5fe41b4a1db896b91096Michael Ernst          }
32510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        }
32610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        Pattern importPattern = Pattern.compile("(?m)^import\\b");
32710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        Pattern packagePattern = Pattern.compile("(?m)^package\\b.*;(\\n|\\r\\n?)");
32810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        int importIndex = 0;      // default: beginning of file
32910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        String srcString = src.getString();
33010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        Matcher m;
33110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        m = importPattern.matcher(srcString);
33210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        if (m.find()) {
33310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          importIndex = m.start();
33410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        } else {
33510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          // if (debug) {
33610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          //   System.out.println("Didn't find import in " + srcString);
33710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          // }
33810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          m = packagePattern.matcher(srcString);
33910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          if (m.find()) {
34010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali            importIndex = m.end();
34110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          }
34210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        }
34310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        String lineSep = System.getProperty("line.separator");
34410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        for (String classname : imports) {
34510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          String toInsert = "import " + classname + ";" + fileLineSep;
34610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          src.insert(importIndex, toInsert);
34710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          importIndex += toInsert.length();
34810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        }
34910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      }
35010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
35110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      // Write the source file.
35210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      try {
353f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        if (in_place) {
354f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          if (verbose) {
355f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst            System.out.printf("Renaming %s to %s%n", javafile, unannotated);
356f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          }
357f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          boolean success = javafile.renameTo(unannotated);
358f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          if (! success) {
359f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst            throw new Error(String.format("Failed renaming %s to %s",
360f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst                                          javafile, unannotated));
361f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          }
362f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        } else {
363f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          outfile.getParentFile().mkdirs();
364f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        }
36510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        OutputStream output = new FileOutputStream(outfile);
366f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        if (verbose) {
367f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          System.out.printf("Writing %s%n", outfile);
368f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        }
36910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        src.write(output);
370f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        output.close();
37110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      } catch (IOException e) {
37210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        System.err.println("Problem while writing file " + outfile);
37310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        e.printStackTrace();
37410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        System.exit(1);
37510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      }
37610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    }
37710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  }
37810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
3795534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst  ///
3805534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst  /// Utility methods
3815534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst  ///
3825534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst
3835534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst  public static String pathToString(TreePath path) {
3845534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst    if (path == null)
3855534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst      return "null";
386f8955bfb5d46ff31634832d5143a95f2faaa14beMichael Ernst    return treeToString(path.getLeaf());
387f8955bfb5d46ff31634832d5143a95f2faaa14beMichael Ernst  }
388f8955bfb5d46ff31634832d5143a95f2faaa14beMichael Ernst
389f8955bfb5d46ff31634832d5143a95f2faaa14beMichael Ernst  public static String treeToString(Tree node) {
390f8955bfb5d46ff31634832d5143a95f2faaa14beMichael Ernst    String asString = node.toString();
3915534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst    String oneLine = firstLine(asString);
3925534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst    return "\"" + oneLine + "\"";
3935534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst  }
3945534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst
3955534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst  /**
3965534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst   * Return the first non-empty line of the string, adding an ellipsis
3975534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst   * (...) if the string was truncated.
3985534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst   */
3995534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst  public static String firstLine(String s) {
4005534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst    while (s.startsWith("\n")) {
4015534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst      s = s.substring(1);
4025534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst    }
4035534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst    int newlineIndex = s.indexOf('\n');
4045534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst    if (newlineIndex == -1) {
4055534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst      return s;
4065534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst    } else {
4075534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst      return s.substring(0, newlineIndex) + "...";
4085534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst    }
4095534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst  }
4105534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst
4113f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst  /** Remove the leading package. */
4123f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst  public static Pair<String,String> removePackage(String s) {
4133f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst    int nameEnd = s.indexOf("(");
4143f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst    if (nameEnd == -1) {
4153f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst      nameEnd = s.length();
4163f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst    }
4173f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst    int dotIndex = s.lastIndexOf(".", nameEnd);
4183f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst    if (dotIndex != -1) {
4193f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst      String packageName = s.substring(0, nameEnd);
4203f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst      if (packageName.startsWith("@")) {
4213f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst        return Pair.of(packageName.substring(1),
4223f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst                       "@" + s.substring(dotIndex + 1));
4233f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst      } else {
4243f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst        return Pair.of(packageName,
4253f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst                       s.substring(dotIndex + 1));
4263f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst      }
4273f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst    } else {
4283f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst      return Pair.of((String)null, s);
4293f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst    }
4303f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst  }
4313f7a095516f29e07e5c1923b9201de0a7881596fMichael Ernst
43210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali}
433