agent/make/ClosureFinder.java

Fri, 14 Sep 2012 21:50:58 -0700

author
amurillo
date
Fri, 14 Sep 2012 21:50:58 -0700
changeset 4059
80e4129f0e28
parent 1907
c18cbe5936b8
permissions
-rw-r--r--

Added tag hs25-b01 for changeset 9b076bc3ab67

     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  */
    25 import java.io.*;
    26 import java.util.*;
    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>
    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>
    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 */
    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 != '/';
    61     public ClosureFinder(Collection roots, String classPath) {
    62         this.roots = roots;
    63         this.classPath = classPath;
    64         parseClassPath();
    65     }
    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());
    74         Object[] arr = paths.toArray();
    75         pathComponents = new String[arr.length];
    76         System.arraycopy(arr, 0, pathComponents, 0, arr.length);
    77     }
    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     }
    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     }
    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     }
   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;
   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     }
   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                 }
   163                 // ignore major and minor version numbers
   164                 dis.readShort();
   165                 dis.readShort();
   167                 // read number of constant pool constants
   168                 int numConsts = (int) dis.readShort();
   169                 String[] strings = new String[numConsts];
   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;
   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;
   190                     case CONSTANT_Long:
   191                     case CONSTANT_Double:
   192                         // 8 byte constants
   193                         dis.readLong();
   194                         // occupies 2 cp entries
   195                         cpIndex++;
   196                         break;
   199                     case CONSTANT_Utf8: {
   200                         strings[cpIndex] = dis.readUTF();
   201                         break;
   202                     }
   204                     default:
   205                         System.err.println("invalid constant pool entry");
   206                         return;
   207                     }
   208                 }
   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             }
   216             } catch (IOException exp) {
   217                 // ignore for now
   218             }
   220         }
   221     }
   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         }
   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         }
   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 }

mercurial