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