package annotations.io.classfile; /*>>> import org.checkerframework.checker.nullness.qual.*; */ import java.io.*; import com.sun.tools.javac.main.CommandLine; import org.objectweb.asm.ClassReader; import plume.Option; import plume.Options; import annotations.el.AScene; import annotations.io.IndexFileParser; /** * A <code> ClassFileWriter </code> provides methods for inserting annotations * from an {@link annotations.el.AScene} into a class file. */ public class ClassFileWriter { @Option("-h print usage information and exit") public static boolean help = false; @Option("print version information and exit") public static boolean version = false; private static String linesep = System.getProperty("line.separator"); static String usage = "usage: insert-annotations [options] class1 indexfile1 class2 indexfile2 ..." + "" + linesep + "For each class/index file pair (a.b.C a.b.C.jaif), read annotations from" + linesep + "the index file a.b.C.jaif and insert them into the class a.b.C, then" + linesep + "output the merged class file to a.b.C.class" + linesep + "Each class is either a fully-qualified name of a class on your classpath," + linesep + "or a path to a .class file, such as e.g. /.../path/to/a/b/C.class ." + linesep + "Arguments beginning with a single '@' are interpreted as argument files to" + linesep + "be read and expanded into the command line. Options:"; /** * Main method meant to a a convenient way to write annotations from an index * file to a class file. For programmatic access to this * tool, one should probably use the insert() methods instead. * <p> * Usage: java annotations.io.ClassFileWriter <em>options</em> [classfile indexfile] ... * <p> * <em>options</em> include:<pre> * -h, --help print usage information and exit * --version print version information and exit * </pre> * @param args options and classes and index files to analyze; * @throws IOException if a class file or index file cannot be opened/written */ public static void main(String[] args) throws IOException { Options options = new Options(usage, ClassFileWriter.class); String[] file_args; try { String[] cl_args = CommandLine.parse(args); file_args = options.parse_or_usage(cl_args); } catch (IOException ex) { System.err.println(ex); System.err.println("(For non-argfile beginning with \"@\", use \"@@\" for initial \"@\"."); System.err.println("Alternative for filenames: indicate directory, e.g. as './@file'."); System.err.println("Alternative for flags: use '=', as in '-o=@Deprecated'.)"); file_args = null; // Eclipse compiler issue workaround System.exit(1); } if (version) { System.out.printf("insert-annotations (%s)", ClassFileReader.INDEX_UTILS_VERSION); } if (help) { options.print_usage(); } if (version || help) { System.exit(-1); } if (file_args.length == 0) { options.print_usage("No arguments given."); System.exit(-1); } if (file_args.length % 2 == 1) { options.print_usage("Must supply an even number of arguments."); System.exit(-1); } // check args for well-formed names for (int i = 0; i < file_args.length; i += 2) { if (!ClassFileReader.checkClass(file_args[i])) { System.exit(-1); } } for (int i = 0; i < file_args.length; i++) { String className = file_args[i]; i++; if (i >= file_args.length) { System.out.println("Error: incorrect number of arguments"); System.out.println("Run insert-annotations --help for usage information"); return; } String indexFileName = file_args[i]; AScene scene = new AScene(); IndexFileParser.parseFile(indexFileName, scene); // annotations loaded from index file into scene, now insert them // into class file try { if (className.endsWith(".class")) { System.out.printf("Adding annotations to class file %s%n", className); insert(scene, className, true); } else { String outputFileName = className + ".class"; System.out.printf("Reading class file %s; writing with annotations to %s%n", className, outputFileName); insert(scene, className, outputFileName, true); } } catch (IOException e) { System.out.printf("IOException: %s%n", e.getMessage()); return; } catch (Exception e) { System.out.println("Unknown error trying to insert annotations from: " + indexFileName + " to " + className); e.printStackTrace(); System.out.println("Please submit a bug report at"); System.out.println(" https://github.com/typetools/annotation-tools/issues"); System.out.println("Be sure to include a copy of the following output trace, instructions on how"); System.out.println("to reproduce this error, and all input files. Thanks!"); return; } } } /** * Inserts the annotations contained in <code> scene </code> into * the class file contained in <code> fileName </code>, and write * the result back into <code> fileName </code>. * * @param scene the scene containing the annotations to insert into a class * @param fileName the file name of the class the annotations should be * inserted into. Should be a file name that can be resolved from * the current working directory, which means it should end in ".class" * for standard Java class files. * @param overwrite controls behavior when an annotation exists on a * particular element in both the scene and the class file. If true, * then the one from the scene is used; else the the existing annotation * in the class file is retained. * @throws IOException if there is a problem reading from or writing to * <code> fileName </code> */ public static void insert( AScene scene, String fileName, boolean overwrite) throws IOException { assert fileName.endsWith(".class"); // can't just call other insert, because this closes the input stream InputStream in = new FileInputStream(fileName); ClassReader cr = new ClassReader(in); in.close(); ClassAnnotationSceneWriter cw = new ClassAnnotationSceneWriter(cr, scene, overwrite); cr.accept(cw, false); OutputStream fos = new FileOutputStream(fileName); fos.write(cw.toByteArray()); fos.close(); } /** * Inserts the annotations contained in <code> scene </code> into * the class file read from <code> in </code>, and writes the resulting * class file into <code> out </code>. <code> in </code> should be a stream * of bytes that specify a valid Java class file, and <code> out </code> will * contain a stream of bytes in the same format, and will also contain the * annotations from <code> scene </code>. * * @param scene the scene containing the annotations to insert into a class * @param in the input stream from which to read a class * @param out the output stream the merged class should be written to * @param overwrite controls behavior when an annotation exists on a * particular element in both the scene and the class file. If true, * then the one from the scene is used; else the the existing annotation * in the class file is retained. * @throws IOException if there is a problem reading from <code> in </code> or * writing to <code> out </code> */ public static void insert(AScene scene, InputStream in, OutputStream out, boolean overwrite) throws IOException { ClassReader cr = new ClassReader(in); ClassAnnotationSceneWriter cw = new ClassAnnotationSceneWriter(cr, scene, overwrite); cr.accept(cw, false); out.write(cw.toByteArray()); } /** * Inserts the annotations contained in <code> scene </code> into * the class <code> in </code>, and writes the resulting * class file into <code> out </code>. <code> in </code> should be the * name of a fully-qualified class, and <code> out </code> should be the * name of a file to output the resulting class file to. * * @param scene the scene containing the annotations to insert into a class * @param className the fully qualified class to read * @param outputFileName the name of the output file the class should be written to * @param overwrite controls behavior when an annotation exists on a * particular element in both the scene and the class file. If true, * then the one from the scene is used; else the the existing annotation * in the class file is retained. * @throws IOException if there is a problem reading from <code> in </code> or * writing to <code> out </code> */ public static void insert(AScene scene, String className, String outputFileName, boolean overwrite) throws IOException { ClassReader cr = new ClassReader(className); ClassAnnotationSceneWriter cw = new ClassAnnotationSceneWriter(cr, scene, overwrite); cr.accept(cw, false); OutputStream fos = new FileOutputStream(outputFileName); fos.write(cw.toByteArray()); fos.close(); } }