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();
}
}