/* * Copyright (c) 2013, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.engine.utilities.collection; import com.google.dart.engine.utilities.translation.DartBlockBody; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * Instances of the class {@code DirectedGraph} implement a directed graph in which the nodes are * arbitrary (client provided) objects and edges are represented implicitly. The graph will allow an * edge from any node to any other node, including itself, but will not represent multiple edges * between the same pair of nodes. * * @param N the type of the nodes in the graph */ public class DirectedGraph<N> { /** * Instances of the class {@code NodeInfo} are used by the {@link SccFinder} to maintain * information about the nodes that have been examined. * * @param N the type of the nodes corresponding to the entries */ private static class NodeInfo<N> { /** * The depth of this node. */ public int index; /** * The depth of the first node in a cycle. */ public int lowlink; /** * A flag indicating whether the corresponding node is on the stack. Used to remove the need for * searching a collection for the node each time the question needs to be asked. */ public boolean onStack; /** * The component that contains the corresponding node. */ public ArrayList<N> component; /** * Initialize a newly created information holder to represent a node at the given depth. * * @param depth the depth of the node being represented */ public NodeInfo(int depth) { index = depth; lowlink = depth; onStack = false; } } /** * Instances of the class {@code SccFinder} implement Tarjan's Algorithm for finding the strongly * connected components in a graph. */ private static class SccFinder<N> { /** * The graph to work with. */ private DirectedGraph<N> graph; /** * The index used to uniquely identify the depth of nodes. */ private int index = 0; /** * The stack of nodes that are being visited in order to identify components. */ private ArrayList<N> stack = new ArrayList<N>(); /** * A table mapping nodes to information about the nodes that is used by this algorithm. */ private HashMap<N, NodeInfo<N>> nodeMap = new HashMap<N, NodeInfo<N>>(); /** * A list of all strongly connected components found, in topological sort order (each node in a * strongly connected component only has edges that point to nodes in the same component or * earlier components). */ private ArrayList<ArrayList<N>> allComponents = new ArrayList<ArrayList<N>>(); /** * Initialize a newly created finder. */ public SccFinder(DirectedGraph<N> graph) { super(); this.graph = graph; } /** * Return a list containing the nodes that are part of the strongly connected component that * contains the given node. * * @param node the node used to identify the strongly connected component to be returned * @return the nodes that are part of the strongly connected component that contains the given * node */ public ArrayList<N> componentContaining(N node) { return strongConnect(node).component; } /** * Run Tarjan's algorithm and return the resulting list of strongly connected components. The * list is in topological sort order (each node in a strongly connected component only has edges * that point to nodes in the same component or earlier components). */ public ArrayList<ArrayList<N>> computeTopologicalSort() { for (N node : graph.edges.keySet()) { NodeInfo<N> nodeInfo = nodeMap.get(node); if (nodeInfo == null) { strongConnect(node); } } return allComponents; } /** * Remove and return the top-most element from the stack. * * @return the element that was removed */ private N pop() { N node = stack.remove(stack.size() - 1); nodeMap.get(node).onStack = false; return node; } /** * Add the given node to the stack. * * @param node the node to be added to the stack */ private void push(N node) { nodeMap.get(node).onStack = true; stack.add(node); } /** * Compute the strongly connected component that contains the given node as well as any * components containing nodes that are reachable from the given component. * * @param v the node from which the search will begin * @return the information about the given node */ private NodeInfo<N> strongConnect(N v) { // // Set the depth index for v to the smallest unused index // NodeInfo<N> vInfo = new NodeInfo<N>(index++); nodeMap.put(v, vInfo); push(v); // // Consider successors of v // HashSet<N> tails = graph.edges.get(v); if (tails != null) { for (N w : tails) { NodeInfo<N> wInfo = nodeMap.get(w); if (wInfo == null) { // Successor w has not yet been visited; recurse on it wInfo = strongConnect(w); vInfo.lowlink = Math.min(vInfo.lowlink, wInfo.lowlink); } else if (wInfo.onStack) { // Successor w is in stack S and hence in the current SCC vInfo.lowlink = Math.min(vInfo.lowlink, wInfo.index); } } } // // If v is a root node, pop the stack and generate an SCC // if (vInfo.lowlink == vInfo.index) { ArrayList<N> component = new ArrayList<N>(); N w; do { w = pop(); component.add(w); nodeMap.get(w).component = component; } while (w != v); allComponents.add(component); } return vInfo; } } /** * The table encoding the edges in the graph. An edge is represented by an entry mapping the head * to a set of tails. Nodes that are not the head of any edge are represented by an entry mapping * the node to an empty set of tails. */ private HashMap<N, HashSet<N>> edges = new HashMap<N, HashSet<N>>(); /** * Initialize a newly create directed graph to be empty. */ public DirectedGraph() { super(); } /** * Add an edge from the given head node to the given tail node. Both nodes will be a part of the * graph after this method is invoked, whether or not they were before. * * @param head the node at the head of the edge * @param tail the node at the tail of the edge */ public void addEdge(N head, N tail) { // // First, ensure that the tail is a node known to the graph. // if (edges.get(tail) == null) { edges.put(tail, new HashSet<N>()); } // // Then create the edge. // HashSet<N> tails = edges.get(head); if (tails == null) { tails = new HashSet<N>(); edges.put(head, tails); } tails.add(tail); } /** * Add the given node to the set of nodes in the graph. * * @param node the node to be added */ public void addNode(N node) { HashSet<N> tails = edges.get(node); if (tails == null) { edges.put(node, new HashSet<N>()); } } /** * Run a topological sort of the graph. Since the graph may contain cycles, this results in a list * of strongly connected components rather than a list of nodes. The nodes in each strongly * connected components only have edges that point to nodes in the same component or earlier * components. */ public ArrayList<ArrayList<N>> computeTopologicalSort() { SccFinder<N> finder = new SccFinder<N>(this); return finder.computeTopologicalSort(); } /** * Return true if the graph contains at least one path from {@code source} to {@code destination}. */ public boolean containsPath(N source, N destination) { HashSet<N> nodesVisited = new HashSet<N>(); return containsPathInternal(source, destination, nodesVisited); } /** * Return a list of nodes that form a cycle containing the given node. If the node is not part of * this graph, then a list containing only the node itself will be returned. * * @return a list of nodes that form a cycle containing the given node */ public List<N> findCycleContaining(N node) { if (node == null) { throw new IllegalArgumentException(); } SccFinder<N> finder = new SccFinder<N>(this); return finder.componentContaining(node); } /** * Return the number of nodes in this graph. * * @return the number of nodes in this graph */ public int getNodeCount() { return edges.size(); } /** * Return a set of all nodes in the graph. */ public Set<N> getNodes() { return edges.keySet(); } /** * Return a set containing the tails of edges that have the given node as their head. The set will * be empty if there are no such edges or if the node is not part of the graph. Clients must not * modify the returned set. * * @param head the node at the head of all of the edges whose tails are to be returned * @return a set containing the tails of edges that have the given node as their head */ public Set<N> getTails(N head) { HashSet<N> tails = edges.get(head); if (tails == null) { return new HashSet<N>(); } return tails; } /** * Return {@code true} if this graph is empty. * * @return {@code true} if this graph is empty */ public boolean isEmpty() { return edges.isEmpty(); } /** * Remove all of the given nodes from this graph. As a consequence, any edges for which those * nodes were either a head or a tail will also be removed. * * @param nodes the nodes to be removed */ public void removeAllNodes(List<N> nodes) { for (N node : nodes) { removeNode(node); } } /** * Remove the edge from the given head node to the given tail node. If there was no such edge then * the graph will be unmodified: the number of edges will be the same and the set of nodes will be * the same (neither node will either be added or removed). * * @param head the node at the head of the edge * @param tail the node at the tail of the edge * @return {@code true} if the graph was modified as a result of this operation */ public void removeEdge(N head, N tail) { HashSet<N> tails = edges.get(head); if (tails != null) { tails.remove(tail); } } /** * Remove the given node from this graph. As a consequence, any edges for which that node was * either a head or a tail will also be removed. * * @param node the node to be removed */ public void removeNode(N node) { edges.remove(node); for (HashSet<N> tails : edges.values()) { tails.remove(node); } } /** * Find one node (referred to as a sink node) that has no outgoing edges (that is, for which there * are no edges that have that node as the head of the edge) and remove it from this graph. Return * the node that was removed, or {@code null} if there are no such nodes either because the graph * is empty or because every node in the graph has at least one outgoing edge. As a consequence of * removing the node from the graph any edges for which that node was a tail will also be removed. * * @return the sink node that was removed */ public N removeSink() { N sink = findSink(); if (sink == null) { return null; } removeNode(sink); return sink; } private boolean containsPathInternal(N source, N destination, HashSet<N> nodesVisited) { if (source == destination) { return true; } HashSet<N> tails = edges.get(source); if (tails != null) { nodesVisited.add(source); for (N tail : tails) { if (!nodesVisited.contains(tail)) { if (containsPathInternal(tail, destination, nodesVisited)) { return true; } } } } return false; } /** * Return one node that has no outgoing edges (that is, for which there are no edges that have * that node as the head of the edge), or {@code null} if there are no such nodes. * * @return a sink node */ @DartBlockBody({// "for (N key in _edges.keys) {",// " if (_edges[key].isEmpty) return key;",// "}",// "return null;"}) private N findSink() { for (Map.Entry<N, HashSet<N>> entry : edges.entrySet()) { if (entry.getValue().isEmpty()) { return entry.getKey(); } } return null; } }