package edu.neu.ccs.demeter.dj; import edu.neu.ccs.demeter.aplib.*; import edu.neu.ccs.demeter.aplib.cd.Part; import java.lang.reflect.*; import java.lang.reflect.Modifier; import java.io.*; import java.util.*; /** A graph whose nodes are classes and whose edges are subclass, superclass, and part-of relationships between classes. */ public class ClassGraph extends edu.neu.ccs.demeter.aplib.cd.ClassGraph { static String version = "DJ version 0.8.6"; /** The DJ version string. */ public static String getVersion() { return version; } /** @deprecated getVersion() */ public static String version() { return getVersion(); } public static boolean debug = false; boolean fields = true, methods = true; /** Make a class graph from all classes in the default package, including all non-static fields and non-static non-void no-argument methods. */ public ClassGraph() { this(""); } /** Make a class graph from all classes in the package named pkg, including all non-static fields and non-static non-void no-argument methods. */ public ClassGraph(String pkg) { this(pkg, true, true); } /** Make a class graph from all classes in the default package. If f is true, include all non-static fields; if m is true, include all non-static non-void no-argument methods. These settings will apply by default for all classes added to the class graph in the future. */ public ClassGraph(boolean f, boolean m) { this("", f, m); } /** Make a class graph from all classes in the package named pkg. If f is true, include all non-static fields; if m is true, include all non-static non-void no-argument methods. These settings will apply by default for all classes added to the class graph in the future. */ public ClassGraph(String pkg, boolean f, boolean m) { super(); fields = f; methods = m; addClass(Object.class, false, false); addPackage(pkg); // FIXME: only add these if they're used? addClass(java.util.Collection.class, false, false); addRepetitionEdge("java.util.Collection", "java.lang.Object"); addClass(Collection.class, false, false); addRepetitionEdge("edu.neu.ccs.demeter.dj.Collection", "java.lang.Object"); } /** Make a class graph with just the classes and edges in tg. */ public ClassGraph(Traversal tg) { ClassGraph cg = (ClassGraph) tg.getClassGraph(); Iterator edges = tg.getEdgeSets().iterator(); while (edges.hasNext()) { EdgeI edge = ((Traversal.EdgeSet) edges.next()).getEdge(); Class cl = cg.getNodeClass(edge.getSource()); // FIXME: call addClass, but don't add supers namesClasses.put(cl.getName(), cl); EdgeI newedge = addEdge(edge); if (edge instanceof Part && ((Part) edge).isDerived()) ((Part) newedge).markDerived(); } } /** Make a class graph with just the classes and edges in cg reachable by following s. */ public ClassGraph(ClassGraph cg, Strategy s) throws TraversalException { this(new Traversal(s, cg)); } /** Make a class graph with just the classes and edges in cg reachable by following strategy s. */ public ClassGraph(ClassGraph cg, String s) throws TraversalException { this(cg, new Strategy(s)); } /** The set of packages that have been added. */ protected Set packages = new HashSet(); /** Add all classes in package p that can be found on the class path. */ public void addPackage(String p) { if (!packages.add(p)) return; String pkgprefix = p.equals("") ? "" : p + "."; String pkgdir = p.replace('.', File.separatorChar); String classpath = System.getProperty("java.class.path"); for (int i = 0; i < classpath.length(); i++) { int j = classpath.indexOf(File.pathSeparatorChar, i); if (j == -1) j = classpath.length(); File path = new File(classpath.substring(i, j)); i = j; if (path.isDirectory()) { File classdir = new File(path, pkgdir); if (debug) System.out.println("Searching " + classdir); File classfiles[] = classdir.listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { return name.endsWith(".class"); } }); if (classfiles != null) { for (int n = 0; n < classfiles.length; n++) { File classfile = classfiles[n]; String clname = classfile.getName(); clname = pkgprefix + clname.substring(0, clname.length() - 6); try { Class cl = Class.forName(clname); if (debug) System.out.println("Adding " + cl); addClass(cl); } catch (ClassNotFoundException e) { System.err.println("Warning: found " + classfile + " but could not load class " + clname + "."); } } } } else { // FIXME: jar/zip file } } } /** A table of classes and what parts have been added. {@link edu.neu.ccs.demeter.aplib.cd.ClassGraph#definesClass} will be true for classes we've added on either end of an edge, but we want to keep track of edge sources in particular. */ protected Map classesAdded = new HashMap(); /** Values in the {@link #classesAdded} map. fields is true if all fields have been added; methods is true if all methods have been added. */ protected class Added { boolean fields, methods; } /* A table of classes indexed by name. Includes both ends of edges added. */ protected Map namesClasses = new HashMap(); /** Add cl and all its non-static members to the class graph as construction edges, if they haven't already been added. */ public void addClass(Class cl) { addClass(cl, fields, methods); } /** Add cl to the class graph, if it hasn't already been added. If addFields is true, add all its non-static fields as construction edges. If addMethods is true, add all its non-static non-void methods with no arguments as derived construction edges. */ public void addClass(Class cl, boolean addFields, boolean addMethods) { namesClasses.put(cl.getName(), cl); Added added = (Added) classesAdded.get(cl); if (added != null && (added.fields || !addFields) && (added.methods || !addMethods)) // We already added this class and everything we needed from it. return; boolean addSupers = false; if (added == null) { added = new Added(); classesAdded.put(cl, added); addSupers = true; } addFields = addFields && !added.fields; addMethods = addMethods && !added.methods; String name = cl.getName(); // Add fields and/or methods as construction edges. if (addFields) { added.fields = true; Field fields[] = cl.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { Field field = fields[i]; int mod = field.getModifiers(); if (Modifier.isStatic(mod)) { // FIXME: add these too? } else { if (debug) System.out.println("Adding " + field); addConstructionEdge(name, field); } } } if (addMethods) { added.methods = true; Method methods[] = cl.getDeclaredMethods(); if (debug) System.out.println(Arrays.asList(methods)); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; int mod = method.getModifiers(); if (Modifier.isStatic(mod)) { // FIXME: add these too? } else if (method.getParameterTypes().length == 0 && // Adding clone() to the graph is just asking for trouble... // FIXME: just add methods starting with "get". !method.getName().equals("clone")) { if (debug) System.out.println("Adding " + method); addConstructionEdge(name, method); } } } if (addSupers) { Class rep = getRepetitionType(cl); if (rep == null) { // Add the superclass as an alternation/inheritance pair. // Recurse up the superclass chain. Class sup = cl.getSuperclass(); if (sup != null) { String supname = sup.getName(); addAlternationEdge(supname, name); addInheritanceEdge(name, supname); addClass(sup, false, false); } // Add the implemented interfaces as alternation/inheritance pairs. // This includes superclasses of interfaces. Recurse up the chain. Class interfaces[] = cl.getInterfaces(); for (int i = 0; i < interfaces.length; i++) { String ifname = interfaces[i].getName(); addAlternationEdge(ifname, name); addInheritanceEdge(name, ifname); addClass(interfaces[i], false, false); } } else { // Repetition class: just add the repetition edge, and // shortcut the inheritance hierarchy to avoid Collection. addRepetitionEdge(name, rep.getName()); addAlternationEdge("java.lang.Object", name); addInheritanceEdge(name, "java.lang.Object"); } } } /** Add a field as a construction edge. */ public Part addConstructionEdge(Field field) { return addConstructionEdge(field.getDeclaringClass().getName(), field); } /** Add a no-args method as a construction edge. */ public Part addConstructionEdge(Method method) { return addConstructionEdge(method.getDeclaringClass().getName(), method); } /** Add a field as a construction edge. */ public Part addConstructionEdge(String source, Field field) { return addConstructionEdge(source, edgeLabel(field), edgeTarget(field)); } /** Add a no-args method as a construction edge. */ public Part addConstructionEdge(String source, Method method) { Part edge = addConstructionEdge(source, edgeLabel(method), edgeTarget(method)); if (edge != null) edge.markDerived(); return edge; } /** Add a class member as a construction edge. */ public Part addConstructionEdge(String source, String label, Class target) { if (target.equals(Void.TYPE)) return null; while (target.isArray()) { // FIXME: aplib should allow array types? target = target.getComponentType(); } namesClasses.put(target.getName(), target); Part ret = addConstructionEdge(source, label, target.getName()); // Add the target and its superclasses/interfaces, but don't add // the fields or methods. This prevents us from dragging in // everything from an external package; if the package later gets // added, then the fields and methods will get added. addClass(target, false, false); return ret; } static boolean isCollectionClass(Class cl) { // FIXME: what about Map? return Collection.class.isAssignableFrom(cl) || java.util.Collection.class.isAssignableFrom(cl); } /** Check the special secret marker field to signify a repetition class. */ static Class getRepetitionType(Class cl) { try { Field f = cl.getDeclaredField("_repeatedPart"); int m = f.getModifiers(); if (Modifier.isPrivate(m) && Modifier.isStatic(m)) return f.getType(); } catch (NoSuchFieldException e) { } return null; } /** Add a repetition edge from source to target. */ public void addRepetitionEdge(String source, String target) { // The AP Library doesn't care if an edge has multiplicity, so // just add it as a construction edge with a distinguished label. addConstructionEdge(source, repetitionEdgeLabel(), target); // FIXME: add repetition edges to the aplib.cd package. } public static boolean isRepetitionEdge(EdgeI edge) { return edge.isConstructionEdge() && edge.getLabel().equals(repetitionEdgeLabel()); } static String repetitionEdgeLabel() { return "elements"; } /** The Class object corresponding to the node o. */ public Class getNodeClass(Object node) { return (Class) namesClasses.get(String.valueOf(node)); } /** The Class object corresponding to the node named name. */ public Class getClassNamed(String name) { return (Class) namesClasses.get(name); } /** The slice of the object graph rooted at o determined by s. */ public ObjectGraphSlice slice(Object o, Strategy s) { return new ObjectGraph(o, this).slice(s); } /** The slice of the object graph rooted at o determined by strategy s. */ public ObjectGraphSlice slice(Object o, String s) { return new ObjectGraph(o, this).slice(s); } /** Traverse the object graph rooted at o along s, visiting v at each node and returning the value of v.getReturnValue() at the end of the traversal. */ public Object traverse(Object o, Strategy s, Visitor v) { return slice(o, s).traverse(new Visitor[] { v }); } /** Traverse the object graph rooted at o along s, visiting the visitors in array v in sequence at each node and returning the value of v[0].getReturnValue() at the end of the traversal. */ public Object traverse(Object o, Strategy s, Visitor[] v) { return slice(o, s).traverse(v); } /** Fetch the object in the object graph rooted at o corresponding to the target of s. */ public Object fetch(Object o, Strategy s) throws FetchException { return slice(o, s).fetch(); } /** Gather into a list the objects in the object graph rooted at o corresponding to the target of s. */ public List gather(Object o, Strategy s) { return slice(o, s).gather(); } /** A fixed-size List backed by the object graph rooted at o. The elements of the list are the objects reachable by s in the object graph with the given root whose class is a target of s. Throws TraversalSourceException if the type of root is not a source of the strategy. Note that this list (like the List returned by Arrays.asList, but unlike the List returned by gather()) is write-through, i.e. modifying it will modify the underlying object graph, and vice versa. */ public List asList(Object o, Strategy s) { return slice(o, s).asList(); } /** Traverse the object graph rooted at o along strategy s using v, returning the value of v.getReturnValue() at the end of the traversal. */ public Object traverse(Object o, String s, Visitor v) { return slice(o, s).traverse(new Visitor[] { v }); } /** Traverse the object graph rooted at o along strategy s using visitors in array v in parallel, returning the value of v[0].getReturnValue() at the end of the traversal. */ public Object traverse(Object o, String s, Visitor[] v) { return slice(o, s).traverse(v); } /** Fetch the object in the object graph rooted at o corresponding to the target of strategy s. */ public Object fetch(Object o, String s) throws FetchException { return slice(o, s).fetch(); } /** Gather the objects in the object graph rooted at o corresponding to the target of strategy s. */ public List gather(Object o, String s) { return slice(o, s).gather(); } /** A fixed-size List backed by the object graph rooted at o. The elements of the list are the objects reachable by strategy s in the object graph with the given root whose class is a target of s. Throws TraversalSourceException if the type of root is not a source of the strategy. Note that this list (like the List returned by Arrays.asList, but unlike the List returned by gather()) is write-through, i.e. modifying it will modify the underlying object graph, and vice versa. */ public List asList(Object o, String s) { return slice(o, s).asList(); } // EdgeI doesn't differentiate between derived and non-derived // edges, so we need to mangle & unmangle the label. static String edgeLabel(Field field) { return fieldEdgeLabel(field.getName()); } static String fieldEdgeLabel(String name) { return /* "f" + */ name; } static String edgeLabel(Method method) { return methodEdgeLabel(method.getName()); } static String methodEdgeLabel(String name) { return /* "m" + */ name; } boolean isDerivedEdge(EdgeI edge) { /* return edge.getLabel().startsWith("m"); */ return edge instanceof Part && ((Part) edge).isDerived(); } static String memberName(EdgeI edge) { return edge.getLabel() /* .substring(1) */; } static Class edgeTarget(Field field) { return field.getType(); } static Class edgeTarget(Method method) { return method.getReturnType(); } /** Testing stub. */ public static void main(String args[]) { ClassGraph cg = new ClassGraph(); cg.addPackage("edu.neu.ccs.demeter.dj"); cg.addPackage("edu.neu.ccs.demeter.aplib"); cg.addPackage("edu.neu.ccs.demeter.aplib.cd"); System.out.println(cg); } }