1 package annotations.io.classfile;
2 
3 /*>>>
4 import org.checkerframework.checker.nullness.qual.*;
5 */
6 
7 import java.io.*;
8 
9 import com.sun.tools.javac.main.CommandLine;
10 
11 import org.objectweb.asm.ClassReader;
12 
13 import plume.Option;
14 import plume.Options;
15 
16 import annotations.el.AScene;
17 import annotations.io.IndexFileParser;
18 
19 /**
20  * A <code> ClassFileWriter </code> provides methods for inserting annotations
21  *  from an {@link annotations.el.AScene} into a class file.
22  */
23 public class ClassFileWriter {
24 
25   @Option("-h print usage information and exit")
26   public static boolean help = false;
27 
28   @Option("print version information and exit")
29   public static boolean version = false;
30 
31   private static String linesep = System.getProperty("line.separator");
32 
33   static String usage
34     = "usage: insert-annotations [options] class1 indexfile1 class2 indexfile2 ..."
35     + ""
36     + linesep
37     + "For each class/index file pair (a.b.C a.b.C.jaif), read annotations from"
38     + linesep
39     + "the index file a.b.C.jaif and insert them into the class a.b.C, then"
40     + linesep
41     + "output the merged class file to a.b.C.class"
42     + linesep
43     + "Each class is either a fully-qualified name of a class on your classpath,"
44     + linesep
45     + "or a path to a .class file, such as e.g. /.../path/to/a/b/C.class ."
46     + linesep
47     + "Arguments beginning with a single '@' are interpreted as argument files to"
48     + linesep
49     + "be read and expanded into the command line.  Options:";
50 
51   /**
52    * Main method meant to a a convenient way to write annotations from an index
53    * file to a class file.  For programmatic access to this
54    * tool, one should probably use the insert() methods instead.
55    * <p>
56    * Usage: java annotations.io.ClassFileWriter <em>options</em> [classfile indexfile] ...
57    * <p>
58    * <em>options</em> include:<pre>
59    *   -h, --help   print usage information and exit
60    *   --version    print version information and exit
61    * </pre>
62    * @param args options and classes and index files to analyze;
63    * @throws IOException if a class file or index file cannot be opened/written
64    */
main(String[] args)65   public static void main(String[] args) throws IOException {
66     Options options = new Options(usage, ClassFileWriter.class);
67     String[] file_args;
68 
69     try {
70       String[] cl_args = CommandLine.parse(args);
71       file_args = options.parse_or_usage(cl_args);
72     } catch (IOException ex) {
73       System.err.println(ex);
74       System.err.println("(For non-argfile beginning with \"@\", use \"@@\" for initial \"@\".");
75       System.err.println("Alternative for filenames: indicate directory, e.g. as './@file'.");
76       System.err.println("Alternative for flags: use '=', as in '-o=@Deprecated'.)");
77       file_args = null;  // Eclipse compiler issue workaround
78       System.exit(1);
79     }
80 
81     if (version) {
82       System.out.printf("insert-annotations (%s)",
83                         ClassFileReader.INDEX_UTILS_VERSION);
84     }
85     if (help) {
86       options.print_usage();
87     }
88     if (version || help) {
89       System.exit(-1);
90     }
91 
92     if (file_args.length == 0) {
93       options.print_usage("No arguments given.");
94       System.exit(-1);
95     }
96     if (file_args.length % 2 == 1) {
97       options.print_usage("Must supply an even number of arguments.");
98       System.exit(-1);
99     }
100 
101     // check args for well-formed names
102     for (int i = 0; i < file_args.length; i += 2) {
103       if (!ClassFileReader.checkClass(file_args[i])) {
104         System.exit(-1);
105       }
106     }
107 
108     for (int i = 0; i < file_args.length; i++) {
109 
110       String className = file_args[i];
111       i++;
112       if (i >= file_args.length) {
113         System.out.println("Error: incorrect number of arguments");
114         System.out.println("Run insert-annotations --help for usage information");
115         return;
116       }
117       String indexFileName = file_args[i];
118 
119       AScene scene = new AScene();
120 
121       IndexFileParser.parseFile(indexFileName, scene);
122 
123       // annotations loaded from index file into scene, now insert them
124       // into class file
125       try {
126         if (className.endsWith(".class")) {
127           System.out.printf("Adding annotations to class file %s%n", className);
128           insert(scene, className, true);
129         } else {
130           String outputFileName = className + ".class";
131           System.out.printf("Reading class file %s; writing with annotations to %s%n",
132                             className, outputFileName);
133           insert(scene, className, outputFileName, true);
134         }
135       } catch (IOException e) {
136         System.out.printf("IOException: %s%n", e.getMessage());
137         return;
138       } catch (Exception e) {
139         System.out.println("Unknown error trying to insert annotations from: " +
140                            indexFileName + " to " + className);
141         e.printStackTrace();
142         System.out.println("Please submit a bug report at");
143         System.out.println("  https://github.com/typetools/annotation-tools/issues");
144         System.out.println("Be sure to include a copy of the following output trace, instructions on how");
145         System.out.println("to reproduce this error, and all input files.  Thanks!");
146         return;
147       }
148     }
149 
150   }
151 
152   /**
153    * Inserts the annotations contained in <code> scene </code> into
154    * the class file contained in <code> fileName </code>, and write
155    * the result back into <code> fileName </code>.
156    *
157    * @param scene the scene containing the annotations to insert into a class
158    * @param fileName the file name of the class the annotations should be
159    * inserted into.  Should be a file name that can be resolved from
160    * the current working directory, which means it should end in ".class"
161    * for standard Java class files.
162    * @param overwrite controls behavior when an annotation exists on a
163    * particular element in both the scene and the class file.  If true,
164    * then the one from the scene is used; else the the existing annotation
165    * in the class file is retained.
166    * @throws IOException if there is a problem reading from or writing to
167    * <code> fileName </code>
168    */
insert( AScene scene, String fileName, boolean overwrite)169   public static void insert(
170       AScene scene, String fileName, boolean overwrite)
171   throws IOException {
172     assert fileName.endsWith(".class");
173 
174     // can't just call other insert, because this closes the input stream
175     InputStream in = new FileInputStream(fileName);
176     ClassReader cr = new ClassReader(in);
177     in.close();
178 
179     ClassAnnotationSceneWriter cw =
180       new ClassAnnotationSceneWriter(cr, scene, overwrite);
181     cr.accept(cw, false);
182 
183     OutputStream fos = new FileOutputStream(fileName);
184     fos.write(cw.toByteArray());
185     fos.close();
186   }
187 
188   /**
189    * Inserts the annotations contained in <code> scene </code> into
190    * the class file read from <code> in </code>, and writes the resulting
191    * class file into <code> out </code>.  <code> in </code> should be a stream
192    * of bytes that specify a valid Java class file, and <code> out </code> will
193    * contain a stream of bytes in the same format, and will also contain the
194    * annotations from <code> scene </code>.
195    *
196    * @param scene the scene containing the annotations to insert into a class
197    * @param in the input stream from which to read a class
198    * @param out the output stream the merged class should be written to
199    * @param overwrite controls behavior when an annotation exists on a
200    * particular element in both the scene and the class file.  If true,
201    * then the one from the scene is used; else the the existing annotation
202    * in the class file is retained.
203    * @throws IOException if there is a problem reading from <code> in </code> or
204    * writing to <code> out </code>
205    */
insert(AScene scene, InputStream in, OutputStream out, boolean overwrite)206   public static void insert(AScene scene, InputStream in,
207       OutputStream out, boolean overwrite) throws IOException {
208     ClassReader cr = new ClassReader(in);
209 
210     ClassAnnotationSceneWriter cw =
211       new ClassAnnotationSceneWriter(cr, scene, overwrite);
212 
213     cr.accept(cw, false);
214 
215     out.write(cw.toByteArray());
216   }
217 
218   /**
219    * Inserts the annotations contained in <code> scene </code> into
220    * the class <code> in </code>, and writes the resulting
221    * class file into <code> out </code>.  <code> in </code> should be the
222    * name of a fully-qualified class, and <code> out </code> should be the
223    * name of a file to output the resulting class file to.
224    *
225    * @param scene the scene containing the annotations to insert into a class
226    * @param className the fully qualified class to read
227    * @param outputFileName the name of the output file the class should be written to
228    * @param overwrite controls behavior when an annotation exists on a
229    * particular element in both the scene and the class file.  If true,
230    * then the one from the scene is used; else the the existing annotation
231    * in the class file is retained.
232    * @throws IOException if there is a problem reading from <code> in </code> or
233    * writing to <code> out </code>
234    */
insert(AScene scene, String className, String outputFileName, boolean overwrite)235   public static void insert(AScene scene,
236       String className, String outputFileName, boolean overwrite) throws IOException {
237     ClassReader cr = new ClassReader(className);
238 
239     ClassAnnotationSceneWriter cw =
240       new ClassAnnotationSceneWriter(cr, scene, overwrite);
241 
242     cr.accept(cw, false);
243 
244     OutputStream fos = new FileOutputStream(outputFileName);
245     fos.write(cw.toByteArray());
246     fos.close();
247   }
248 }
249