/* * Copyright 2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package com.sun.tools.classfile; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import com.sun.tools.classfile.Dependency.Finder; import com.sun.tools.classfile.Dependency.Filter; import com.sun.tools.classfile.Dependency.Location; import com.sun.tools.classfile.Type.ArrayType; import com.sun.tools.classfile.Type.ClassSigType; import com.sun.tools.classfile.Type.ClassType; import com.sun.tools.classfile.Type.MethodType; import com.sun.tools.classfile.Type.SimpleType; import com.sun.tools.classfile.Type.TypeParamType; import com.sun.tools.classfile.Type.WildcardType; import static com.sun.tools.classfile.ConstantPool.*; /** * A framework for determining {@link Dependency dependencies} between class files. * * A {@link Dependency.Finder finder} is used to identify the dependencies of * individual classes. Some finders may return subtypes of {@code Dependency} to * further characterize the type of dependency, such as a dependency on a * method within a class. * * A {@link Dependency.Filter filter} may be used to restrict the set of * dependencies found by a finder. * * Dependencies that are found may be passed to a {@link Dependencies.Recorder * recorder} so that the dependencies can be stored in a custom data structure. */ public class Dependencies { /** * Thrown when a class file cannot be found. */ public static class ClassFileNotFoundException extends Exception { private static final long serialVersionUID = 3632265927794475048L; public ClassFileNotFoundException(String className) { super(className); this.className = className; } public ClassFileNotFoundException(String className, Throwable cause) { this(className); initCause(cause); } public final String className; } /** * Thrown when an exception is found processing a class file. */ public static class ClassFileError extends Error { private static final long serialVersionUID = 4111110813961313203L; public ClassFileError(Throwable cause) { initCause(cause); } } /** * Service provider interface to locate and read class files. */ public interface ClassFileReader { /** * Get the ClassFile object for a specified class. * @param className the name of the class to be returned. * @return the ClassFile for the given class * @throws Dependencies#ClassFileNotFoundException if the classfile cannot be * found */ public ClassFile getClassFile(String className) throws ClassFileNotFoundException; } /** * Service provide interface to handle results. */ public interface Recorder { /** * Record a dependency that has been found. * @param d */ public void addDependency(Dependency d); } /** * Get the default finder used to locate the dependencies for a class. * @return the default finder */ public static Finder getDefaultFinder() { return new APIDependencyFinder(AccessFlags.ACC_PRIVATE); } /** * Get a finder used to locate the API dependencies for a class. * These include the superclass, superinterfaces, and classes referenced in * the declarations of fields and methods. The fields and methods that * are checked can be limited according to a specified access. * The access parameter must be one of {@link AccessFlags#ACC_PUBLIC ACC_PUBLIC}, * {@link AccessFlags#ACC_PRIVATE ACC_PRIVATE}, * {@link AccessFlags#ACC_PROTECTED ACC_PROTECTED}, or 0 for * package private access. Members with greater than or equal accessibility * to that specified will be searched for dependencies. * @param access the access of members to be checked * @return an API finder */ public static Finder getAPIFinder(int access) { return new APIDependencyFinder(access); } /** * Get the finder used to locate the dependencies for a class. * @return the finder */ public Finder getFinder() { if (finder == null) finder = getDefaultFinder(); return finder; } /** * Set the finder used to locate the dependencies for a class. * @param f the finder */ public void setFinder(Finder f) { f.getClass(); // null check finder = f; } /** * Get the default filter used to determine included when searching * the transitive closure of all the dependencies. * Unless overridden, the default filter accepts all dependencies. * @return the default filter. */ public static Filter getDefaultFilter() { return DefaultFilter.instance(); } /** * Get a filter which uses a regular expression on the target's class name * to determine if a dependency is of interest. * @param pattern the pattern used to match the target's class name * @return a filter for matching the target class name with a regular expression */ public static Filter getRegexFilter(Pattern pattern) { return new TargetRegexFilter(pattern); } /** * Get a filter which checks the package of a target's class name * to determine if a dependency is of interest. The filter checks if the * package of the target's class matches any of a set of given package * names. The match may optionally match subpackages of the given names as well. * @param packageNames the package names used to match the target's class name * @param matchSubpackages whether or not to match subpackages as well * @return a filter for checking the target package name against a list of package names */ public static Filter getPackageFilter(Set packageNames, boolean matchSubpackages) { return new TargetPackageFilter(packageNames, matchSubpackages); } /** * Get the filter used to determine the dependencies included when searching * the transitive closure of all the dependencies. * Unless overridden, the default filter accepts all dependencies. * @return the filter */ public Filter getFilter() { if (filter == null) filter = getDefaultFilter(); return filter; } /** * Set the filter used to determine the dependencies included when searching * the transitive closure of all the dependencies. * @param f the filter */ public void setFilter(Filter f) { f.getClass(); // null check filter = f; } /** * Find the dependencies of a class, using the current * {@link Dependencies#getFinder finder} and * {@link Dependencies#getFilter filter}. * The search may optionally include the transitive closure of all the * filtered dependencies, by also searching in the classes named in those * dependencies. * @param classFinder a finder to locate class files * @param rootClassNames the names of the root classes from which to begin * searching * @param transitiveClosure whether or not to also search those classes * named in any filtered dependencies that are found. * @return the set of dependencies that were found * @throws ClassFileNotFoundException if a required class file cannot be found * @throws ClassFileError if an error occurs while processing a class file, * such as an error in the internal class file structure. */ public Set findAllDependencies( ClassFileReader classFinder, Set rootClassNames, boolean transitiveClosure) throws ClassFileNotFoundException { final Set results = new HashSet(); Recorder r = new Recorder() { public void addDependency(Dependency d) { results.add(d); } }; findAllDependencies(classFinder, rootClassNames, transitiveClosure, r); return results; } /** * Find the dependencies of a class, using the current * {@link Dependencies#getFinder finder} and * {@link Dependencies#getFilter filter}. * The search may optionally include the transitive closure of all the * filtered dependencies, by also searching in the classes named in those * dependencies. * @param classFinder a finder to locate class files * @param rootClassNames the names of the root classes from which to begin * searching * @param transitiveClosure whether or not to also search those classes * named in any filtered dependencies that are found. * @param recorder a recorder for handling the results * @throws ClassFileNotFoundException if a required class file cannot be found * @throws ClassFileError if an error occurs while processing a class file, * such as an error in the internal class file structure. */ public void findAllDependencies( ClassFileReader classFinder, Set rootClassNames, boolean transitiveClosure, Recorder recorder) throws ClassFileNotFoundException { Set doneClasses = new HashSet(); getFinder(); // ensure initialized getFilter(); // ensure initialized // Work queue of names of classfiles to be searched. // Entries will be unique, and for classes that do not yet have // dependencies in the results map. Deque deque = new LinkedList(rootClassNames); String className; while ((className = deque.poll()) != null) { assert (!doneClasses.contains(className)); doneClasses.add(className); ClassFile cf = classFinder.getClassFile(className); // The following code just applies the filter to the dependencies // followed for the transitive closure. for (Dependency d: finder.findDependencies(cf)) { recorder.addDependency(d); if (transitiveClosure && filter.accepts(d)) { String cn = d.getTarget().getClassName(); if (!doneClasses.contains(cn)) deque.add(cn); } } } } private Filter filter; private Finder finder; /** * A location identifying a class. */ static class SimpleLocation implements Location { public SimpleLocation(String className) { this.className = className; } /** * Get the name of the class being depended on. This name will be used to * locate the class file for transitive dependency analysis. * @return the name of the class being depended on */ public String getClassName() { return className; } @Override public boolean equals(Object other) { if (this == other) return true; if (!(other instanceof SimpleLocation)) return false; return (className.equals(((SimpleLocation) other).className)); } @Override public int hashCode() { return className.hashCode(); } @Override public String toString() { return className; } private String className; } /** * A dependency of one class on another. */ static class SimpleDependency implements Dependency { public SimpleDependency(Location origin, Location target) { this.origin = origin; this.target = target; } public Location getOrigin() { return origin; } public Location getTarget() { return target; } @Override public boolean equals(Object other) { if (this == other) return true; if (!(other instanceof SimpleDependency)) return false; SimpleDependency o = (SimpleDependency) other; return (origin.equals(o.origin) && target.equals(o.target)); } @Override public int hashCode() { return origin.hashCode() * 31 + target.hashCode(); } @Override public String toString() { return origin + ":" + target; } private Location origin; private Location target; } /** * This class accepts all dependencies. */ static class DefaultFilter implements Filter { private static DefaultFilter instance; static DefaultFilter instance() { if (instance == null) instance = new DefaultFilter(); return instance; } public boolean accepts(Dependency dependency) { return true; } } /** * This class accepts those dependencies whose target's class name matches a * regular expression. */ static class TargetRegexFilter implements Filter { TargetRegexFilter(Pattern pattern) { this.pattern = pattern; } public boolean accepts(Dependency dependency) { return pattern.matcher(dependency.getTarget().getClassName()).matches(); } private final Pattern pattern; } /** * This class accepts those dependencies whose class name is in a given * package. */ static class TargetPackageFilter implements Filter { TargetPackageFilter(Set packageNames, boolean matchSubpackages) { for (String pn: packageNames) { if (pn.length() == 0) // implies null check as well throw new IllegalArgumentException(); } this.packageNames = packageNames; this.matchSubpackages = matchSubpackages; } public boolean accepts(Dependency dependency) { String cn = dependency.getTarget().getClassName(); int lastSep = cn.lastIndexOf("/"); String pn = (lastSep == -1 ? "" : cn.substring(0, lastSep)); if (packageNames.contains(pn)) return true; if (matchSubpackages) { for (String n: packageNames) { if (pn.startsWith(n + ".")) return true; } } return false; } private final Set packageNames; private final boolean matchSubpackages; } /** * This class identifies class names directly or indirectly in the constant pool. */ static class ClassDependencyFinder extends BasicDependencyFinder { public Iterable findDependencies(ClassFile classfile) { Visitor v = new Visitor(classfile); for (CPInfo cpInfo: classfile.constant_pool.entries()) { v.scan(cpInfo); } return v.deps; } } /** * This class identifies class names in the signatures of classes, fields, * and methods in a class. */ static class APIDependencyFinder extends BasicDependencyFinder { APIDependencyFinder(int access) { switch (access) { case AccessFlags.ACC_PUBLIC: case AccessFlags.ACC_PROTECTED: case AccessFlags.ACC_PRIVATE: case 0: showAccess = access; break; default: throw new IllegalArgumentException("invalid access 0x" + Integer.toHexString(access)); } } public Iterable findDependencies(ClassFile classfile) { try { Visitor v = new Visitor(classfile); v.addClass(classfile.super_class); v.addClasses(classfile.interfaces); // inner classes? for (Field f : classfile.fields) { if (checkAccess(f.access_flags)) v.scan(f.descriptor, f.attributes); } for (Method m : classfile.methods) { if (checkAccess(m.access_flags)) { v.scan(m.descriptor, m.attributes); Exceptions_attribute e = (Exceptions_attribute) m.attributes.get(Attribute.Exceptions); if (e != null) v.addClasses(e.exception_index_table); } } return v.deps; } catch (ConstantPoolException e) { throw new ClassFileError(e); } } boolean checkAccess(AccessFlags flags) { // code copied from javap.Options.checkAccess boolean isPublic = flags.is(AccessFlags.ACC_PUBLIC); boolean isProtected = flags.is(AccessFlags.ACC_PROTECTED); boolean isPrivate = flags.is(AccessFlags.ACC_PRIVATE); boolean isPackage = !(isPublic || isProtected || isPrivate); if ((showAccess == AccessFlags.ACC_PUBLIC) && (isProtected || isPrivate || isPackage)) return false; else if ((showAccess == AccessFlags.ACC_PROTECTED) && (isPrivate || isPackage)) return false; else if ((showAccess == 0) && (isPrivate)) return false; else return true; } private int showAccess; } static abstract class BasicDependencyFinder implements Finder { private Map locations = new HashMap(); Location getLocation(String className) { Location l = locations.get(className); if (l == null) locations.put(className, l = new SimpleLocation(className)); return l; } class Visitor implements ConstantPool.Visitor, Type.Visitor { private ConstantPool constant_pool; private Location origin; Set deps; Visitor(ClassFile classFile) { try { constant_pool = classFile.constant_pool; origin = getLocation(classFile.getName()); deps = new HashSet(); } catch (ConstantPoolException e) { throw new ClassFileError(e); } } void scan(Descriptor d, Attributes attrs) { try { scan(new Signature(d.index).getType(constant_pool)); Signature_attribute sa = (Signature_attribute) attrs.get(Attribute.Signature); if (sa != null) scan(new Signature(sa.signature_index).getType(constant_pool)); } catch (ConstantPoolException e) { throw new ClassFileError(e); } } void scan(CPInfo cpInfo) { cpInfo.accept(this, null); } void scan(Type t) { t.accept(this, null); } void addClass(int index) throws ConstantPoolException { if (index != 0) { String name = constant_pool.getClassInfo(index).getBaseName(); if (name != null) addDependency(name); } } void addClasses(int[] indices) throws ConstantPoolException { for (int i: indices) addClass(i); } private void addDependency(String name) { deps.add(new SimpleDependency(origin, getLocation(name))); } // ConstantPool.Visitor methods public Void visitClass(CONSTANT_Class_info info, Void p) { try { if (info.getName().startsWith("[")) new Signature(info.name_index).getType(constant_pool).accept(this, null); else addDependency(info.getBaseName()); return null; } catch (ConstantPoolException e) { throw new ClassFileError(e); } } public Void visitDouble(CONSTANT_Double_info info, Void p) { return null; } public Void visitFieldref(CONSTANT_Fieldref_info info, Void p) { return visitRef(info, p); } public Void visitFloat(CONSTANT_Float_info info, Void p) { return null; } public Void visitInteger(CONSTANT_Integer_info info, Void p) { return null; } public Void visitInterfaceMethodref(CONSTANT_InterfaceMethodref_info info, Void p) { return visitRef(info, p); } public Void visitLong(CONSTANT_Long_info info, Void p) { return null; } public Void visitNameAndType(CONSTANT_NameAndType_info info, Void p) { try { new Signature(info.type_index).getType(constant_pool).accept(this, null); return null; } catch (ConstantPoolException e) { throw new ClassFileError(e); } } public Void visitMethodref(CONSTANT_Methodref_info info, Void p) { return visitRef(info, p); } public Void visitString(CONSTANT_String_info info, Void p) { return null; } public Void visitUtf8(CONSTANT_Utf8_info info, Void p) { return null; } private Void visitRef(CPRefInfo info, Void p) { try { visitClass(info.getClassInfo(), p); return null; } catch (ConstantPoolException e) { throw new ClassFileError(e); } } // Type.Visitor methods private void findDependencies(Type t) { if (t != null) t.accept(this, null); } private void findDependencies(List ts) { if (ts != null) { for (Type t: ts) t.accept(this, null); } } public Void visitSimpleType(SimpleType type, Void p) { return null; } public Void visitArrayType(ArrayType type, Void p) { findDependencies(type.elemType); return null; } public Void visitMethodType(MethodType type, Void p) { findDependencies(type.paramTypes); findDependencies(type.returnType); findDependencies(type.throwsTypes); return null; } public Void visitClassSigType(ClassSigType type, Void p) { findDependencies(type.superclassType); findDependencies(type.superinterfaceTypes); return null; } public Void visitClassType(ClassType type, Void p) { findDependencies(type.outerType); addDependency(type.name); findDependencies(type.typeArgs); return null; } public Void visitTypeParamType(TypeParamType type, Void p) { findDependencies(type.classBound); findDependencies(type.interfaceBounds); return null; } public Void visitWildcardType(WildcardType type, Void p) { findDependencies(type.boundType); return null; } } } }