// Copyright 2014 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 // // 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.devtools.build.lib.graph; import static java.util.Comparator.comparing; import static java.util.Comparator.comparingLong; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.PriorityQueue; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; /** * {@code Digraph} a generic directed graph or "digraph", suitable for modeling asymmetric binary * relations. * *

An instance G = <V,E> consists of a set of nodes or vertices V * , and a set of directed edges E, which is a subset of V × V. This * permits self-edges but does not represent multiple edges between the same pair of nodes. * *

Nodes may be labeled with values of any type (type parameter T). All nodes within a graph have * distinct labels. The null pointer is not a valid label. * *

The package supports various operations for modeling partial order relations, and supports * input/output in AT&T's 'dot' format. See http://www.research.att.com/sw/tools/graphviz/. * *

Some invariants: * *

* *

Each node stores successor and predecessor adjacency sets using a representation that * dynamically changes with size: small sets are stored as arrays, large sets using hash tables. * This representation provides significant space and time performance improvements upon two prior * versions: the earliest used only HashSets; a later version used linked lists, as described in * Cormen, Leiserson & Rivest. */ public final class Digraph implements Cloneable { /** Maps labels to nodes, which are in strict 1:1 correspondence. */ private final Map> nodes = new ConcurrentHashMap<>(); /** * Construct an empty Digraph. */ public Digraph() {} /** * Sanity-check: assert that a node is indeed a member of this graph and not * another one. Perform this check whenever a function is supplied a node by * the user. */ private final void checkNode(Node node) { if (getNode(node.getLabel()) != node) { throw new IllegalArgumentException("node " + node + " is not a member of this graph"); } } /** * Adds a directed edge between the nodes labelled 'from' and 'to', creating * them if necessary. * * @return true iff the edge was not already present. */ public boolean addEdge(T from, T to) { Node fromNode = createNode(from); Node toNode = createNode(to); return addEdge(fromNode, toNode); } /** * Adds a directed edge between the specified nodes, which must exist and * belong to this graph. * * @return true iff the edge was not already present. * * Note: multi-edges are ignored. Self-edges are permitted. */ public boolean addEdge(Node fromNode, Node toNode) { checkNode(fromNode); checkNode(toNode); return fromNode.addEdge(toNode); } /** * Returns true iff the graph contains an edge between the * specified nodes, which must exist and belong to this graph. */ public boolean containsEdge(Node fromNode, Node toNode) { checkNode(fromNode); checkNode(toNode); // TODO(bazel-team): (2009) iterate only over the shorter of from.succs, to.preds. return fromNode.getSuccessors().contains(toNode); } /** * Removes the edge between the specified nodes. Idempotent: attempts to * remove non-existent edges have no effect. * * @return true iff graph changed. */ public boolean removeEdge(Node fromNode, Node toNode) { checkNode(fromNode); checkNode(toNode); return fromNode.removeEdge(toNode); } /** * Remove all nodes and edges. */ public void clear() { nodes.clear(); } @Override public String toString() { return "Digraph[" + getNodeCount() + " nodes]"; } @Override public int hashCode() { throw new UnsupportedOperationException(); // avoid nondeterminism } /** * Returns true iff the two graphs are equivalent, i.e. have the same set * of node labels, with the same connectivity relation. * * O(n^2) in the worst case, i.e. equivalence. The algorithm could be speed up by * close to a factor 2 in the worst case by a more direct implementation instead * of using isSubgraph twice. */ @Override public boolean equals(Object thatObject) { /* If this graph is a subgraph of thatObject, then we know that thatObject is of * type Digraph and thatObject can be cast to this type. */ return isSubgraph(thatObject) && ((Digraph) thatObject).isSubgraph(this); } /** * Returns true iff this graph is a subgraph of the argument. This means that this graph's nodes * are a subset of those of the argument; moreover, for each node of this graph the set of * successors is a subset of those of the corresponding node in the argument graph. * * This algorithm is O(n^2), but linear in the total sizes of the graphs. */ public boolean isSubgraph(Object thatObject) { if (this == thatObject) { return true; } if (!(thatObject instanceof Digraph)) { return false; } @SuppressWarnings("unchecked") Digraph that = (Digraph) thatObject; if (this.getNodeCount() > that.getNodeCount()) { return false; } for (Node n1: nodes.values()) { Node n2 = that.getNodeMaybe(n1.getLabel()); if (n2 == null) { return false; // 'that' is missing a node } // Now compare the successor relations. // Careful: // - We can't do simple equality on the succs-sets because the // nodes belong to two different graphs! // - There's no need to check both predecessor and successor // relations, either one is sufficient. Collection> n1succs = n1.getSuccessors(); Collection> n2succs = n2.getSuccessors(); if (n1succs.size() > n2succs.size()) { return false; } // foreach successor of n1, ensure n2 has a similarly-labeled succ. for (Node succ1: n1succs) { Node succ2 = that.getNodeMaybe(succ1.getLabel()); if (succ2 == null) { return false; } if (!n2succs.contains(succ2)) { return false; } } } return true; } /** * Returns a duplicate graph with the same set of node labels and the same * connectivity relation. The labels themselves are not cloned. */ @Override public Digraph clone() { final Digraph that = new Digraph(); visitNodesBeforeEdges( new AbstractGraphVisitor() { @Override public void visitEdge(Node lhs, Node rhs) { that.addEdge(lhs.getLabel(), rhs.getLabel()); } @Override public void visitNode(Node node) { that.createNode(node.getLabel()); } }, nodes.values(), null); return that; } /** Returns a deterministic immutable copy of the nodes of this graph. */ public Collection> getNodes(final Comparator comparator) { return ImmutableList.sortedCopyOf(comparing(Node::getLabel, comparator), nodes.values()); } /** * Returns an immutable view of the nodes of this graph. * * Note: we have to return Collection and not Set because values() returns * one: the 'nodes' HashMap doesn't know that it is injective. :-( */ public Collection> getNodes() { return Collections.unmodifiableCollection(nodes.values()); } /** * @return the set of root nodes: those with no predecessors. * * NOTE: in a cyclic graph, there may be nodes that are not reachable from * any "root". */ public Set> getRoots() { Set> roots = new HashSet<>(); for (Node node: nodes.values()) { if (!node.hasPredecessors()) { roots.add(node); } } return roots; } /** * @return the set of leaf nodes: those with no successors. */ public Set> getLeaves() { Set> leaves = new HashSet<>(); for (Node node: nodes.values()) { if (!node.hasSuccessors()) { leaves.add(node); } } return leaves; } /** * @return an immutable view of the set of labels of this graph's nodes. */ public Set getLabels() { return Collections.unmodifiableSet(nodes.keySet()); } /** * Finds and returns the node with the specified label. If there is no such * node, an exception is thrown. The null pointer is not a valid label. * * @return the node whose label is "label". * @throws IllegalArgumentException if no node was found with the specified * label. */ public Node getNode(T label) { if (label == null) { throw new NullPointerException(); } Node node = nodes.get(label); if (node == null) { throw new IllegalArgumentException("No such node label: " + label); } return node; } /** * Find the node with the specified label. Returns null if it doesn't exist. * The null pointer is not a valid label. * * @return the node whose label is "label", or null if it was not found. */ public Node getNodeMaybe(T label) { if (label == null) { throw new NullPointerException(); } return nodes.get(label); } /** * @return the number of nodes in the graph. */ public int getNodeCount() { return nodes.size(); } /** * @return the number of edges in the graph. * * Note: expensive! Useful when asserting against mutations though. */ public int getEdgeCount() { int edges = 0; for (Node node: nodes.values()) { edges += node.getSuccessors().size(); } return edges; } /** * Find or create a node with the specified label. This is the only factory of Nodes. The * null pointer is not a valid label. */ public Node createNode(T label) { return nodes.computeIfAbsent(label, Digraph::createNodeNative); } private static Node createNodeNative(T label) { Preconditions.checkNotNull(label); return new Node<>(label); } /****************************************************************** * * * Graph Algorithms * * * ******************************************************************/ /** * These only manipulate the graph through methods defined above. */ /** * Returns true iff the graph is cyclic. Time: O(n). */ public boolean isCyclic() { // To detect cycles, we use a colored depth-first search. All nodes are // initially marked white. When a node is encountered, it is marked grey, // and when its descendants are completely visited, it is marked black. // If a grey node is ever encountered, then there is a cycle. final Object WHITE = null; // i.e. not present in nodeToColor, the default. final Object GREY = new Object(); final Object BLACK = new Object(); final Map, Object> nodeToColor = new HashMap<>(); // empty => all white class CycleDetector { /* defining a class gives us lexical scope */ boolean visit(Node node) { nodeToColor.put(node, GREY); for (Node succ: node.getSuccessors()) { if (nodeToColor.get(succ) == GREY) { return true; } else if (nodeToColor.get(succ) == WHITE) { if (visit(succ)) { return true; } } } nodeToColor.put(node, BLACK); return false; } } CycleDetector detector = new CycleDetector(); for (Node node: nodes.values()) { if (nodeToColor.get(node) == WHITE) { if (detector.visit(node)) { return true; } } } return false; } /** * Returns the strong component graph of "this". That is, returns a new * acyclic graph in which all strongly-connected components in the original * graph have been "fused" into a single node. * * @return a new graph, whose node labels are sets of nodes of the * original graph. (Do not get confused as to which graph each * set of Nodes belongs!) */ public Digraph>> getStrongComponentGraph() { Collection>> sccs = getStronglyConnectedComponents(); Digraph>> scGraph = createImageUnderPartition(sccs); scGraph.removeSelfEdges(); // scGraph should be acyclic: no self-edges return scGraph; } /** * Returns a partition of the nodes of this graph into sets, each set being * one strongly-connected component of the graph. */ public Collection>> getStronglyConnectedComponents() { final List>> sccs = new ArrayList<>(); NodeSetReceiver r = sccs::add; SccVisitor v = new SccVisitor<>(); for (Node node : nodes.values()) { v.visit(r, node); } return sccs; } /** *

Given a partition of the graph into sets of nodes, returns the image * of this graph under the function which maps each node to the * partition-set in which it appears. The labels of the new graph are the * (immutable) sets of the partition, and the edges of the new graph are the * edges of the original graph, mapped via the same function.

* *

Note: the resulting graph may contain self-edges. If these are not * wanted, call removeSelfEdges()> on the result.

* *

Interesting special case: if the partition is the set of * strongly-connected components, the result of this function is the * strong-component graph.

*/ public Digraph>> createImageUnderPartition(Collection>> partition) { // Build mapping function: each node label is mapped to its equiv class: Map>> labelToImage = new HashMap<>(); for (Set> set: partition) { // It's important to use immutable sets of node labels when sets are keys // in a map; see ImmutableSet class for explanation. Set> imageSet = ImmutableSet.copyOf(set); for (Node node: imageSet) { labelToImage.put(node.getLabel(), imageSet); } } if (labelToImage.size() != getNodeCount()) { throw new IllegalArgumentException( "createImageUnderPartition(): argument is not a partition"); } return createImageUnderMapping(labelToImage); } /** * Returns the image of this graph in a given function, expressed as a * mapping from labels to some other domain. */ public Digraph createImageUnderMapping(Map map) { Digraph imageGraph = new Digraph<>(); for (Node fromNode: nodes.values()) { T fromLabel = fromNode.getLabel(); IMAGE fromImage = map.get(fromLabel); if (fromImage == null) { throw new IllegalArgumentException( "Incomplete function: undefined for " + fromLabel); } imageGraph.createNode(fromImage); for (Node toNode: fromNode.getSuccessors()) { T toLabel = toNode.getLabel(); IMAGE toImage = map.get(toLabel); if (toImage == null) { throw new IllegalArgumentException( "Incomplete function: undefined for " + toLabel); } imageGraph.addEdge(fromImage, toImage); } } return imageGraph; } /** * Removes any self-edges (x,x) in this graph. */ public void removeSelfEdges() { for (Node node: nodes.values()) { removeEdge(node, node); } } /** * Finds the shortest directed path from "fromNode" to "toNode". The path is * returned as an ordered list of nodes, including both endpoints. Returns * null if there is no path. Uses breadth-first search. Running time is * O(n). */ public List> getShortestPath(Node fromNode, Node toNode) { checkNode(fromNode); checkNode(toNode); if (fromNode == toNode) { return Collections.singletonList(fromNode); } Map, Node> pathPredecessor = new HashMap<>(); Set> marked = new HashSet<>(); LinkedList> queue = new LinkedList<>(); queue.addLast(fromNode); marked.add(fromNode); while (!queue.isEmpty()) { Node u = queue.removeFirst(); for (Node v: u.getSuccessors()) { if (marked.add(v)) { pathPredecessor.put(v, u); if (v == toNode) { return getPathToTreeNode(pathPredecessor, v); // found a path } queue.addLast(v); } } } return null; // no path } /** * Given a tree (expressed as a map from each node to its parent), and a * starting node, returns the path from the root of the tree to 'node' as a * list. */ public static List getPathToTreeNode(Map tree, X node) { List path = new ArrayList<>(); while (node != null) { path.add(node); node = tree.get(node); // get parent } Collections.reverse(path); return path; } /** * Returns the nodes of an acyclic graph in topological order * [a.k.a "reverse post-order" of depth-first search.] * * A topological order is one such that, if (u, v) is a path in * acyclic graph G, then u is before v in the topological order. * In other words "tails before heads" or "roots before leaves". * * @return The nodes of the graph, in a topological order */ public List> getTopologicalOrder() { List> order = getPostorder(); Collections.reverse(order); return order; } /** * Returns the nodes of an acyclic graph in topological order * [a.k.a "reverse post-order" of depth-first search.] * * A topological order is one such that, if (u, v) is a path in * acyclic graph G, then u is before v in the topological order. * In other words "tails before heads" or "roots before leaves". * * If an ordering is given, returns a specific topological order from the set * of all topological orders; if no ordering given, returns an arbitrary * (nondeterministic) one, but is a bit faster because no sorting needs to be * done for each node. * * @param edgeOrder the ordering in which edges originating from the same node * are visited. * @return The nodes of the graph, in a topological order */ public List> getTopologicalOrder(Comparator edgeOrder) { CollectingVisitor visitor = new CollectingVisitor<>(); DFS visitation = new DFS<>(DFS.Order.POSTORDER, edgeOrder, false); visitor.beginVisit(); for (Node node : getNodes(edgeOrder)) { visitation.visit(node, visitor); } visitor.endVisit(); List> order = visitor.getVisitedNodes(); Collections.reverse(order); return order; } /** * Returns the nodes of an acyclic graph in post-order. */ public List> getPostorder() { CollectingVisitor collectingVisitor = new CollectingVisitor<>(); visitPostorder(collectingVisitor); return collectingVisitor.getVisitedNodes(); } /** * Returns the (immutable) set of nodes reachable from node 'n' (reflexive * transitive closure). */ public Set> getFwdReachable(Node n) { return getFwdReachable(Collections.singleton(n)); } /** * Returns the (immutable) set of nodes reachable from any node in {@code * startNodes} (reflexive transitive closure). */ public Set> getFwdReachable(Collection> startNodes) { // This method is intentionally not static, to permit future expansion. DFS dfs = new DFS(DFS.Order.PREORDER, false); for (Node n : startNodes) { dfs.visit(n, new AbstractGraphVisitor<>()); } return dfs.getMarked(); } /** * Returns the (immutable) set of nodes that reach node 'n' (reflexive * transitive closure). */ public Set> getBackReachable(Node n) { return getBackReachable(Collections.singleton(n)); } /** * Returns the (immutable) set of nodes that reach some node in {@code * startNodes} (reflexive transitive closure). */ public Set> getBackReachable(Collection> startNodes) { // This method is intentionally not static, to permit future expansion. DFS dfs = new DFS(DFS.Order.PREORDER, true); for (Node n : startNodes) { dfs.visit(n, new AbstractGraphVisitor<>()); } return dfs.getMarked(); } /** * Removes the specified node in the graph. * *

If preserveOrder flag is set than after removing node this method connects all predecessors * and successors. * *

Let's consider graph * *

   * a -> n -> c
   * b -> n -> d
   * 
* * After n removed the following edges will be added * *
   * a -> c
   * a -> d
   * b -> c
   * b -> d
   * 
* * @param node the node to remove (must be in the graph). * @param preserveOrder see removeNode(T, boolean). */ public Collection> removeNode(Node node, boolean preserveOrder) { checkNode(node); Collection> predecessors = node.removeAllPredecessors(); Collection> successors = node.removeAllSuccessors(); List> neighbours = Collections.emptyList(); if (preserveOrder) { neighbours = new ArrayList<>(successors.size() + predecessors.size()); neighbours.addAll(successors); neighbours.addAll(predecessors); for (Node p : predecessors) { for (Node s : successors) { p.addEdge(s); } } } Object del = nodes.remove(node.getLabel()); if (del != node) { throw new IllegalStateException(del + " " + node); } return neighbours; } /** * Extracts the subgraph G' of this graph G, containing exactly the nodes * specified by the labels in V', and preserving the original * transitive graph relation among those nodes.

* * @param subset a subset of the labels of this graph; the resulting graph * will have only the nodes with these labels. */ public Digraph extractSubgraph(final Set subset) { Digraph subgraph = this.clone(); subgraph.subgraph(subset); return subgraph; } /** * Removes all nodes from this graph except those whose label is an element of {@code keepLabels}. * Edges are added so as to preserve the transitive closure relation. * * @param keepLabels a subset of the labels of this graph; the resulting graph will have only the * nodes with these labels. */ private void subgraph(final Set keepLabels) { // This algorithm does the following: // Let keep = nodes that have labels in keepLabels. // Let toRemove = nodes \ keep. reachables = successors and predecessors of keep in nodes. // reachables is the subset of nodes of remove that are an immediate neighbor of some node in // keep. // // Removes all nodes of reachables from keepLabels. // Until reachables is empty: // Takes n from reachables // for all s in succ(n) // for all p in pred(n) // add the edge (p, s) // add s to reachables // for all p in pred(n) // add p to reachables // Remove n and its edges // // A few adjustments are needed to do the whole computation. final Set> toRemove = new HashSet<>(); final Set> keepNeighbors = new HashSet<>(); // Look for all nodes if they are to be kept or removed for (Node node : nodes.values()) { if (keepLabels.contains(node.getLabel())) { // Node is to be kept keepNeighbors.addAll(node.getPredecessors()); keepNeighbors.addAll(node.getSuccessors()); } else { // node is to be removed. toRemove.add(node); } } if (toRemove.isEmpty()) { // This premature return is needed to avoid 0-size priority queue creation. return; } // We use a priority queue to look for low-order nodes first so we don't propagate the high // number of paths of high-order nodes making the time consumption explode. // For perfect results we should reorder the set each time we add a new edge but this would // be too expensive, so this is a good enough approximation. final PriorityQueue> reachables = new PriorityQueue<>( toRemove.size(), comparingLong(arg -> (long) arg.numPredecessors() * (long) arg.numSuccessors())); // Construct the reachables queue with the list of successors and predecessors of keep in // toRemove. keepNeighbors.retainAll(toRemove); reachables.addAll(keepNeighbors); toRemove.removeAll(reachables); // Remove nodes, least connected first, preserving reachability. while (!reachables.isEmpty()) { Node node = reachables.poll(); Collection> neighbours = removeNode(node, /*preserveOrder*/ true); for (Node neighbour : neighbours) { if (toRemove.remove(neighbour)) { reachables.add(neighbour); } } } // Final cleanup for non-reachable nodes. for (Node node : toRemove) { removeNode(node, false); } } @FunctionalInterface private interface NodeSetReceiver { void accept(Set> nodes); } /** * Find strongly connected components using path-based strong component * algorithm. This has the advantage over the default method of returning * the components in postorder. * * We visit nodes depth-first, keeping track of the order that * we visit them in (preorder). Our goal is to find the smallest node (in * this preorder of visitation) reachable from a given node. We keep track of the * smallest node pointed to so far at the top of a stack. If we ever find an * already-visited node, then if it is not already part of a component, we * pop nodes from that stack until we reach this already-visited node's number * or an even smaller one. * * Once the depth-first visitation of a node is complete, if this node's * number is at the top of the stack, then it is the "first" element visited * in its strongly connected component. Hence we pop all elements that were * pushed onto the visitation stack and put them in a strongly connected * component with this one, then send a passed-in {@link Digraph.NodeSetReceiver} this component. */ private class SccVisitor { // Nodes already assigned to a strongly connected component. private final Set> assigned = new HashSet<>(); // The order each node was visited in. private final Map, Integer> preorder = new HashMap<>(); // Stack of all nodes visited whose SCC has not yet been determined. When an SCC is found, // that SCC is an initial segment of this stack, and is popped off. Every time a new node is // visited, it is put on this stack. private final List> stack = new ArrayList<>(); // Stack of visited indices for the first-visited nodes in each of their known-so-far // strongly connected components. A node pushes its index on when it is visited. If any of // its successors have already been visited and are not in an already-found strongly connected // component, then, since the successor was already visited, it and this node must be part of a // cycle. So every node visited since the successor is actually in the same strongly connected // component. In this case, preorderStack is popped until the top is at most the successor's // index. // // After all descendants of a node have been visited, if the top element of preorderStack is // still the current node's index, then it was the first element visited of the current strongly // connected component. So all nodes on {@code stack} down to the current node are in its // strongly connected component. And the node's index is popped from preorderStack. private final List preorderStack = new ArrayList<>(); // Index of node being visited. private int counter = 0; private void visit(NodeSetReceiver visitor, Node node) { if (preorder.containsKey(node)) { // This can only happen if this was a non-recursive call, and a previous // visit call had already visited node. return; } preorder.put(node, counter); stack.add(node); preorderStack.add(counter++); int preorderLength = preorderStack.size(); for (Node succ : node.getSuccessors()) { Integer succPreorder = preorder.get(succ); if (succPreorder == null) { visit(visitor, succ); } else { // Does succ not already belong to an SCC? If it doesn't, then it // must be in the same SCC as node. The "starting node" of this SCC // must have been visited before succ (or is succ itself). if (!assigned.contains(succ)) { while (preorderStack.get(preorderStack.size() - 1) > succPreorder) { preorderStack.remove(preorderStack.size() - 1); } } } } if (preorderLength == preorderStack.size()) { // If the length of the preorderStack is unchanged, we did not find any earlier-visited // nodes that were part of a cycle with this node. So this node is the first-visited // element in its strongly connected component, and we collect the component. preorderStack.remove(preorderStack.size() - 1); Set> scc = new HashSet<>(); Node compNode; do { compNode = stack.remove(stack.size() - 1); assigned.add(compNode); scc.add(compNode); } while (!node.equals(compNode)); visitor.accept(scc); } } } /******************************************************************** * * * Orders, traversals and visitors * * * ********************************************************************/ /** * A visitation over all the nodes in the graph that invokes * visitor.visitNode() for each node in a depth-first * post-order: each node is visited after each of its successors; the * order in which edges are traversed is the order in which they were added * to the graph. visitor.visitEdge() is not called. * * @param startNodes the set of nodes from which to begin the visitation. */ public void visitPostorder(GraphVisitor visitor, Iterable> startNodes) { visitDepthFirst(visitor, DFS.Order.POSTORDER, false, startNodes); } /** * Equivalent to {@code visitPostorder(visitor, getNodes())}. */ public void visitPostorder(GraphVisitor visitor) { visitPostorder(visitor, nodes.values()); } /** * A visitation over all the nodes in the graph that invokes * visitor.visitNode() for each node in a depth-first * pre-order: each node is visited before each of its successors; the * order in which edges are traversed is the order in which they were added * to the graph. visitor.visitEdge() is not called. * * @param startNodes the set of nodes from which to begin the visitation. */ public void visitPreorder(GraphVisitor visitor, Iterable> startNodes) { visitDepthFirst(visitor, DFS.Order.PREORDER, false, startNodes); } /** * Equivalent to {@code visitPreorder(visitor, getNodes())}. */ public void visitPreorder(GraphVisitor visitor) { visitPreorder(visitor, nodes.values()); } /** * A visitation over all the nodes in the graph in depth-first order. See * DFS constructor for meaning of 'order' and 'transpose' parameters. * * @param startNodes the set of nodes from which to begin the visitation. */ public void visitDepthFirst(GraphVisitor visitor, DFS.Order order, boolean transpose, Iterable> startNodes) { DFS visitation = new DFS<>(order, transpose); visitor.beginVisit(); for (Node node: startNodes) { visitation.visit(node, visitor); } visitor.endVisit(); } private static Comparator> makeNodeComparator( final Comparator comparator) { return comparing(Node::getLabel, comparator::compare); } /** * Given {@code unordered}, a collection of nodes and a (possibly null) {@code comparator} for * their labels, returns a sorted collection if {@code comparator} is non-null, otherwise returns * {@code unordered}. */ private static Collection> maybeOrderCollection( Collection> unordered, @Nullable final Comparator comparator) { return comparator == null ? unordered : ImmutableList.sortedCopyOf(makeNodeComparator(comparator), unordered); } private void visitNodesBeforeEdges( GraphVisitor visitor, Iterable> startNodes, @Nullable Comparator comparator) { visitor.beginVisit(); for (Node fromNode: startNodes) { visitor.visitNode(fromNode); for (Node toNode : maybeOrderCollection(fromNode.getSuccessors(), comparator)) { visitor.visitEdge(fromNode, toNode); } } visitor.endVisit(); } /** * A visitation over the graph that visits all nodes and edges in topological order * such that each node is visited before any edge coming out of that node; ties among nodes are * broken using the provided {@code comparator} if not null; edges are visited in order specified * by the comparator, not topological order of the target nodes. */ public void visitNodesBeforeEdges( GraphVisitor visitor, @Nullable Comparator comparator) { visitNodesBeforeEdges( visitor, comparator == null ? getTopologicalOrder() : getTopologicalOrder(comparator), comparator); } }