comparison agent/make/ClosureFinder.java @ 0:a61af66fc99e jdk7-b24

Initial load
author duke
date Sat, 01 Dec 2007 00:00:00 +0000
parents
children c18cbe5936b8
comparison
equal deleted inserted replaced
-1:000000000000 0:a61af66fc99e
1 /*
2 * Copyright 2003-2004 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
20 * CA 95054 USA or visit www.sun.com if you need additional information or
21 * have any questions.
22 *
23 */
24
25 import java.io.*;
26 import java.util.*;
27
28
29 /**
30 <p> This class finds transitive closure of dependencies from a given
31 root set of classes. If your project has lots of .class files and you
32 want to ship only those .class files which are used (transitively)
33 from a root set of classes, then you can use this utility. </p> <p>
34 How does it work?</p>
35
36 <p> We walk through all constant pool entries of a given class and
37 find all modified UTF-8 entries. Anything that looks like a class name is
38 considered as a class and we search for that class in the given
39 classpath. If we find a .class of that name, then we add that class to
40 list.</p>
41
42 <p> We could have used CONSTANT_ClassInfo type constants only. But
43 that will miss classes used through Class.forName or xyz.class
44 construct. But, if you refer to a class name in some other string we
45 would include it as dependency :(. But this is quite unlikely
46 anyway. To look for exact Class.forName argument(s) would involve
47 bytecode analysis. Also, we handle only simple reflection. If you
48 accept name of a class from externally (for eg properties file or
49 command line args for example, this utility will not be able to find
50 that dependency. In such cases, include those classes in the root set.
51 </p>
52 */
53
54 public class ClosureFinder {
55 private Collection roots; // root class names Collection<String>
56 private Map visitedClasses; // set of all dependencies as a Map
57 private String classPath; // classpath to look for .class files
58 private String[] pathComponents; // classpath components
59 private static final boolean isWindows = File.separatorChar != '/';
60
61 public ClosureFinder(Collection roots, String classPath) {
62 this.roots = roots;
63 this.classPath = classPath;
64 parseClassPath();
65 }
66
67 // parse classPath into pathComponents array
68 private void parseClassPath() {
69 List paths = new ArrayList();
70 StringTokenizer st = new StringTokenizer(classPath, File.pathSeparator);
71 while (st.hasMoreTokens())
72 paths.add(st.nextToken());
73
74 Object[] arr = paths.toArray();
75 pathComponents = new String[arr.length];
76 System.arraycopy(arr, 0, pathComponents, 0, arr.length);
77 }
78
79 // if output is aleady not computed, compute it now
80 // result is a map from class file name to base path where the .class was found
81 public Map find() {
82 if (visitedClasses == null) {
83 visitedClasses = new HashMap();
84 computeClosure();
85 }
86 return visitedClasses;
87 }
88
89 // compute closure for all given root classes
90 private void computeClosure() {
91 for (Iterator rootsItr = roots.iterator(); rootsItr.hasNext();) {
92 String name = (String) rootsItr.next();
93 name = name.substring(0, name.indexOf(".class"));
94 computeClosure(name);
95 }
96 }
97
98
99 // looks up for .class in pathComponents and returns
100 // base path if found, else returns null
101 private String lookupClassFile(String classNameAsPath) {
102 for (int i = 0; i < pathComponents.length; i++) {
103 File f = new File(pathComponents[i] + File.separator +
104 classNameAsPath + ".class");
105 if (f.exists()) {
106 if (isWindows) {
107 String name = f.getName();
108 // Windows reports special devices AUX,NUL,CON as files
109 // under any directory. It does not care about file extention :-(
110 if (name.compareToIgnoreCase("AUX.class") == 0 ||
111 name.compareToIgnoreCase("NUL.class") == 0 ||
112 name.compareToIgnoreCase("CON.class") == 0) {
113 return null;
114 }
115 }
116 return pathComponents[i];
117 }
118 }
119 return null;
120 }
121
122
123 // from JVM spec. 2'nd edition section 4.4
124 private static final int CONSTANT_Class = 7;
125 private static final int CONSTANT_FieldRef = 9;
126 private static final int CONSTANT_MethodRef = 10;
127 private static final int CONSTANT_InterfaceMethodRef = 11;
128 private static final int CONSTANT_String = 8;
129 private static final int CONSTANT_Integer = 3;
130 private static final int CONSTANT_Float = 4;
131 private static final int CONSTANT_Long = 5;
132 private static final int CONSTANT_Double = 6;
133 private static final int CONSTANT_NameAndType = 12;
134 private static final int CONSTANT_Utf8 = 1;
135
136 // whether a given string may be a class name?
137 private boolean mayBeClassName(String internalClassName) {
138 int len = internalClassName.length();
139 for (int s = 0; s < len; s++) {
140 char c = internalClassName.charAt(s);
141 if (!Character.isJavaIdentifierPart(c) && c != '/')
142 return false;
143 }
144 return true;
145 }
146
147 // compute closure for a given class
148 private void computeClosure(String className) {
149 if (visitedClasses.get(className) != null) return;
150 String basePath = lookupClassFile(className);
151 if (basePath != null) {
152 visitedClasses.put(className, basePath);
153 try {
154 File classFile = new File(basePath + File.separator + className + ".class");
155 FileInputStream fis = new FileInputStream(classFile);
156 DataInputStream dis = new DataInputStream(fis);
157 // look for .class signature
158 if (dis.readInt() != 0xcafebabe) {
159 System.err.println(classFile.getAbsolutePath() + " is not a valid .class file");
160 return;
161 }
162
163 // ignore major and minor version numbers
164 dis.readShort();
165 dis.readShort();
166
167 // read number of constant pool constants
168 int numConsts = (int) dis.readShort();
169 String[] strings = new String[numConsts];
170
171 // zero'th entry is unused
172 for (int cpIndex = 1; cpIndex < numConsts; cpIndex++) {
173 int constType = (int) dis.readByte();
174 switch (constType) {
175 case CONSTANT_Class:
176 case CONSTANT_String:
177 dis.readShort(); // string name index;
178 break;
179
180 case CONSTANT_FieldRef:
181 case CONSTANT_MethodRef:
182 case CONSTANT_InterfaceMethodRef:
183 case CONSTANT_NameAndType:
184 case CONSTANT_Integer:
185 case CONSTANT_Float:
186 // all these are 4 byte constants
187 dis.readInt();
188 break;
189
190 case CONSTANT_Long:
191 case CONSTANT_Double:
192 // 8 byte constants
193 dis.readLong();
194 // occupies 2 cp entries
195 cpIndex++;
196 break;
197
198
199 case CONSTANT_Utf8: {
200 strings[cpIndex] = dis.readUTF();
201 break;
202 }
203
204 default:
205 System.err.println("invalid constant pool entry");
206 return;
207 }
208 }
209
210 // now walk thru the string constants and look for class names
211 for (int s = 0; s < numConsts; s++) {
212 if (strings[s] != null && mayBeClassName(strings[s]))
213 computeClosure(strings[s].replace('/', File.separatorChar));
214 }
215
216 } catch (IOException exp) {
217 // ignore for now
218 }
219
220 }
221 }
222
223 // a sample main that accepts roots classes in a file and classpath as args
224 public static void main(String[] args) {
225 if (args.length != 2) {
226 System.err.println("Usage: ClosureFinder <root class file> <class path>");
227 System.exit(1);
228 }
229
230 List roots = new ArrayList();
231 try {
232 FileInputStream fis = new FileInputStream(args[0]);
233 DataInputStream dis = new DataInputStream(fis);
234 String line = null;
235 while ((line = dis.readLine()) != null) {
236 if (isWindows) {
237 line = line.replace('/', File.separatorChar);
238 }
239 roots.add(line);
240 }
241 } catch (IOException exp) {
242 System.err.println(exp.getMessage());
243 System.exit(2);
244 }
245
246 ClosureFinder cf = new ClosureFinder(roots, args[1]);
247 Map out = cf.find();
248 Iterator res = out.keySet().iterator();
249 for(; res.hasNext(); ) {
250 String className = (String) res.next();
251 System.out.println(className + ".class");
252 }
253 }
254 }