Main.java revision 00623041550b198d6a389410ea3a34687cddced5
110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Alipackage annotator;
210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Aliimport java.io.*;
410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Aliimport java.util.*;
510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Aliimport java.util.regex.*;
610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Aliimport utilMDE.*;
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
1810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali/**
1910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali * This is the main class for the annotator, which inserts annotations in
2010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali * Java source code.  It takes as input
2110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali * <ul>
2210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali *   <li>annotation (index) files, which indcate the annotations to insert</li>
2310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali *   <li>Java source files, into which the annotator inserts annotations</li>
2410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali * </ul>
2510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali * Use the --help option for full usage details.
2610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali * <p>
2710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali *
2810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali * Annotations that are not for the specified Java files are ignored.
2910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali */
3010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Alipublic class Main {
3110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
3210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  public static final String INDEX_UTILS_VERSION =
331dcec9cfcd5a43838f923a2a1c8c6ee3a269e6aeMichael Ernst    "Annotation file utilities: insert-annotations-to-source v2.3";
3410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
3510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  /** Directory in which output files are written. */
3610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  @Option("-d <directory> Directory in which output files are written")
3710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  public static String outdir = "annotated/";
3810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
39f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst  // It's already possible to emulate this (without the backing up) via
40f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst  //   -d .
41f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst  // but the --in-place argument is more convenient and explicit.
42f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst  /** Directory in which output files are written. */
43f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst  @Option("-i Overwrite original source files")
44f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst  public static boolean in_place = false;
45f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst
4610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  @Option("-h Print usage information and exit")
4710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  public static boolean help = false;
4810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
4910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  @Option("-a Abbreviate annotation names")
5010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  public static boolean abbreviate = true;
5110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
5210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  @Option("-c Insert annotations in comments")
5310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  public static boolean comments = false;
5410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
5510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  @Option("-v Verbose (print progress information)")
5610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  public static boolean verbose;
5710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
5810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  @Option("Debug (print debug information)")
5910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  public static boolean debug = false;
6010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
6110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  // Implementation details:
6210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  //  1. The annotator partially compiles source
6310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  //     files using the compiler API (JSR-199), obtaining an AST.
6410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  //  2. The annotator reads the specification file, producing a set of
6510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  //     annotator.find.Insertions.  Insertions completely specify what to
6610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  //     write (as a String, which is ultimately translated according to the
6710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  //     keyword file) and how to write it (as annotator.find.Criteria).
6810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  //  3. It then traverses the tree, looking for nodes that satisfy the
6910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  //     Insertion Criteria, translating the Insertion text against the
7010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  //     keyword file, and inserting the annotations into the source file.
7110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
7210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  /**
7310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali   * Runs the annotator, parsing the source and spec files and applying
7410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali   * the annotations.
7510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali   */
7610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  public static void main(String[] args) {
7710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
7810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    if (verbose) {
7910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      System.out.println(INDEX_UTILS_VERSION);
8010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    }
8110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
82f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst    Options options = new Options("Main [options] ann-file... java-file...", Main.class);
83f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst    String[] file_args = options.parse_and_usage (args);
84f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst
8510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    if (help) {
8610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      options.print_usage();
8710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      System.exit(0);
8810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    }
8910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
90f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst    if (in_place && outdir != "annotated/") { // interned
91f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst      options.print_usage("The --outdir and --in-place options are mutually exclusive.");
92f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst      System.exit(1);
93f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst    }
94f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst
9510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    if (file_args.length < 2) {
96f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst      options.print_usage("Supplied %d arguments, at least 2 needed%n", file_args.length);
9710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      System.exit(1);
9810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    }
9910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
10010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    // The insertions specified by the annotation files.
10110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    List<Insertion> insertions = new ArrayList<Insertion>();
10210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    // The Java files into which to insert.
10310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    List<String> javafiles = new ArrayList<String>();
10410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
10510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    for (String arg : file_args) {
10610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      if (arg.endsWith(".java")) {
10710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        javafiles.add(arg);
10810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      } else if (arg.endsWith(".jaif")) {
10910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        try {
11010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          Specification spec = new IndexFileSpecification(arg);
11110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          List<Insertion> parsedSpec = spec.parse();
11210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          insertions.addAll(parsedSpec);
11310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          if (verbose) {
11410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali            System.out.printf("Read %d annotations from %s%n",
11510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali                              parsedSpec.size(), arg);
11610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          }
11710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        } catch (FileIOException e) {
11810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          System.err.println("Error while parsing annotation file " + arg);
11910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          if (e.getMessage() != null) {
12010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali            System.err.println(e.getMessage());
12110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          }
12210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          e.printStackTrace();
12310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          System.exit(1);
12410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        }
12510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      } else {
12610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        throw new Error("Unrecognized file extension: " + arg);
12710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      }
12810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    }
12910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
13010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    if (debug) {
13110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      System.err.printf("%d insertions, %d .java files%n", insertions.size(), javafiles.size());
13210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    }
13310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    if (debug) {
13410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      System.err.printf("Insertions:%n");
13510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      for (Insertion insertion : insertions) {
13610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        System.err.printf("  %s%n", insertion);
13710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      }
13810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    }
13910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
14010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    for (String javafilename : javafiles) {
14110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
14210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      if (verbose) {
14310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        System.out.println("Processing " + javafilename);
14410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      }
14510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
146f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst      File javafile = new File(javafilename);
147f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst
148f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst      File outfile;
149f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst      File unannotated = new File(javafilename + ".unannotated");
150f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst      if (in_place) {
151f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        // It doesn't make sense to check timestamps;
152f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        // if the .java.unannotated file exists, then just use it.
153f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        // A user can rename that file back to just .java to cause the
154f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        // .java file to be read.
155f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        if (unannotated.exists()) {
156f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          if (verbose) {
157f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst            System.out.printf("Renaming %s to %s%n", unannotated, javafile);
158f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          }
159f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          boolean success = unannotated.renameTo(javafile);
160f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          if (! success) {
161f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst            throw new Error(String.format("Failed renaming %s to %s",
162f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst                                          unannotated, javafile));
163f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          }
164f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        }
165f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        outfile = javafile;
166f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst      } else {
167f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        String baseName;
168f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        if (javafile.isAbsolute()) {
169f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          baseName = javafile.getName();
170f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        } else {
171f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          baseName = javafile.getPath();
172f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        }
173f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        outfile = new File(outdir, baseName);
174f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst      }
175f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst
176f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst      Set<String> imports = new LinkedHashSet<String>();
177f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst
17810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      String fileLineSep = System.getProperty("line.separator");
17910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      Source src;
18010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      // Get the source file, and use it to obtain parse trees.
18110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      try {
18210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        // fileLineSep is set here so that exceptions can be caught
18310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        fileLineSep = UtilMDE.inferLineSeparator(javafilename);
18410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        src = new Source(javafilename);
18510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      } catch (CompilerException e) {
18610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        e.printStackTrace();
18710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        return;
18810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      } catch (IOException e) {
18910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        e.printStackTrace();
19010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        return;
19110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      }
19210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
19310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      for (CompilationUnitTree tree : src.parse()) {
19410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
19510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        // Create a finder, and use it to get positions.
19610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        TreeFinder finder = new TreeFinder(tree);
19710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        Map<Integer, String> positions = finder.getPositions(tree, insertions);
19810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
19910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        // Apply the positions to the source file.
20010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        if (debug) {
20110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          System.err.printf("%d positions in tree for %s%n", positions.size(), javafilename);
20210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        }
20310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
20410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        for (Integer pos : positions.keySet()) {
20510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          String toInsert = positions.get(pos).trim();
20610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          if (! toInsert.startsWith("@")) {
20710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali            throw new Error("Insertion doesn't start with '@': " + toInsert);
20810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          }
20910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          if (abbreviate) {
21000623041550b198d6a389410ea3a34687cddced5Michael Ernst            int nameEnd = toInsert.indexOf("(");
21100623041550b198d6a389410ea3a34687cddced5Michael Ernst            if (nameEnd == -1) {
21200623041550b198d6a389410ea3a34687cddced5Michael Ernst              nameEnd = toInsert.length();
21300623041550b198d6a389410ea3a34687cddced5Michael Ernst            }
21400623041550b198d6a389410ea3a34687cddced5Michael Ernst            int dotIndex = toInsert.lastIndexOf(".", nameEnd);
21510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali            if (dotIndex != -1) {
21600623041550b198d6a389410ea3a34687cddced5Michael Ernst              imports.add(toInsert.substring(1, nameEnd));
21710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali              toInsert = "@" + toInsert.substring(dotIndex + 1);
21810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali            }
21910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          }
22010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          if (comments) {
22110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali            toInsert = "/*" + toInsert + "*/";
22210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          }
22310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
22410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          char precedingChar = src.charAt(pos-1);
22510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          if (! (Character.isWhitespace(precedingChar)
22610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali                 // No space if it's the first formal or generic parameter
22710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali                 || precedingChar == '('
22810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali                 || precedingChar == '<')) {
22910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali            toInsert = " " + toInsert;
23010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          }
23110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          // If it's already there, don't re-insert.  This is a hack!
23210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          String precedingTextPlusChar
23310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali            = src.getString().substring(pos-toInsert.length()-1, pos);
23410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          // System.out.println("Inserting " + toInsert + " at " + pos + " in code of length " + src.getString().length() + " with preceding text '" + precedingTextPlusChar + "'");
23510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          if (toInsert.equals(precedingTextPlusChar.substring(0, toInsert.length()))
23610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali              || toInsert.equals(precedingTextPlusChar.substring(1))) {
23710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali            if (debug) {
23810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali              System.out.println("Already present, skipping");
23910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali            }
24010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali            continue;
24110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          }
24210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          src.insert(pos, toInsert + " ");
24310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          if (debug) {
24410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali            System.out.println("Post-insertion source: " + src.getString());
24510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          }
24610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        }
24710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      }
24810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
24910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      // insert import statements
25010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      {
25110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        if (debug) {
25210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          System.out.println(imports.size() + " imports to insert");
25310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        }
25410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        Pattern importPattern = Pattern.compile("(?m)^import\\b");
25510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        Pattern packagePattern = Pattern.compile("(?m)^package\\b.*;(\\n|\\r\\n?)");
25610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        int importIndex = 0;      // default: beginning of file
25710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        String srcString = src.getString();
25810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        Matcher m;
25910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        m = importPattern.matcher(srcString);
26010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        if (m.find()) {
26110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          importIndex = m.start();
26210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        } else {
26310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          // if (debug) {
26410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          //   System.out.println("Didn't find import in " + srcString);
26510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          // }
26610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          m = packagePattern.matcher(srcString);
26710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          if (m.find()) {
26810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali            importIndex = m.end();
26910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          }
27010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        }
27110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        String lineSep = System.getProperty("line.separator");
27210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        for (String classname : imports) {
27310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          String toInsert = "import " + classname + ";" + fileLineSep;
27410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          src.insert(importIndex, toInsert);
27510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali          importIndex += toInsert.length();
27610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        }
27710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      }
27810353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
27910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      // Write the source file.
28010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      try {
281f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        if (in_place) {
282f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          if (verbose) {
283f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst            System.out.printf("Renaming %s to %s%n", javafile, unannotated);
284f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          }
285f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          boolean success = javafile.renameTo(unannotated);
286f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          if (! success) {
287f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst            throw new Error(String.format("Failed renaming %s to %s",
288f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst                                          javafile, unannotated));
289f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          }
290f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        } else {
291f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          outfile.getParentFile().mkdirs();
292f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        }
29310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        OutputStream output = new FileOutputStream(outfile);
294f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        if (verbose) {
295f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst          System.out.printf("Writing %s%n", outfile);
296f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        }
29710353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        src.write(output);
298f5fbd2ee90bc394cb18e8cf7dbaf3ecbbd4e7ad7Michael Ernst        output.close();
29910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      } catch (IOException e) {
30010353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        System.err.println("Problem while writing file " + outfile);
30110353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        e.printStackTrace();
30210353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali        System.exit(1);
30310353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali      }
30410353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali    }
30510353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali  }
30610353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali
3075534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst  ///
3085534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst  /// Utility methods
3095534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst  ///
3105534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst
3115534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst  public static String pathToString(TreePath path) {
3125534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst    if (path == null)
3135534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst      return "null";
314f8955bfb5d46ff31634832d5143a95f2faaa14beMichael Ernst    return treeToString(path.getLeaf());
315f8955bfb5d46ff31634832d5143a95f2faaa14beMichael Ernst  }
316f8955bfb5d46ff31634832d5143a95f2faaa14beMichael Ernst
317f8955bfb5d46ff31634832d5143a95f2faaa14beMichael Ernst  public static String treeToString(Tree node) {
318f8955bfb5d46ff31634832d5143a95f2faaa14beMichael Ernst    String asString = node.toString();
3195534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst    String oneLine = firstLine(asString);
3205534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst    return "\"" + oneLine + "\"";
3215534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst  }
3225534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst
3235534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst  /**
3245534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst   * Return the first non-empty line of the string, adding an ellipsis
3255534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst   * (...) if the string was truncated.
3265534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst   */
3275534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst  public static String firstLine(String s) {
3285534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst    while (s.startsWith("\n")) {
3295534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst      s = s.substring(1);
3305534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst    }
3315534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst    int newlineIndex = s.indexOf('\n');
3325534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst    if (newlineIndex == -1) {
3335534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst      return s;
3345534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst    } else {
3355534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst      return s.substring(0, newlineIndex) + "...";
3365534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst    }
3375534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst  }
3385534e50f6966d53ba8c8104a4bf847df0cd53409Michael Ernst
33910353ed766fc48a0af6bd33d934439e695c03e3Mahmood Ali}
340