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