blob: 2ffce7be21143f3773aa8023a59ebf974320ea99 [file] [log] [blame]
package org.eclipse.jdt.internal.core.builder.impl;
/*
* (c) Copyright IBM Corp. 2000, 2001.
* All Rights Reserved.
*/
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.internal.core.Assert;
import org.eclipse.jdt.internal.core.builder.*;
import org.eclipse.jdt.internal.core.util.*;
import org.eclipse.jdt.internal.core.Util;
import java.util.*;
/**
* An explicit graph of dependencies between source elements and packages
* in the state.
*/
public class DependencyGraph implements IDumpable, Cloneable {
/**
* Maps ICompilationUnits to INodes in the dependency graph
*/
private LookupTable fCompilationUnits;
/**
* Maps IPackages to INodes in the dependency graph
*/
private LookupTable fNamespaces;
/**
* Maps ITypes to INodes in the graph
*/
private LookupTable fTypes;
/**
* Maps Strings (filenames) to INodes in the graph
*/
private LookupTable fZips;
/**
* DependencyGraph constructor comment.
*/
public DependencyGraph() {
fNamespaces = new LookupTable(11);
fCompilationUnits = new LookupTable(11);
fTypes = new LookupTable(11);
fZips = new LookupTable(11);
}
/**
* Creates a node for the object. Returns the added node.
*/
public INode add(Object element) {
INode node = getNodeFor(element);
// force creation of a node for the package
if (element instanceof PackageElement) {
getNodeFor(((PackageElement) element).getPackage());
}
return node;
}
/**
* Add the compilation unit, its dependencies, and any other relevant info
* from the compilation result to the graph.
* The compilation unit has just been compiled, so its status is COMPILED.
*/
public void add(PackageElement resultUnit, IType[] types, Vector vDependencies) {
// force the package node if not yet present
getNodeFor(resultUnit.getPackage());
JCUNode unitNode = (JCUNode)getNodeFor(resultUnit);
unitNode.setTypes(types);
INode node = unitNode;
// convert dependencies to INodes
INode[] deps = new INode[vDependencies.size()];
int count = 0;
for (Enumeration e = vDependencies.elements(); e.hasMoreElements();) {
Object o = e.nextElement();
INode depNode = getNodeFor(o);
deps[count++] = depNode;
}
node.setDependencies(deps);
}
/**
* Returns a copy of the dependency graph with all counters and flags reset
*/
protected Object clone() {
try {
DependencyGraph newGraph = (DependencyGraph) super.clone();
/* First pass: copy tables and all nodes,
* leaving dependencies and dependents pointing to old nodes. */
newGraph.fNamespaces = copyTableAndNodesWithoutReplacingDeps(fNamespaces);
newGraph.fCompilationUnits = copyTableAndNodesWithoutReplacingDeps(fCompilationUnits);
newGraph.fTypes = copyTableAndNodesWithoutReplacingDeps(fTypes);
newGraph.fZips = copyTableAndNodesWithoutReplacingDeps(fZips);
/* Second pass: replace dependencies and dependents to point to new nodes. */
replaceDeps(newGraph.fNamespaces, newGraph);
replaceDeps(newGraph.fCompilationUnits, newGraph);
replaceDeps(newGraph.fTypes, newGraph);
replaceDeps(newGraph.fZips, newGraph);
return newGraph;
}
catch (CloneNotSupportedException e) {
// Should not happen since we implement Cloneable
Assert.isTrue(false, "Unexpected clone exception in DependencyGraph.clone()"); //$NON-NLS-1$
return null;
}
}
/**
* Returns a copy of the dependency graph with all counters and flags reset
*/
public DependencyGraph copy() {
//this.integrityCheck();
DependencyGraph newGraph = (DependencyGraph) this.clone();
//newGraph.integrityCheck();
return newGraph;
}
/**
* Copies the edges from an old node to a new node. Any nodes that have
* to be created are added to the graph.
*/
protected void copyEdges(INode oldNode, INode newNode) {
/* copy dependencies */
INode[] oldDeps = oldNode.getDependencies();
INode[] newDeps = new INode[oldDeps.length];
/* create nodes in the new graph for each dependency in the old graph */
for (int i = oldDeps.length; --i >= 0;) {
newDeps[i] = getNodeFor(oldDeps[i]);
}
newNode.setDependencies(newDeps);
/**
* Don't have to add dependents, because dependents
* will add newNode as a dependency, creating both directional links
*/
}
/**
* Returns a copy of a table of nodes, with copies of the nodes,
* but with their dependencies and dependents not copied.
*/
private static LookupTable copyTableAndNodesWithoutReplacingDeps(LookupTable oldTable) {
LookupTable newTable = (LookupTable) oldTable.clone();
for (Enumeration e = oldTable.elements(); e.hasMoreElements();) {
AbstractNode node = (AbstractNode) e.nextElement();
newTable.put(node.getElement(), node.copyWithoutReplacingDeps());
}
return newTable;
}
/**
* Deletes the node in this graph corresponding to the given object,
* and answers it if found, or null if it wasn't found.
* The node must not have any dependents.
*/
public INode deleteNode(Object o) {
INode toRemove = getNodeFor(o, false);
if (toRemove != null) {
deleteNode(toRemove);
}
return toRemove;
}
/**
* Deletes the node in this graph corresponding to the given object,
* and answers it if found, or null if it wasn't found.
* The node must not have any dependents.
*/
INode deleteNode(INode toRemove) {
Assert.isTrue(toRemove.getDependents().length == 0);
Object value = null;
switch (toRemove.getKind()) {
case INode.JCU_NODE:
value = fCompilationUnits.remove(((JCUNode) toRemove).getPackageElement());
break;
case INode.TYPE_NODE:
value = fTypes.remove(((TypeNode) toRemove).getPackageElement());
break;
case INode.NAMESPACE_NODE:
value = fNamespaces.remove(((NamespaceNode) toRemove).getPackage());
break;
case INode.ZIP_NODE:
value = fZips.remove(((ZipNode)toRemove).getZipFile());
break;
default:
Assert.isTrue(false, "Attempt to delete unknown node type from dependency graph"); //$NON-NLS-1$
}
return (INode)value;
}
/**
* For debugging only. Dump the graph in readable form.
*/
public void dump(Dumper dumper) {
dumper.dumpMessage("Namespaces", ""); //$NON-NLS-2$ //$NON-NLS-1$
dumper.indent();
for (Enumeration e = fNamespaces.elements(); e.hasMoreElements();) {
AbstractNode node = (AbstractNode) e.nextElement();
dumper.dump(node);
}
dumper.outdent();
dumper.dumpMessage("JCUs", ""); //$NON-NLS-2$ //$NON-NLS-1$
dumper.indent();
for (Enumeration e = fCompilationUnits.elements(); e.hasMoreElements();) {
AbstractNode node = (AbstractNode) e.nextElement();
dumper.dump(node);
}
dumper.outdent();
dumper.dumpMessage("ZIPs", ""); //$NON-NLS-2$ //$NON-NLS-1$
dumper.indent();
for (Enumeration e = fZips.elements(); e.hasMoreElements();) {
AbstractNode node = (AbstractNode) e.nextElement();
dumper.dump(node);
}
dumper.outdent();
}
/**
* Debugging - Force the order counts.
*/
public void forceOrders() {
for (Enumeration e = fCompilationUnits.elements(); e.hasMoreElements();) {
AbstractNode node = (AbstractNode) e.nextElement();
node.getOrder();
}
}
/**
* Returns the elements which directly depend on the given element.
*/
public Object[] getDependents(Object element) {
INode node = getNodeFor(element, false);
if (node == null) {
return new Object[0];
}
INode[] dependentNodes = node.getDependents();
Object[] dependents = new Object[dependentNodes.length];
for (int i = 0; i < dependents.length; ++i) {
dependents[i] = dependentNodes[i].getElement();
}
return dependents;
}
/**
* Returns the number of bytes that the nodes of the dependency graph use.
* For debugging and profiling purposes only.
*/
public int getFootprint() {
int size = 0;
size += getFootprint(fCompilationUnits);
size += getFootprint(fNamespaces);
size += getFootprint(fTypes);
size += getFootprint(fZips);
return size;
}
/**
* Returns the number of bytes that the nodes of the dependency graph use.
* For debugging and profiling purposes only.
*/
public int getFootprint(Dictionary table) {
int size = 0;
for (Enumeration e = table.elements(); e.hasMoreElements();) {
AbstractNode node = (AbstractNode)e.nextElement();
size += node.getFootprint();
}
return size;
}
/**
* Returns the JCU nodes in the graph.
*/
public Enumeration getJCUNodes() {
return fCompilationUnits.elements();
}
/**
* Returns the packages this source element is directly dependent on.
*/
public IPackage[] getNamespaceDependencies(Object element) {
INode node = getNodeFor(element, false);
if (node == null) {
return new IPackage[0];
}
Vector vPackages = new Vector();
INode[] dependencies = node.getDependencies();
for (int i = 0; i < dependencies.length; i++) {
if (dependencies[i].getKind() == INode.NAMESPACE_NODE) {
vPackages.addElement(((NamespaceNode)dependencies[i]).getPackage());
}
}
IPackage[] results = new IPackage[vPackages.size()];
vPackages.copyInto(results);
return results;
}
/**
* Returns a node in this graph corresponding to the given object.
* A new node is created if necessary.
*/
public INode getNodeFor(Object o) {
return getNodeFor(o, true);
}
/**
* Returns a node in this graph corresponding to the given object.
* If create is true, the node is added if necessary.
*/
public INode getNodeFor(Object o, boolean create) {
if (o instanceof PackageElement) {
return getNodeFor((PackageElement)o, create);
} else if (o instanceof IPackage) {
return getNodeFor((IPackage)o, create);
} else if (o instanceof IPath) {
return getNodeFor((IPath)o, create);
}
else {
Assert.isTrue(false, "Unknown kind of node"); //$NON-NLS-1$
return null;
}
}
/**
* Returns the node for a zip file. If create is true, the node is
* created if necessary.
*/
public INode getNodeFor(IPath zipFile, boolean create) {
/* what about the namespaces for this zip?? */
INode zipNode = (INode) fZips.get(zipFile);
if (zipNode == null && create) {
zipNode = new ZipNode(zipFile);
fZips.put(zipFile, zipNode);
}
return zipNode;
}
/**
* Returns a node in this graph corresponding to the given node, which
* may be from another graph. A new node is created if necessary.
*/
INode getNodeFor(INode node) {
return getNodeFor(node.getElement(), true);
}
/**
* Returns the node for the given compilation unit. The node
* is added if necessary.
*/
public INode getNodeFor(PackageElement e) {
return getNodeFor(e, true);
}
/**
* Returns the node for the given package element.
* If create is true, the node is added if necessary.
* A package element may be a JCU, a class file, or a zip file.
* Class files are keyed by type handle, and zip files are keyed
* by the filename.
*/
public INode getNodeFor(PackageElement e, boolean create) {
INode node;
if (e.isSource()) {
/* create a node for this unit if necessary */
node = (INode)fCompilationUnits.get(e);
if (node == null && create) {
node = new JCUNode(e);
fCompilationUnits.put(e, node);
}
} else {
node = (INode)fTypes.get(e);
if (node == null && create) {
node = new TypeNode(e);
fTypes.put(e, node);
}
}
return node;
}
/**
* Returns the node for a builder package. The node is
* created if necessary.
*/
public INode getNodeFor(IPackage pkg) {
return getNodeFor(pkg, true);
}
/**
* Returns the node for a builder package. If create is true, the node is
* created if necessary.
*/
public INode getNodeFor(IPackage pkg, boolean create) {
/* create the namespace if necessary */
INode pkgNode = (INode) fNamespaces.get(pkg);
if (pkgNode == null && create) {
pkgNode = new NamespaceNode(pkg);
fNamespaces.put(pkg, pkgNode);
}
return pkgNode;
}
/**
* Returns an enumeration over all nodes in the graph. Uses a composite
* enumerator to avoid copying all the elements.
*/
public Enumeration getNodes() {
Vector v = new Vector(fNamespaces.size() + fTypes.size() + fCompilationUnits.size() + fZips.size());
for (Enumeration e = fNamespaces.elements(); e.hasMoreElements();) {
v.addElement(e.nextElement());
}
for (Enumeration e = fTypes.elements(); e.hasMoreElements();) {
v.addElement(e.nextElement());
}
for (Enumeration e = fCompilationUnits.elements(); e.hasMoreElements();) {
v.addElement(e.nextElement());
}
for (Enumeration e = fZips.elements(); e.hasMoreElements();) {
v.addElement(e.nextElement());
}
return v.elements();
}
/**
* Returns the topological order number of the given element.
*/
public int getOrder(Object element) {
AbstractNode node = (AbstractNode) getNodeFor(element, true);
return node.getOrder();
}
/**
* Returns the types this source element is directly dependent on.
*/
public IType[] getTypeDependencies(Object element) {
INode node = getNodeFor(element);
Vector vTypes = new Vector();
INode[] dependencies = node.getDependencies();
for (int i = 0; i < dependencies.length; i++) {
switch (dependencies[i].getKind()) {
case INode.JCU_NODE:
case INode.TYPE_NODE:
IType[] types = dependencies[i].getTypes();
for (int j = 0; j < types.length; ++j) {
vTypes.addElement(types[j]);
}
break;
}
}
IType[] results = new IType[vTypes.size()];
vTypes.copyInto(results);
return results;
}
/**
* Returns the types that belong to the given source element, or null
* if the element is not present.
*/
public IType[] getTypes(Object element) {
// Need to create node if not present to properly handle
// binary types.
INode node = getNodeFor(element, true);
return node == null ? null : node.getTypes();
}
/**
* Returns a Vector of unused namespace nodes.
* That is, namespace nodes which have no dependents.
*/
public Vector getUnusedNamespaceNodes() {
Vector v = new Vector();
for (Enumeration e = fNamespaces.elements(); e.hasMoreElements();) {
INode node = (INode)e.nextElement();
if (node.getDependents().length == 0) {
v.addElement(node);
}
}
return v;
}
/**
* For debugging only -- asserts graph integrity
*/
public void integrityCheck() {
integrityCheck(fCompilationUnits);
integrityCheck(fTypes);
integrityCheck(fNamespaces);
}
/**
* For debugging only -- asserts graph integrity
*/
public void integrityCheck(Dictionary table) {
String msg = "Internal Error: the dependency graph is corrupt, do a full build to workaround error"; //$NON-NLS-1$
for (Enumeration e = table.elements(); e.hasMoreElements();) {
AbstractNode node = (AbstractNode) e.nextElement();
/* do for each dependent of node */
INode[] nodesThatDependOnMe = node.getDependents();
for (int i = 0; i < nodesThatDependOnMe.length; i++) {
/* make sure current node is a dependency of the dependent node */
INode[] depDeps = nodesThatDependOnMe[i].getDependencies();
boolean found = false;
for (int j = depDeps.length; --j >= 0;) {
if (depDeps[j] == node) {
found = true;
}
}
Assert.isTrue(found, msg);
}
/* do for each dependency of node */
INode[] nodesThatIDependOn = node.getDependencies();
for (int i = 0; i < nodesThatIDependOn.length; i++) {
/* make sure current node is a dependent of the dependency node */
INode[] depDeps = nodesThatIDependOn[i].getDependents();
boolean found = false;
for (int j = depDeps.length; --j >= 0;) {
if (depDeps[j] == node) {
found = true;
}
}
Assert.isTrue(found, msg);
}
}
}
/**
* Remove the node for the given element, and mark all dependents
* as having to be recompiled, if they haven't been already.
* Returns true if the node was successfully removed, and false
* if the node could not be found.
*/
public boolean remove(Object element) {
INode node = getNodeFor(element, false);
if (node == null) {
return false;
}
/* remove dependencies -- this removes backwards links as well */
node.clearDependencies();
/* do for each dependent (nodes that depend on me)
note: these weren't cleared above */
INode[] dependents = node.getDependents();
for (int i = 0; i < dependents.length; i++) {
INode dep = dependents[i];
/* remove the dependency */
dep.removeDependency(node); // this must not cause the dependents local to be modified
}
return deleteNode(node) != null;
}
/**
* A package is being removed from the state. Delete the corresponding namespace nodes,
* but only if they have no dependents.
*/
public void removePackage(IPackage pkg) {
INode node = (INode)fNamespaces.get(pkg);
if (node != null && node.getDependents().length == 0) {
fNamespaces.remove(pkg);
}
}
/**
* Replaces the dependencies and dependents of nodes in the given
* table with the corresponding nodes in the new graph.
*/
private static void replaceDeps(Dictionary table, DependencyGraph newGraph) {
for (Enumeration e = table.elements(); e.hasMoreElements();) {
AbstractNode node = (AbstractNode) e.nextElement();
node.replaceDeps(newGraph);
}
}
/**
* Returns the number of nodes in the graph.
*/
public int size() {
return fNamespaces.size() + fTypes.size() + fCompilationUnits.size();
}
public String toString() {
return "a DependencyGraph"; //$NON-NLS-1$
}
}