1 package annotations.io.classfile;
2 
3 /*>>>
4 import org.checkerframework.checker.nullness.qual.*;
5 */
6 
7 import java.io.*;
8 
9 import plume.*;
10 
11 import com.sun.tools.javac.main.CommandLine;
12 
13 import org.objectweb.asm.ClassReader;
14 
15 import annotations.el.AScene;
16 import annotations.io.IndexFileWriter;
17 
18 /**
19  * A <code> ClassFileReader </code> provides methods for reading in annotations
20  *  from a class file into an {@link annotations.el.AScene}.
21  */
22 public class ClassFileReader {
23 
24   public static final String INDEX_UTILS_VERSION
25     = "Annotation File Utilities v3.6.44";
26 
27   @Option("-b omit annotations from bridge (compiler-created) methods")
28   public static boolean ignore_bridge_methods = false;
29 
30   @Option("-h print usage information and exit")
31   public static boolean help = false;
32 
33   @Option("-v print version information and exit")
34   public static boolean version = false;
35 
36   private static String linesep = System.getProperty("line.separator");
37 
38   static String usage
39     = "extract-annotations [options] class1 class2 ..."
40     + linesep
41     + "Each argument is a class a.b.C (that is on your classpath) or a class file"
42     + linesep
43     + "a/b/C.class.  Extracts the annotations from each such argument and prints"
44     + linesep
45     + "them in index-file format to a.b.C.jaif .  Arguments beginning with a"
46     + linesep
47     + "single '@' are interpreted as argument files to be read and expanded into"
48     + linesep
49     + "the command line.  Options:";
50 
51   /**
52    * From the command line, read annotations from a class file and write
53    * them to an index file.  Also see the Anncat tool, which is more
54    * versatile (and which calls this as a subroutine).
55    * <p>
56    *
57    * For usage information, supply the -h or --help option.
58    * <p>
59    *
60    * For programmatic access to this tool, use the read() methods instead.
61    * <p>
62    *
63    * @param args options and classes to analyze;
64    * @throws IOException if a class file cannot be found
65    */
main(String[] args)66   public static void main(String[] args) throws IOException {
67     Options options = new Options(usage, ClassFileReader.class);
68     String[] file_args;
69 
70     try {
71       String[] cl_args = CommandLine.parse(args);
72       file_args = options.parse_or_usage(cl_args);
73     } catch (IOException ex) {
74       System.err.println(ex);
75       System.err.println("(For non-argfile beginning with \"@\", use \"@@\" for initial \"@\".");
76       System.err.println("Alternative for filenames: indicate directory, e.g. as './@file'.");
77       System.err.println("Alternative for flags: use '=', as in '-o=@Deprecated'.)");
78       file_args = null;  // Eclipse compiler issue workaround
79       System.exit(1);
80     }
81 
82     if (version) {
83       System.out.printf("extract-annotations (%s)", 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 
97     // check args for well-formed names
98     for (String arg : file_args) {
99       if (!checkClass(arg)) {
100         System.exit(-1);
101       }
102     }
103 
104     for (String origName : file_args) {
105       System.out.println("reading: " + origName);
106       String className = origName;
107       if (origName.endsWith(".class")) {
108           origName = origName.replace(".class", "");
109       }
110 
111       AScene scene = new AScene();
112       try {
113         if (className.endsWith(".class")) {
114           read(scene, className);
115         } else {
116           readFromClass(scene, className);
117         }
118         String outputFile = origName + ".jaif";
119         System.out.println("printing results to : " + outputFile);
120         IndexFileWriter.write(scene, outputFile);
121       } catch (IOException e) {
122         System.out.println("There was an error in reading class: " + origName);
123         System.out.println(
124             "Did you ensure that this class is on your classpath?");
125         return;
126       } catch (Exception e) {
127         System.out.println("Unknown error trying to extract annotations from: " +
128             origName);
129         System.out.println(e.getMessage());
130         e.printStackTrace();
131         System.out.println("Please submit a bug report at");
132         System.out.println("  https://github.com/typetools/annotation-tools/issues");
133         System.out.println("Be sure to include a copy of the output trace, instructions on how");
134         System.out.println("to reproduce this error, and all input files.  Thanks!");
135         return;
136       }
137     }
138   }
139 
140   /**
141    * If s is not a valid representation of a class, print a warning message
142    * and return false.
143    */
checkClass(String arg)144   public static boolean checkClass(String arg) {
145     // check for invalid class file paths with '.'
146     if (!arg.contains(".class") && arg.contains("/")) {
147       System.out.println("Error: bad class " + arg);
148       System.out.println("Use a fully qualified class name such as java.lang.Object");
149       System.out.println("or a filename such as .../path/to/MyClass.class");
150       return false;
151     }
152     return true;
153   }
154 
155   /**
156    * Reads the annotations from the class file <code> fileName </code>
157    * and inserts them into <code> scene </code>.
158    * <code> fileName </code> should be a file name that can be resolved from
159    * the current working directory, which means it should end in ".class"
160    * for standard Java class files.
161    *
162    * @param scene the scene into which the annotations should be inserted
163    * @param fileName the file name of the class the annotations should be
164    * read from
165    * @throws IOException if there is a problem reading from
166    * <code> fileName </code>
167    */
read(AScene scene, String fileName)168   public static void read(AScene scene, String fileName)
169   throws IOException {
170     read(scene, new FileInputStream(fileName));
171   }
172 
173   /**
174    * Reads the annotations from the class <code> className </code>,
175    * assumed to be in your classpath,
176    * and inserts them into <code> scene </code>.
177    *
178    * @param scene the scene into which the annotations should be inserted
179    * @param className the name of the class to read in
180    * @throws IOException if there is a problem reading <code> className </code>
181    */
readFromClass(AScene scene, String className)182   public static void readFromClass(AScene scene, String className)
183   throws IOException {
184     read(scene, new ClassReader(className));
185   }
186 
187   /**
188    * Reads the annotations from the class file <code> fileName </code>
189    * and inserts them into <code> scene </code>.
190    *
191    * @param scene the scene into which the annotations should be inserted
192    * @param in an input stream containing the class that the annotations
193    * should be read from
194    * @throws IOException if there is a problem reading from <code> in </code>
195    */
read(AScene scene, InputStream in)196   public static void read(AScene scene, InputStream in)
197   throws IOException {
198     read(scene, new ClassReader(in));
199   }
200 
read(AScene scene, ClassReader cr)201   public static void read(AScene scene, ClassReader cr) {
202     ClassAnnotationSceneReader ca =
203         new ClassAnnotationSceneReader(cr, scene, ignore_bridge_methods);
204     cr.accept(ca, true);
205   }
206 
207 }
208