/*******************************************************************************
* Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
******************************************************************************/
package org.eclipse.persistence.tools.workbench.utility.classfile.tools;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.persistence.tools.workbench.utility.ClassTools;
import org.eclipse.persistence.tools.workbench.utility.classfile.ClassFile;
/**
* This class can be used to build a dependency graph
* for all the classes in the specified classpath entry
* (either a directory or a JAR)
* or the specified collection of class files.
*
* @see ClassFile
*/
public class ClassDependencyGraph {
/**
* nodes for the class files found in the specified
* classpath entry or collection of class files;
* this will not hold ALL the nodes, since some nodes
* will be for classes not found in the original collection
* of class files
*/
private Collection nodes;
/**
* ALL the nodes created, keyed by class name
*/
private Map allNodes;
// ********** constructors **********
public ClassDependencyGraph(String classpathEntry) {
this(new ClassFileIterator(classpathEntry));
}
public ClassDependencyGraph(Iterator classFiles) {
super();
this.initialize(classFiles);
}
// ********** initialization **********
private void initialize(Iterator classFiles) {
Collection tempNames = new ArrayList(500);
this.allNodes = new HashMap(500);
while (classFiles.hasNext()) {
ClassFile classFile = (ClassFile) classFiles.next();
tempNames.add(classFile.className());
this.addClassDependencyNode(classFile);
}
// directly hold *only* the classes found in the classpath entry;
// there will be other nodes, but they will only be held by these nodes
this.nodes = new ArrayList(500);
for (Iterator stream = tempNames.iterator(); stream.hasNext();) {
this.nodes.add(this.allNodes.get(stream.next()));
}
// now, set the backpointers for the main nodes
for (Iterator stream = this.nodes.iterator(); stream.hasNext();) {
((Node) stream.next()).addReferencingNodesFrom(this.nodes);
}
}
private void addClassDependencyNode(ClassFile classFile) {
String className = classFile.className();
Node node = (Node) this.allNodes.get(className);
if (node == null) {
node = new Node(className);
this.allNodes.put(className, node);
}
node.initialize(classFile, this.allNodes);
}
// ********** queries **********
public Iterator nodes() {
return this.nodes.iterator();
}
public String toString() {
return ClassTools.shortClassNameForObject(this) + '(' + this.nodes.size() + '/' + this.allNodes.size() + " nodes)";
}
// ********** static methods **********
// public static void main(String[] args) throws Exception {
// String classpathEntry = null;
// if (args == null || args.length == 0) {
// classpathEntry = toplinkJarFileName();
// } else {
// classpathEntry = args[0];
// }
//
// long startTime = (new Date()).getTime();
// ClassDependencyGraph tree = new ClassDependencyGraph(classpathEntry);
// long endTime = (new Date()).getTime();
// float elapsedSeconds = (endTime - startTime) / 1000;
// System.out.println(tree.toString() + " (creation time: " + elapsedSeconds + " seconds)");
// }
//
// private static String toplinkJarFileName() {
// return ClasspathTools.entryForFileName("toplink_g.jar", ClasspathTools.javaClasspath());
// }
//
// ********** member classes **********
/**
* a node in the class dependency graph;
* it holds a pair of collections:
* - the nodes that this node references
* - the nodes that reference this node
*/
public static class Node {
/** this node's class */
private String className;
/** classes referenced by this node's class */
private Map referencedNodes;
/** classes that reference this node's class */
private Map referencingNodes;
Node(String className) {
super();
this.className = className;
this.referencedNodes = new IdentityHashMap();
this.referencingNodes = new IdentityHashMap();
}
/**
* build referencedNodes from the class file;
* tempNodes maps class names to their nodes
*/
void initialize(ClassFile classFile, Map allNodes) {
// this method should only be called once...
if ( ! this.referencedNodes.isEmpty()) {
throw new IllegalStateException();
}
String[] names = classFile.referencedClassNames();
for (int i = names.length; i-- > 0;) {
String name = names[i];
Node node = (Node) allNodes.get(name);
if (node == null) {
node = new Node(name);
allNodes.put(name, node);
}
this.referencedNodes.put(node, node);
}
}
/**
* add to referencingNodes any of the specified
* nodes that actually reference this node
*/
void addReferencingNodesFrom(Collection nodes) {
for (Iterator stream = nodes.iterator(); stream.hasNext();) {
Node node = (Node) stream.next();
node.addReferencingNodeTo(this);
}
}
/**
* if this node references the other node, add this
* node to the other node's referencing nodes
*/
private void addReferencingNodeTo(Node node) {
if (this.referencedNodes.containsKey(node)) {
node.addReferencingNode(this);
}
}
void addReferencingNode(Node node) {
this.referencingNodes.put(node, node);
}
public String getClassName() {
return this.className;
}
public Iterator referencedNodes() {
return this.referencedNodes.keySet().iterator();
}
public Iterator referencingNodes() {
return this.referencingNodes.keySet().iterator();
}
public String toString() {
return ClassTools.toStringClassNameForObject(this) + "(" + this.className + ")";
}
}
}