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);
}
}