author | jgodinez |
Fri, 18 Feb 2011 12:04:36 -0800 | |
changeset 8364 | 07cd2e4eb172 |
parent 5547 | f4b087cbb361 |
permissions | -rw-r--r-- |
1 | 1 |
/* |
5547
f4b087cbb361
6941466: Oracle rebranding changes for Hotspot repositories
trims
parents:
1
diff
changeset
|
2 |
* Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved. |
1 | 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 |
* |
|
5547
f4b087cbb361
6941466: Oracle rebranding changes for Hotspot repositories
trims
parents:
1
diff
changeset
|
19 |
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
f4b087cbb361
6941466: Oracle rebranding changes for Hotspot repositories
trims
parents:
1
diff
changeset
|
20 |
* or visit www.oracle.com if you need additional information or have any |
f4b087cbb361
6941466: Oracle rebranding changes for Hotspot repositories
trims
parents:
1
diff
changeset
|
21 |
* questions. |
1 | 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 |
} |