/*******************************************************************************
* Copyright (c) 2000, 2010 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.core.internal.resources;
import java.util.*;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspace;
/**
* Implementation of a sort algorithm for computing the project order. This algorithm handles cycles
* in the project reference graph in a reasonable way.
*
* @since 2.1
*/
class ComputeProjectOrder {
/*
* Prevent class from being instantiated.
*/
private ComputeProjectOrder() {
// not allowed
}
/**
* A directed graph. Once the vertexes and edges of the graph have been defined, the graph can
* be queried for the depth-first finish time of each vertex.
* <p>
* Ref: Cormen, Leiserson, and Rivest <it>Introduction to Algorithms</it>, McGraw-Hill, 1990.
* The depth-first search algorithm is in section 23.3.
* </p>
*/
private static class Digraph {
/**
* struct-like object for representing a vertex along with various values computed during
* depth-first search (DFS).
*/
public static class Vertex {
/**
* White is for marking vertexes as unvisited.
*/
public static final String WHITE= "white"; //$NON-NLS-1$
/**
* Grey is for marking vertexes as discovered but visit not yet finished.
*/
public static final String GREY= "grey"; //$NON-NLS-1$
/**
* Black is for marking vertexes as visited.
*/
public static final String BLACK= "black"; //$NON-NLS-1$
/**
* Color of the vertex. One of <code>WHITE</code> (unvisited), <code>GREY</code> (visit
* in progress), or <code>BLACK</code> (visit finished). <code>WHITE</code> initially.
*/
public String color= WHITE;
/**
* The DFS predecessor vertex, or <code>null</code> if there is no predecessor.
* <code>null</code> initially.
*/
public Vertex predecessor= null;
/**
* Timestamp indicating when the vertex was finished (became BLACK) in the DFS. Finish
* times are between 1 and the number of vertexes.
*/
public int finishTime;
/**
* The id of this vertex.
*/
public Object id;
/**
* Ordered list of adjacent vertexes. In other words, "this" is the "from" vertex and
* the elements of this list are all "to" vertexes.
*
* Element type: <code>Vertex</code>
*/
public List adjacent= new ArrayList(3);
/**
* Creates a new vertex with the given id.
*
* @param id the vertex id
*/
public Vertex(Object id) {
this.id= id;
}
}
/**
* Ordered list of all vertexes in this graph.
*
* Element type: <code>Vertex</code>
*/
private List vertexList= new ArrayList(100);
/**
* Map from id to vertex.
*
* Key type: <code>Object</code>; value type: <code>Vertex</code>
*/
private Map vertexMap= new HashMap(100);
/**
* DFS visit time. Non-negative.
*/
private int time;
/**
* Indicates whether the graph has been initialized. Initially <code>false</code>.
*/
private boolean initialized= false;
/**
* Indicates whether the graph contains cycles. Initially <code>false</code>.
*/
private boolean cycles= false;
/**
* Creates a new empty directed graph object.
* <p>
* After this graph's vertexes and edges are defined with <code>addVertex</code> and
* <code>addEdge</code>, call <code>freeze</code> to indicate that the graph is all there,
* and then call <code>idsByDFSFinishTime</code> to read off the vertexes ordered by DFS
* finish time.
* </p>
*/
public Digraph() {
super();
}
/**
* Freezes this graph. No more vertexes or edges can be added to this graph after this
* method is called. Has no effect if the graph is already frozen.
*/
public void freeze() {
if (!initialized) {
initialized= true;
// only perform depth-first-search once
DFS();
}
}
/**
* Defines a new vertex with the given id. The depth-first search is performed in the
* relative order in which vertexes were added to the graph.
*
* @param id the id of the vertex
* @exception IllegalArgumentException if the vertex id is already defined or if the graph
* is frozen
*/
public void addVertex(Object id) throws IllegalArgumentException {
if (initialized) {
throw new IllegalArgumentException();
}
Vertex vertex= new Vertex(id);
Object existing= vertexMap.put(id, vertex);
// nip problems with duplicate vertexes in the bud
if (existing != null) {
throw new IllegalArgumentException();
}
vertexList.add(vertex);
}
/**
* Adds a new directed edge between the vertexes with the given ids. Vertexes for the given
* ids must be defined beforehand with <code>addVertex</code>. The depth-first search is
* performed in the relative order in which adjacent "to" vertexes were added to a given
* "from" index.
*
* @param fromId the id of the "from" vertex
* @param toId the id of the "to" vertex
* @exception IllegalArgumentException if either vertex is undefined or if the graph is
* frozen
*/
public void addEdge(Object fromId, Object toId) throws IllegalArgumentException {
if (initialized) {
throw new IllegalArgumentException();
}
Vertex fromVertex= (Vertex)vertexMap.get(fromId);
Vertex toVertex= (Vertex)vertexMap.get(toId);
// nip problems with bogus vertexes in the bud
if (fromVertex == null) {
throw new IllegalArgumentException();
}
if (toVertex == null) {
throw new IllegalArgumentException();
}
fromVertex.adjacent.add(toVertex);
}
/**
* Returns the ids of the vertexes in this graph ordered by depth-first search finish time.
* The graph must be frozen.
*
* @param increasing <code>true</code> if objects are to be arranged into increasing order
* of depth-first search finish time, and <code>false</code> if objects are to be
* arranged into decreasing order of depth-first search finish time
* @return the list of ids ordered by depth-first search finish time (element type:
* <code>Object</code>)
* @exception IllegalArgumentException if the graph is not frozen
*/
public List idsByDFSFinishTime(boolean increasing) {
if (!initialized) {
throw new IllegalArgumentException();
}
int len= vertexList.size();
Object[] r= new Object[len];
for (Iterator allV= vertexList.iterator(); allV.hasNext();) {
Vertex vertex= (Vertex)allV.next();
int f= vertex.finishTime;
// note that finish times start at 1, not 0
if (increasing) {
r[f - 1]= vertex.id;
} else {
r[len - f]= vertex.id;
}
}
return Arrays.asList(r);
}
/**
* Returns whether the graph contains cycles. The graph must be frozen.
*
* @return <code>true</code> if this graph contains at least one cycle, and
* <code>false</code> if this graph is cycle free
* @exception IllegalArgumentException if the graph is not frozen
*/
public boolean containsCycles() {
if (!initialized) {
throw new IllegalArgumentException();
}
return cycles;
}
/**
* Returns the non-trivial components of this graph. A non-trivial component is a set of 2
* or more vertexes that were traversed together. The graph must be frozen.
*
* @return the possibly empty list of non-trivial components, where each component is an
* array of ids (element type: <code>Object[]</code>)
* @exception IllegalArgumentException if the graph is not frozen
*/
public List nonTrivialComponents() {
if (!initialized) {
throw new IllegalArgumentException();
}
// find the roots of each component
// Map<Vertex,List<Object>> components
Map components= new HashMap();
for (Iterator it= vertexList.iterator(); it.hasNext();) {
Vertex vertex= (Vertex)it.next();
if (vertex.predecessor == null) {
// this vertex is the root of a component
// if component is non-trivial we will hit a child
} else {
// find the root ancestor of this vertex
Vertex root= vertex;
while (root.predecessor != null) {
root= root.predecessor;
}
List component= (List)components.get(root);
if (component == null) {
component= new ArrayList(2);
component.add(root.id);
components.put(root, component);
}
component.add(vertex.id);
}
}
List result= new ArrayList(components.size());
for (Iterator it= components.values().iterator(); it.hasNext();) {
List component= (List)it.next();
if (component.size() > 1) {
result.add(component.toArray());
}
}
return result;
}
// /**
// * Performs a depth-first search of this graph and records interesting
// * info with each vertex, including DFS finish time. Employs a recursive
// * helper method <code>DFSVisit</code>.
// * <p>
// * Although this method is not used, it is the basis of the
// * non-recursive <code>DFS</code> method.
// * </p>
// */
// private void recursiveDFS() {
// // initialize
// // all vertex.color initially Vertex.WHITE;
// // all vertex.predecessor initially null;
// time = 0;
// for (Iterator allV = vertexList.iterator(); allV.hasNext();) {
// Vertex nextVertex = (Vertex) allV.next();
// if (nextVertex.color == Vertex.WHITE) {
// DFSVisit(nextVertex);
// }
// }
// }
//
// /**
// * Helper method. Performs a depth first search of this graph.
// *
// * @param vertex the vertex to visit
// */
// private void DFSVisit(Vertex vertex) {
// // mark vertex as discovered
// vertex.color = Vertex.GREY;
// List adj = vertex.adjacent;
// for (Iterator allAdjacent=adj.iterator(); allAdjacent.hasNext();) {
// Vertex adjVertex = (Vertex) allAdjacent.next();
// if (adjVertex.color == Vertex.WHITE) {
// // explore edge from vertex to adjVertex
// adjVertex.predecessor = vertex;
// DFSVisit(adjVertex);
// } else if (adjVertex.color == Vertex.GREY) {
// // back edge (grey vertex means visit in progress)
// cycles = true;
// }
// }
// // done exploring vertex
// vertex.color = Vertex.BLACK;
// time++;
// vertex.finishTime = time;
// }
/**
* Performs a depth-first search of this graph and records interesting info with each
* vertex, including DFS finish time. Does not employ recursion.
*/
private void DFS() {
// state machine rendition of the standard recursive DFS algorithm
int state;
final int NEXT_VERTEX= 1;
final int START_DFS_VISIT= 2;
final int NEXT_ADJACENT= 3;
final int AFTER_NEXTED_DFS_VISIT= 4;
// use precomputed objects to avoid garbage
final Integer NEXT_VERTEX_OBJECT= new Integer(NEXT_VERTEX);
final Integer AFTER_NEXTED_DFS_VISIT_OBJECT= new Integer(AFTER_NEXTED_DFS_VISIT);
// initialize
// all vertex.color initially Vertex.WHITE;
// all vertex.predecessor initially null;
time= 0;
// for a stack, append to the end of an array-based list
List stack= new ArrayList(Math.max(1, vertexList.size()));
Iterator allAdjacent= null;
Vertex vertex= null;
Iterator allV= vertexList.iterator();
state= NEXT_VERTEX;
nextStateLoop: while (true) {
switch (state) {
case NEXT_VERTEX:
// on entry, "allV" contains vertexes yet to be visited
if (!allV.hasNext()) {
// all done
break nextStateLoop;
}
Vertex nextVertex= (Vertex)allV.next();
if (nextVertex.color == Vertex.WHITE) {
stack.add(NEXT_VERTEX_OBJECT);
vertex= nextVertex;
state= START_DFS_VISIT;
continue nextStateLoop;
}
//else
state= NEXT_VERTEX;
continue nextStateLoop;
case START_DFS_VISIT:
// on entry, "vertex" contains the vertex to be visited
// top of stack is return code
// mark the vertex as discovered
vertex.color= Vertex.GREY;
allAdjacent= vertex.adjacent.iterator();
state= NEXT_ADJACENT;
continue nextStateLoop;
case NEXT_ADJACENT:
// on entry, "allAdjacent" contains adjacent vertexes to
// be visited; "vertex" contains vertex being visited
if (allAdjacent.hasNext()) {
Vertex adjVertex= (Vertex)allAdjacent.next();
if (adjVertex.color == Vertex.WHITE) {
// explore edge from vertex to adjVertex
adjVertex.predecessor= vertex;
stack.add(allAdjacent);
stack.add(vertex);
stack.add(AFTER_NEXTED_DFS_VISIT_OBJECT);
vertex= adjVertex;
state= START_DFS_VISIT;
continue nextStateLoop;
}
if (adjVertex.color == Vertex.GREY) {
// back edge (grey means visit in progress)
cycles= true;
}
state= NEXT_ADJACENT;
continue nextStateLoop;
}
//else done exploring vertex
vertex.color= Vertex.BLACK;
time++;
vertex.finishTime= time;
state= ((Integer)stack.remove(stack.size() - 1)).intValue();
continue nextStateLoop;
case AFTER_NEXTED_DFS_VISIT:
// on entry, stack contains "vertex" and "allAjacent"
vertex= (Vertex)stack.remove(stack.size() - 1);
allAdjacent= (Iterator)stack.remove(stack.size() - 1);
state= NEXT_ADJACENT;
continue nextStateLoop;
}
}
}
}
/**
* Sorts the given list of project in a manner that honors the given project reference
* relationships. That is, if project A references project B, then the resulting order will list
* B before A if possible. For graphs that do not contain cycles, the result is the same as a
* conventional topological sort. For graphs containing cycles, the order is based on ordering
* the strongly connected components of the graph. This has the effect of keeping each knot of
* projects together without otherwise affecting the order of projects not involved in a cycle.
* For a graph G, the algorithm performs in O(|G|) space and time.
* <p>
* When there is an arbitrary choice, vertexes are ordered as supplied. Arranged projects in
* descending alphabetical order generally results in an order that builds "A" before "Z" when
* there are no other constraints.
* </p>
* <p>
* Ref: Cormen, Leiserson, and Rivest <it>Introduction to Algorithms</it>, McGraw-Hill, 1990.
* The strongly-connected-components algorithm is in section 23.5.
* </p>
*
* @param projects a list of projects (element type: <code>IProject</code>)
* @param references a list of project references [A,B] meaning that A references B (element
* type: <code>IProject[]</code>)
* @return an object describing the resulting project order
*/
static IWorkspace.ProjectOrder computeProjectOrder(SortedSet projects, List references) {
// Step 1: Create the graph object.
final Digraph g1= new Digraph();
// add vertexes
for (Iterator it= projects.iterator(); it.hasNext();) {
IProject project= (IProject)it.next();
g1.addVertex(project);
}
// add edges
for (Iterator it= references.iterator(); it.hasNext();) {
IProject[] ref= (IProject[])it.next();
IProject p= ref[0];
IProject q= ref[1];
// p has a project reference to q
// therefore create an edge from q to p
// to cause q to come before p in eventual result
g1.addEdge(q, p);
}
g1.freeze();
// Step 2: Create the transposed graph. This time, define the vertexes
// in decreasing order of depth-first finish time in g1
// interchange "to" and "from" to reverse edges from g1
final Digraph g2= new Digraph();
// add vertexes
List resortedVertexes= g1.idsByDFSFinishTime(false);
for (Iterator it= resortedVertexes.iterator(); it.hasNext();) {
final IProject project= (IProject)it.next();
g2.addVertex(project);
}
// add edges
for (Iterator it= references.iterator(); it.hasNext();) {
IProject[] ref= (IProject[])it.next();
IProject p= ref[0];
IProject q= ref[1];
// p has a project reference to q
// therefore create an edge from p to q
// N.B. this is the reverse of step 1
g2.addEdge(p, q);
}
g2.freeze();
// Step 3: Return the vertexes in increasing order of depth-first finish
// time in g2
List sortedProjectList= g2.idsByDFSFinishTime(true);
IProject[] orderedProjects= new IProject[sortedProjectList.size()];
sortedProjectList.toArray(orderedProjects);
IProject[][] knots;
boolean hasCycles= g2.containsCycles();
if (hasCycles) {
List knotList= g2.nonTrivialComponents();
knots= new IProject[knotList.size()][];
// cannot use knotList.toArray(knots) because each knot is Object[]
// and we need each to be an IProject[]
int k= 0;
for (Iterator it= knotList.iterator(); it.hasNext();) {
Object[] knot= (Object[])it.next();
IProject[] knotCopy= new IProject[knot.length];
for (int i= 0; i < knot.length; i++) {
knotCopy[i]= (IProject)knot[i];
}
knots[k]= knotCopy;
k++;
}
} else {
knots= new IProject[][] {};
}
return new IWorkspace.ProjectOrder(orderedProjects, hasCycles, knots);
}
}