hotspot/agent/make/ClosureFinder.java
changeset 13874 a93c03275528
parent 13872 bbee2239e884
parent 13873 7b72e3873785
child 13875 bb683b4a79ac
equal deleted inserted replaced
13872:bbee2239e884 13874:a93c03275528
     1 /*
       
     2  * Copyright (c) 2003, 2004, Oracle and/or its affiliates. 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 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 
       
    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 }