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 }