/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * Graph.java * Creation date: (August 24 2000 12:12:22 PM) * By: Bo Ilic */ package org.openquark.cal.util; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * A class used to represent a directed Graph. The class has several methods that implement useful * graph algorithms such as calculateStronglyConnectedComponents. * <p> * The type parameter <code>T</code> this is the type of the data (usually a name like a function name or * a module name) encapsulated by the graph vertices. * * Creation date: (8/24/00 12:12:22 PM) * @author Bo Ilic */ public final class Graph<T> implements Cloneable { /** list of all the vertices in the graph */ private final VertexList<T> vertexList; /** a helper member used during depth first traversal vertex numbering */ private int depthFirstNumber; /** list of all the strongly connected components in the graph */ private List<Component> componentList; /** * Helper class to efficiently access the strongly connected components of the Graph */ public final class Component { //the component refers to indices [startIndex, endIndex) within a VertexList private final int startIndex; private final int endIndex; private final boolean cyclic; /** * Construct a Component */ public Component(int startIndex, int endIndex, boolean cyclic) { this.startIndex = startIndex; this.endIndex = endIndex; this.cyclic = cyclic; } /**. * Returns the size of this Component */ public final int size() { return endIndex - startIndex; } /**. * Returns true if this Component has more than one vertex in it, or if the single * vertex has an arc to itself. */ public final boolean isCyclic() { return cyclic; } /**. * Returns the ith vertex in this Component */ public final Vertex<T> getVertex(int i) { if (i < 0 || i >= size()) { throw new IndexOutOfBoundsException(); } return vertexList.get(startIndex + i); } @Override public String toString() { StringBuilder result = new StringBuilder("["); for (int i = 0, size = size(); i < size; ++ i) { if (i > 0) { result.append(", "); } result.append(getVertex(i).getName()); } result.append("]"); return result.toString(); } } /** * Constructs a graph from the vertexBuilderList initialization information. Note that * vertexBuilderList is assumed to determine a well formed graph. * What this means is: * a. scName is different for each VertexBuilder object in the list. * b. if a name occurs in the dependeeNames list of a VertexBuilder object, then it also occurs as * a scName for some VertexBuilder object in vertexBuilderList. * c. the dependeeNames set of a VertexBuilder object does not have duplicated names. * * Creation date: (8/28/00 4:56:01 PM) * @param vertexBuilderList VertexBuilderList */ public Graph(VertexBuilderList<T> vertexBuilderList) { vertexList = new VertexList<T>(); Map<T, Vertex<T>> nameToVertexMap = new HashMap<T, Vertex<T>>(); int nFunctions = vertexBuilderList.size(); for (int i = 0; i < nFunctions; ++i) { VertexBuilder<T> vb = vertexBuilderList.get(i); T vertexName = vb.getName(); Vertex<T> v = new Vertex<T>(vertexName); vertexList.add(v); nameToVertexMap.put(vertexName, v); } for (int i = 0; i < nFunctions; ++i) { VertexBuilder<T> vb = vertexBuilderList.get(i); Vertex<T> sourceVertex = nameToVertexMap.get(vb.getName()); VertexList<T> adjacentVertices = new VertexList<T>(); for (final T dependeeName : vb.getDependeeNames()) { Vertex<T> o = nameToVertexMap.get(dependeeName); if (o != null) { adjacentVertices.add(o); } } sourceVertex.setAdjacentVertices(adjacentVertices); } } /** * Creates a new graph, making a copy of vertexList. * * @param vertexListToCopy */ private Graph(VertexList<T> vertexListToCopy) { vertexList = new VertexList<T>(); Map<Vertex<T>, Vertex<T>> map = new HashMap<Vertex<T>, Vertex<T>>(); int nVertices = vertexListToCopy.size(); for (int i = 0; i < nVertices; ++i) { Vertex<T> v1 = vertexListToCopy.get(i); Vertex<T> v2 = new Vertex<T>(v1); v2.setAdjacentVertices(null); vertexList.add(v2); map.put(v1, v2); } // Fix up the adjacent vertices in the copy. for (int i = 0; i < nVertices; ++i) { VertexList<T> arcs1 = vertexListToCopy.get(i).getAdjacentVertices(); VertexList<T> arcs2 = new VertexList<T>(); for (int j = 0, nArcs = arcs1.size(); j < nArcs; ++j) { Vertex<T> arc = map.get(arcs1.get(j)); if (arc != null) { arcs2.add(arc); } } vertexList.get(i).setAdjacentVertices(arcs2); } } /** * Returns a copy of the graph with vertices in the same strongly connected component listed * sequentially, and the strongly connected components ordered topologically. * * Two vertices v1 and v2 are in the same strongly connected component if there is a path from * v1 to v2 and from v2 to v1. Topological ordering means that if v1 and v2 are not in the * same strongly connected component, and there is an arc from v1 to v2, then v1 is to the right * of v2 in the list of vertices. * * The algorithm used is from * Data Structures and Algorithms, Alfred Aho, John Hopcroft, Jeffrey Ullman, 1983 pg 222-226. * * Creation date: (8/24/00 6:14:26 PM) * @return stronglyconnectedcomponents.Graph */ public final Graph<T> calculateStronglyConnectedComponents() { depthFirstSearchNumbering(); Graph<T> g = reverse(); { final Comparator<Vertex<T>> c = new Comparator<Vertex<T>>() { public int compare(Vertex<T> vertex1, Vertex<T> vertex2) { final int n1 = vertex1.getDepthFirstNumber(); final int n2 = vertex2.getDepthFirstNumber(); if (n1 > n2) { return -1; } else if (n1 < n2) { return 1; } else { return 0; } } }; // Sort the vertex list in descending order of depth first number java.util.Collections.sort(g.vertexList, c); } int nComponents = g.markComponents(); // reflect the component numbers so they correspond to the order of declaration for // functions. For example, I x = x; main = I 2. Then I should have component #1 // and main have component #2. for (int i = 0, nVertices = g.vertexList.size(); i < nVertices; ++i) { Vertex<T> v = g.vertexList.get(i); v.setComponentNumber(nComponents - v.getComponentNumber() + 1); } // For simple examples, the vertices appear to be ordered sequentially by component // number, but this is not true in general. Thus we need to sort them again. { final Comparator<Vertex<T>> c = new Comparator<Vertex<T>>() { public int compare(Vertex<T> vertex1, Vertex<T> vertex2) { final int n1 = vertex1.getComponentNumber(); final int n2 = vertex2.getComponentNumber(); if (n1 < n2) { return -1; } else if (n1 > n2) { return 1; } else { return 0; } } }; java.util.Collections.sort(g.vertexList, c); } g.makeComponentList(); return g; } /** * Clones the graph. * * Creation date: (8/24/00 5:11:17 PM) * @return Object */ @Override public Graph<T> clone() { Graph<T> g = new Graph<T>(vertexList); g.depthFirstNumber = depthFirstNumber; return g; } /** * Number the vertices of the graph by their order of visitation in a depth * first search. * * Creation date: (8/24/00 12:19:02 PM) */ public final void depthFirstSearchNumbering() { int nVertices = vertexList.size(); for (int i = 0; i < nVertices; ++i) { vertexList.get(i).setIsVisited(false); } depthFirstNumber = 0; for (int i = 0; i < nVertices; ++i) { Vertex<T> v = vertexList.get(i); if (!v.getIsVisited()) { depthFirstSearchNumbering(v); } } } /** * A helper method for doing the depth first search numbering of the graph. * * Creation date: (8/24/00 12:31:02 PM) * @param v stronglyconnectedcomponents.Vertex */ private final void depthFirstSearchNumbering(Vertex<T> v) { v.setIsVisited(true); VertexList<T> adjacentVertices = v.getAdjacentVertices(); for (int i = 0, nAdjacent = adjacentVertices.size(); i < nAdjacent; ++i) { Vertex<T> adjacent = adjacentVertices.get(i); if (!adjacent.getIsVisited()) { depthFirstSearchNumbering(adjacent); } } ++depthFirstNumber; v.setDepthFirstNumber(depthFirstNumber); } /** * Get the names of vertices dependent (directly or indirectly) on a given vertex. * @param vertexName the name of the vertex for which to find dependents. * @return (Set of String) the set of names of vertices dependent on the given vertex. This set includes vertexName itself. */ public final Set<T> getDependentVertexNames(T vertexName) { // Get the vertex corresponding to vertexName. Vertex<T> dependeeVertex = null; for (int i = 0, nVertices = vertexList.size(); i < nVertices; ++i) { Vertex<T> vertex = vertexList.get(i); if (vertex.getName().equals(vertexName)) { dependeeVertex = vertex; break; } } if (dependeeVertex == null) { throw new IllegalArgumentException("Could not find vertex named " + vertexName); } return getDependentNamesHelper(dependeeVertex, new HashSet<T>()); } /** * Helper method for getDependentVertexNames * @param dependeeVertex * @param dependentNamesSet (Set of String) the set of names of dependents collected so far * @return the dependentNamesSet after the method is done with it */ private final Set<T> getDependentNamesHelper(Vertex<T> dependeeVertex, Set<T> dependentNamesSet) { T vertexName = dependeeVertex.getName(); if (!dependentNamesSet.add(vertexName)) { return dependentNamesSet; } VertexList<T> adjacentVertices = dependeeVertex.getAdjacentVertices(); for (int i = 0, nAdjacent = adjacentVertices.size(); i < nAdjacent; ++i) { Vertex<T> adjacentVertex = adjacentVertices.get(i); getDependentNamesHelper(adjacentVertex, dependentNamesSet); } return dependentNamesSet; } /** * Returns the number of strongly connected components in this graph. Assumes that * calculateStronglyConnectedComponents has been called since the last graph update. * Creation date: (1/18/01 4:15:45 PM) * @return int number of strongly connected components in this graph */ public final int getNStronglyConnectedComponents() { int nVertices = vertexList.size(); if (nVertices == 0) { return 0; } return vertexList.get(nVertices - 1).getComponentNumber(); } /** * Zero indexed accessor to a component. * Creation date: (1/18/01 5:22:49 PM) * @return Component * @param componentN int */ public final Component getStronglyConnectedComponent(int componentN) { return componentList.get(componentN); } /** * Returns a list of the vertices in this graph. * * Creation date: (9/1/00 9:45:57 AM) * @return VertexList */ public final VertexList<T> getVertexList() { return vertexList; } /** * Some test cases for debugging Graph and its supporting classes. * Creation date: (8/28/00 4:26:18 PM) * @param args */ public static void main(String[] args) { VertexBuilderList<String> v1 = new VertexBuilderList<String>(); v1.add(new VertexBuilder<String>("a", new String[] { "b" })); v1.add(new VertexBuilder<String>("b", new String[] { "c" })); v1.add(new VertexBuilder<String>("c", new String[] { "a" })); v1.add(new VertexBuilder<String>("d", new String[] { "a" })); v1.add(new VertexBuilder<String>("e", new String[] { "c" })); // a is a duplicate. It is removed when the VertexBuilder object is constructed. v1.add(new VertexBuilder<String>("f", new String[] { "a", "g", "h", "a" })); v1.add(new VertexBuilder<String>("g", new String[] { "b", "f" })); // h has an arc to itself v1.add(new VertexBuilder<String>("h", new String[] { "d", "h" })); System.out.println(v1.makesValidGraph() ? "Input forms a valid graph\n" : "Input does not form a valid graph\n"); Graph<String> g1 = new Graph<String>(v1); g1.depthFirstSearchNumbering(); System.out.println(g1.toString()); System.out.println(g1.reverse().toString()); System.out.println(g1.calculateStronglyConnectedComponents().toString()); } /** * Initializes the componentList member variable to allow easy and efficient access to the strongly connected * connected components of this Graph. * Creation date: (1/18/01 5:00:22 PM) */ private final void makeComponentList() { int nVertices = vertexList.size(); int nComponents = getNStronglyConnectedComponents(); componentList = new ArrayList<Component>(nComponents); int startComponentIndex = 0; int nextStartComponentIndex = 0; for (int i = 0; i < nComponents; ++i) { // component number i + 1 has indices [startComponentIndex, nextStartComponentIndex) startComponentIndex = nextStartComponentIndex; nextStartComponentIndex = startComponentIndex; while (nextStartComponentIndex < nVertices && i + 1 == vertexList.get(nextStartComponentIndex).getComponentNumber()) { ++nextStartComponentIndex; } // components of size 1 that do not depend upon themselves are not cyclic Vertex<T> v = vertexList.get(startComponentIndex); boolean isCyclic = nextStartComponentIndex - startComponentIndex > 1 || v.getAdjacentVertices().contains(v); componentList.add(new Component(startComponentIndex, nextStartComponentIndex, isCyclic)); } } /** * Marks the components of the reversed graph with their strongly connected component number. * @return the number of components. */ private final int markComponents() { int nVertices = vertexList.size(); for (int i = 0; i < nVertices; ++i) { vertexList.get(i).setIsVisited(false); } int componentNumber = 0; for (int i = 0; i < nVertices; ++i) { Vertex<T> v = vertexList.get(i); if (!v.getIsVisited()) { ++componentNumber; markComponents(v, componentNumber); } } return componentNumber; } /** * A helper function for markComponents (). * * @param v a vertex in the component. * @param componentNumber the component number with which to mark this component. */ private final void markComponents(Vertex<T> v, int componentNumber) { v.setIsVisited(true); VertexList<T> adjacentVertices = v.getAdjacentVertices(); for (int i = 0, nAdjacent = adjacentVertices.size(); i < nAdjacent; ++i) { Vertex<T> adjacent = adjacentVertices.get(i); if (!adjacent.getIsVisited()) { markComponents(adjacent, componentNumber); } } v.setComponentNumber(componentNumber); } /** * Returns the graph in which all arcs in the original graph have been reversed. * * Creation date: (8/24/00 12:53:22 PM) * @return Graph */ public final Graph<T> reverse() { Graph<T> g = this.clone(); int nVertices = vertexList.size(); Map<Vertex<T>, Vertex<T>> map = new HashMap<Vertex<T>, Vertex<T>>(); for (int i = 0; i < nVertices; ++i) { Vertex<T> v1 = vertexList.get(i); Vertex<T> v2 = g.vertexList.get(i); v2.setAdjacentVertices(new VertexList<T>()); map.put(v1, v2); } for (int i = 0; i < nVertices; ++i) { Vertex<T> v1 = vertexList.get(i); VertexList<T> arcs = v1.getAdjacentVertices(); for (int j = 0, nArcs = arcs.size(); j < nArcs; ++j) { Vertex<T> v2 = map.get(arcs.get(j)); v2.getAdjacentVertices().add(map.get(v1)); } } return g; } /** * Provides a string representation of the graph. * * Creation date: (8/28/00 5:18:27 PM) * @return String */ @Override public String toString() { StringBuilder sb = new StringBuilder(); for (int i = 0, nVertices = vertexList.size(); i < nVertices; ++i) { sb.append(vertexList.get(i).toString()); sb.append('\n'); } return sb.toString(); } }