All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.osgi.internal.container.ComputeNodeOrder Maven / Gradle / Ivy

There is a newer version: 1.9.3.RC1
Show newest version
/*******************************************************************************
 * Copyright (c) 2004, 2016 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.osgi.internal.container;

import java.util.*;

/**
 * Borrowed from org.eclipse.core.internal.resources.ComputeProjectOrder to be
 * used when computing the stop order. Implementation of a sort algorithm for
 * computing the node order. This algorithm handles cycles in the node reference
 * graph in a reasonable way.
 *
 * @since 3.0
 */
public class ComputeNodeOrder {

	/*
	 * Prevent class from being instantiated.
	 */
	private ComputeNodeOrder() {
		// 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.
	 * 

* Ref: Cormen, Leiserson, and Rivest Introduction to Algorithms, * McGraw-Hill, 1990. The depth-first search algorithm is in section 23.3. *

*/ 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 WHITE (unvisited), GREY * (visit in progress), or BLACK (visit finished). * WHITE initially. */ public String color = WHITE; /** * The DFS predecessor vertex, or null if there is no predecessor. * null 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: Vertex */ 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: Vertex */ private List vertexList = new ArrayList<>(100); /** * Map from id to vertex. * * Key type: Object; value type: Vertex */ private Map vertexMap = new HashMap<>(100); /** * DFS visit time. Non-negative. */ private int time; /** * Indicates whether the graph has been initialized. Initially * false. */ private boolean initialized = false; /** * Indicates whether the graph contains cycles. Initially false. */ private boolean cycles = false; /** * Creates a new empty directed graph object. *

* After this graph's vertexes and edges are defined with addVertex * and addEdge, call freeze to indicate that the graph * is all there, and then call idsByDFSFinishTime to read off the * vertexes ordered by DFS finish time. *

*/ 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 addVertex. 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 = vertexMap.get(fromId); Vertex toVertex = vertexMap.get(toId); // ignore edges when one of the vertices is unknown if (fromVertex == null || toVertex == null) return; 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 true if objects are to be arranged into * increasing order of depth-first search finish time, and * false 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: Object) * @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 (Vertex vertex : vertexList) { 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 true if this graph contains at least one cycle, and * false 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: Object[]) * @exception IllegalArgumentException if the graph is not frozen */ public List nonTrivialComponents() { if (!initialized) { throw new IllegalArgumentException(); } // find the roots of each component // Map> components Map> components = new HashMap<>(); for (Vertex vertex : vertexList) { 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 = 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 (List component : components.values()) { 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 DFSVisit. // *

// * Although this method is not used, it is the basis of the // * non-recursive DFS method. // *

// */ // 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 = Integer.valueOf(NEXT_VERTEX); final Integer AFTER_NEXTED_DFS_VISIT_OBJECT = Integer.valueOf(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 = allV.next(); if (nextVertex.color == Vertex.WHITE) { stack.add(NEXT_VERTEX_OBJECT); vertex = nextVertex; state = START_DFS_VISIT; continue nextStateLoop; } 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 = 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; } // 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); @SuppressWarnings("unchecked") Iterator unchecked = (Iterator) stack.remove(stack.size() - 1); allAdjacent = unchecked; state = NEXT_ADJACENT; continue nextStateLoop; } } } } /** * Sorts the given list of projects 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. *

* 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. *

*

* Ref: Cormen, Leiserson, and Rivest Introduction to Algorithms, * McGraw-Hill, 1990. The strongly-connected-components algorithm is in section * 23.5. *

* * @param objects a list of projects (element type: IProject) * @param references a list of project references [A,B] meaning that A * references B (element type: IProject[]) * @return an object describing the resulting project order */ public static Object[][] computeNodeOrder(Object[] objects, Object[][] references) { // Step 1: Create the graph object. final Digraph g1 = new Digraph(); // add vertexes for (Object object : objects) { g1.addVertex(object); } // add edges for (Object[] reference : references) { // create an edge from q to p // to cause q to come before p in eventual result g1.addEdge(reference[1], reference[0]); } 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 (Object object : resortedVertexes) g2.addVertex(object); // add edges for (Object[] reference : references) { g2.addEdge(reference[0], reference[1]); } g2.freeze(); // Step 3: Return the vertexes in increasing order of depth-first finish // time in g2 List sortedProjectList = g2.idsByDFSFinishTime(true); Object[] orderedNodes = new Object[sortedProjectList.size()]; sortedProjectList.toArray(orderedNodes); Object[][] knots; boolean hasCycles = g2.containsCycles(); if (hasCycles) { List knotList = g2.nonTrivialComponents(); knots = knotList.toArray(new Object[knotList.size()][]); } else { knots = new Object[0][]; } for (int i = 0; i < orderedNodes.length; i++) objects[i] = orderedNodes[i]; return knots; } }