6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 *
23 */
24 package com.sun.classanalyzer;
25
26 import java.io.BufferedReader;
27 import java.io.FileInputStream;
28 import java.io.IOException;
29 import java.io.InputStreamReader;
30 import java.io.PrintWriter;
31 import java.io.File;
32 import java.util.ArrayDeque;
33 import java.util.Deque;
34 import java.util.HashMap;
35 import java.util.LinkedList;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.TreeSet;
40
41 import com.sun.tools.classfile.*;
42 import com.sun.tools.classfile.ConstantPool.*;
43 import static com.sun.tools.classfile.ConstantPool.*;
44 import com.sun.tools.classfile.Instruction.TypeKind;
45 import com.sun.tools.classfile.Type.*;
46
47 /**
48 * Generate the module config for the boot module with
49 * a given set of roots (classes or methods) and exclude list.
50 *
51 * This tool does method-level dependency analysis starting
52 * from the root set and follows references transitively as follows:
53 * <ul>
54 * <li>For a given class, it will parse the ClassFile to
55 * find its superclass and superinterfaces and also
56 * its static initializer <clinit>.</li>
57 * <li>For each method, it will parse its Code attribute
58 * to look for a Methodref, Fieldref, and InterfaceMethodref.
59 * </li>
60 * <li>For each Fieldref, it will include the type of
61 * the field in the dependency.</li>
62 * <li>For each MethodRef, it will follow all references in
63 * that method.</li>
64 * <li>For each InterfaceMethodref, it will follow all references in
65 * that method defined its implementation classes in
66 * the resulting dependency list.</li>
67 * </ul>
68 *
69 * Limitation:
70 * <ul>
71 * <li>For each Methodref, it only parses the method of
72 * the specified type. It doesn't analyze the class hierarchy
73 * and follow references of its subclasses since it ends up
74 * pulls in many unnecessary dependencies. For now,
75 * the list of subclasses and methods need to be listed in
76 * the root set.</li>
77 * </ul>
78 *
79 * @author Mandy Chung
80 */
81 public class BootAnalyzer {
82
83 public static void main(String[] args) throws Exception {
84 String jdkhome = null;
85 String config = null;
86 String output = ".";
87 boolean printClassList = false;
88
89 // process arguments
90 int i = 0;
91 while (i < args.length) {
92 String arg = args[i++];
93 if (arg.equals("-jdkhome")) {
94 if (i < args.length) {
95 jdkhome = args[i++];
96 } else {
97 usage();
98 }
99 } else if (arg.equals("-config")) {
100 config = args[i++];
101 } else if (arg.equals("-output")) {
102 output = args[i++];
103 } else if (arg.equals("-classlist")) {
104 printClassList = true;
105 } else {
106 usage();
107 }
108 }
109
110
111
112 if (jdkhome == null || config == null) {
113 usage();
114 }
115
116 File jre = new File(jdkhome, "jre");
117 if (jre.exists()) {
118 ClassPath.setJDKHome(jdkhome);
119 } else {
120 File classes = new File(jdkhome, "classes");
121 if (classes.exists()) {
122 ClassPath.setClassPath(classes.getCanonicalPath());
123 } else {
124 throw new RuntimeException("Invalid jdkhome: " + jdkhome);
125 }
126 }
127
128 parseConfigFile(config);
129 followRoots();
130
131 // create output directory if it doesn't exist
132 File dir = new File(output);
133 if (!dir.isDirectory()) {
134 if (!dir.exists()) {
135 boolean created = dir.mkdir();
136 if (!created) {
137 throw new RuntimeException("Unable to create `" + dir + "'");
138 }
139 }
140 }
141
142 String bootmodule = "boot";
143 String bootconfig = resolve(dir, bootmodule, "config");
144 printBootConfig(bootconfig, bootmodule);
145
146 List<ModuleConfig> list = ModuleConfig.readConfigurationFile(bootconfig);
147 Module module = Module.addModule(list.get(0));
148 for (Klass k : Klass.getAllClasses()) {
149 module.addKlass(k);
150 }
151 module.fixupDependencies();
152
153 if (printClassList) {
154 module.printClassListTo(resolve(dir, bootmodule, "classlist"));
155 module.printSummaryTo(resolve(dir, bootmodule, "summary"));
156 }
157 }
158
159 // print boot.config file as an input to the ClassAnalyzer
160 private static void printBootConfig(String output, String bootmodule) throws IOException {
161
162 File f = new File(output);
163 PrintWriter writer = new PrintWriter(f);
164 try {
165 int count = 0;
166 writer.format("module %s {%n", bootmodule);
167 for (Klass k : Klass.getAllClasses()) {
168 if (count++ == 0) {
169 writer.format("%4s%7s %s", "", "include", k);
170 } else {
171 writer.format(",%n");
172 writer.format("%4s%7s %s", "", "", k);
173 }
174 }
175 writer.format(";%n}%n");
176 } finally {
177 writer.close();
178 }
321 KlassInfo getSuperclass() {
322 ensureParse();
323 return superclass;
324 }
325
326 List<KlassInfo> getInterfaces() {
327 ensureParse();
328 return java.util.Collections.unmodifiableList(interfaces);
329 }
330
331 void ensureParse() {
332 try {
333 getClassFileParser();
334 } catch (IOException e) {
335 throw new RuntimeException(e);
336 }
337 }
338
339 synchronized ClassFileParser getClassFileParser() throws IOException {
340 if (parser == null) {
341 parser = ClassPath.parserForClass(classname);
342 if (parser != null) {
343 parseClassFile();
344 List<String> descriptors = parse(new MethodDescriptor(classname + ".<clinit>", "()V", false));
345 }
346 }
347 return parser;
348 }
349
350 List<String> parse(MethodDescriptor md) {
351 ensureParse();
352 try {
353 List<String> descriptors = new LinkedList<String>();
354 for (Method m : parser.classfile.methods) {
355 String name = m.getName(parser.classfile.constant_pool);
356 String desc = parser.constantPoolParser.getDescriptor(m.descriptor.index);
357 if (name.equals(md.methodname)) {
358 if (md.descriptor.equals("*") || md.descriptor.equals(desc)) {
359 parseMethod(parser, m);
360 descriptors.add(desc);
361 }
787
788 public String visitMethodref(CONSTANT_Methodref_info info, Void p) {
789 return addMethodDescriptor(info, p);
790 }
791
792 public String visitModuleId(CONSTANT_ModuleId_info info, Void p) {
793 // skip
794 return null;
795 }
796
797 public String visitString(CONSTANT_String_info info, Void p) {
798 // skip
799 return null;
800 }
801
802 public String visitUtf8(CONSTANT_Utf8_info info, Void p) {
803 return null;
804 }
805 };
806 }
807 static boolean traceOn = System.getProperty("classanalyzer.debug") != null;
808
809 private static void trace(String format, Object... args) {
810 if (traceOn) {
811 System.out.format(format, args);
812 }
813 }
814
815 private static void usage() {
816 System.out.println("Usage: BootAnalyzer <options>");
817 System.out.println("Options: ");
818 System.out.println("\t-jdkhome <JDK home> where all jars will be parsed");
819 System.out.println("\t-config <roots for the boot module>");
820 System.out.println("\t-output <output dir>");
821 System.out.println("\t-classlist print class list and summary");
822 System.exit(-1);
823 }
824 }
|
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 *
23 */
24 package com.sun.classanalyzer;
25
26 import java.util.Collections;
27 import java.io.BufferedReader;
28 import java.io.FileInputStream;
29 import java.io.IOException;
30 import java.io.InputStreamReader;
31 import java.io.PrintWriter;
32 import java.io.File;
33 import java.util.ArrayDeque;
34 import java.util.Deque;
35 import java.util.HashMap;
36 import java.util.LinkedList;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Set;
40 import java.util.TreeSet;
41
42 import com.sun.tools.classfile.*;
43 import com.sun.tools.classfile.ConstantPool.*;
44 import static com.sun.tools.classfile.ConstantPool.*;
45 import com.sun.tools.classfile.Instruction.TypeKind;
46 import com.sun.tools.classfile.Type.*;
47 import com.sun.classanalyzer.ModuleInfo.PackageInfo;
48 import static com.sun.classanalyzer.Trace.*;
49
50 /**
51 * Generate the module config for the boot module with
52 * a given set of roots (classes or methods) and exclude list.
53 *
54 * This tool does method-level dependency analysis starting
55 * from the root set and follows references transitively as follows:
56 * <ul>
57 * <li>For a given class, it will parse the ClassFile to
58 * find its superclass and superinterfaces and also
59 * its static initializer <clinit>.</li>
60 * <li>For each method, it will parse its Code attribute
61 * to look for a Methodref, Fieldref, and InterfaceMethodref.
62 * </li>
63 * <li>For each Fieldref, it will include the type of
64 * the field in the dependency.</li>
65 * <li>For each MethodRef, it will follow all references in
66 * that method.</li>
67 * <li>For each InterfaceMethodref, it will follow all references in
68 * that method defined its implementation classes in
69 * the resulting dependency list.</li>
70 * </ul>
71 *
72 * Limitation:
73 * <ul>
74 * <li>For each Methodref, it only parses the method of
75 * the specified type. It doesn't analyze the class hierarchy
76 * and follow references of its subclasses since it ends up
77 * pulls in many unnecessary dependencies. For now,
78 * the list of subclasses and methods need to be listed in
79 * the root set.</li>
80 * </ul>
81 *
82 * @author Mandy Chung
83 */
84 public class BootAnalyzer {
85 private static ClassPaths cpaths;
86 public static void main(String[] args) throws Exception {
87 String jdkhome = null;
88 String config = null;
89 String output = ".";
90 String version = "7-ea";
91 boolean printClassList = false;
92
93 // process arguments
94 int i = 0;
95 while (i < args.length) {
96 String arg = args[i++];
97 if (arg.equals("-jdkhome")) {
98 if (i < args.length) {
99 jdkhome = args[i++];
100 } else {
101 usage();
102 }
103 } else if (arg.equals("-version")) {
104 version = args[i++];
105 } else if (arg.equals("-config")) {
106 config = args[i++];
107 } else if (arg.equals("-output")) {
108 output = args[i++];
109 } else if (arg.equals("-classlist")) {
110 printClassList = true;
111 } else {
112 usage();
113 }
114 }
115
116 if (jdkhome == null || config == null) {
117 usage();
118 }
119
120 File jre = new File(jdkhome, "jre");
121 if (jre.exists()) {
122 cpaths = ClassPaths.newJDKClassPaths(jdkhome);
123 } else {
124 File classes = new File(jdkhome, "classes");
125 if (classes.exists()) {
126 cpaths = ClassPaths.newInstance(classes.getCanonicalPath());
127 } else {
128 throw new RuntimeException("Invalid jdkhome: " + jdkhome);
129 }
130 }
131
132 parseConfigFile(config);
133 followRoots();
134
135 // create output directory if it doesn't exist
136 File dir = new File(output);
137 if (!dir.isDirectory()) {
138 if (!dir.exists()) {
139 boolean created = dir.mkdir();
140 if (!created) {
141 throw new RuntimeException("Unable to create `" + dir + "'");
142 }
143 }
144 }
145
146 String bootmodule = "boot";
147 String bootconfig = resolve(dir, bootmodule, "config");
148 printBootConfig(bootconfig, bootmodule);
149
150 ModuleBuilder builder =
151 new ModuleBuilder(Collections.singletonList(bootconfig), version);
152
153 assert Module.getAllModules().size() == 1;
154 Module module = null;
155 for (Module m : Module.getAllModules()) {
156 module = m;
157 break;
158 }
159 for (Klass k : Klass.getAllClasses()) {
160 module.addKlass(k);
161 }
162 builder.run();
163
164 if (printClassList) {
165 ClassListWriter writer = new ClassListWriter(dir, module);
166 writer.printClassList();
167 writer.printResourceList();
168 printModuleSummary(dir, module);
169 }
170 }
171
172 private static void printModuleSummary(File dir, Module m) throws IOException {
173 PrintWriter summary =
174 new PrintWriter(Files.resolve(dir, m.name(), "summary"));
175 try {
176 long total = 0L;
177 int count = 0;
178 summary.format("%10s\t%10s\t%s%n", "Bytes", "Classes", "Package name");
179 for (PackageInfo info : m.getModuleInfo().packages()) {
180 if (info.count > 0) {
181 summary.format("%10d\t%10d\t%s%n",
182 info.filesize, info.count, info.pkgName);
183 total += info.filesize;
184 count += info.count;
185 }
186 }
187 summary.format("%nTotal: %d bytes (uncompressed) %d classes%n",
188 total, count);
189 } finally {
190 summary.close();
191 }
192 }
193
194 // print boot.config file as an input to the ClassAnalyzer
195 private static void printBootConfig(String output, String bootmodule) throws IOException {
196
197 File f = new File(output);
198 PrintWriter writer = new PrintWriter(f);
199 try {
200 int count = 0;
201 writer.format("module %s {%n", bootmodule);
202 for (Klass k : Klass.getAllClasses()) {
203 if (count++ == 0) {
204 writer.format("%4s%7s %s", "", "include", k);
205 } else {
206 writer.format(",%n");
207 writer.format("%4s%7s %s", "", "", k);
208 }
209 }
210 writer.format(";%n}%n");
211 } finally {
212 writer.close();
213 }
356 KlassInfo getSuperclass() {
357 ensureParse();
358 return superclass;
359 }
360
361 List<KlassInfo> getInterfaces() {
362 ensureParse();
363 return java.util.Collections.unmodifiableList(interfaces);
364 }
365
366 void ensureParse() {
367 try {
368 getClassFileParser();
369 } catch (IOException e) {
370 throw new RuntimeException(e);
371 }
372 }
373
374 synchronized ClassFileParser getClassFileParser() throws IOException {
375 if (parser == null) {
376 parser = cpaths.parserForClass(classname);
377 if (parser != null) {
378 parseClassFile();
379 List<String> descriptors = parse(new MethodDescriptor(classname + ".<clinit>", "()V", false));
380 }
381 }
382 return parser;
383 }
384
385 List<String> parse(MethodDescriptor md) {
386 ensureParse();
387 try {
388 List<String> descriptors = new LinkedList<String>();
389 for (Method m : parser.classfile.methods) {
390 String name = m.getName(parser.classfile.constant_pool);
391 String desc = parser.constantPoolParser.getDescriptor(m.descriptor.index);
392 if (name.equals(md.methodname)) {
393 if (md.descriptor.equals("*") || md.descriptor.equals(desc)) {
394 parseMethod(parser, m);
395 descriptors.add(desc);
396 }
822
823 public String visitMethodref(CONSTANT_Methodref_info info, Void p) {
824 return addMethodDescriptor(info, p);
825 }
826
827 public String visitModuleId(CONSTANT_ModuleId_info info, Void p) {
828 // skip
829 return null;
830 }
831
832 public String visitString(CONSTANT_String_info info, Void p) {
833 // skip
834 return null;
835 }
836
837 public String visitUtf8(CONSTANT_Utf8_info info, Void p) {
838 return null;
839 }
840 };
841 }
842
843 private static void usage() {
844 System.out.println("Usage: BootAnalyzer <options>");
845 System.out.println("Options: ");
846 System.out.println("\t-jdkhome <JDK home> where all jars will be parsed");
847 System.out.println("\t-config <roots for the boot module>");
848 System.out.println("\t-output <output dir>");
849 System.out.println("\t-classlist print class list and summary");
850 System.exit(-1);
851 }
852 }
|