/*
* Copyright 2012-present Facebook, Inc.
*
* 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.facebook.buck.graph;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Represents a directed graph with unweighted edges. For a given source and sink node pair, there
* is at most one directed edge connecting them in the graph. The graph is not required to be
* connected or acyclic.
*
* @param <T> the type of object stored as nodes in this graph
*/
public final class MutableDirectedGraph<T> implements TraversableGraph<T> {
/**
* It is possible to have a node in the graph without any edges, which is why we must maintain a
* separate collection for nodes in the graph, rather than just using the keySet of {@link
* #outgoingEdges}.
*/
private final Set<T> nodes;
/**
* Represents the edges in the graph. Keys are source nodes; values are corresponding sync nodes.
*/
private final SetMultimap<T, T> outgoingEdges;
/**
* Represents the edges in the graph. Keys are sink nodes; values are corresponding source nodes.
*/
private final SetMultimap<T, T> incomingEdges;
private MutableDirectedGraph(
Set<T> nodes, SetMultimap<T, T> outgoingEdges, SetMultimap<T, T> incomingEdges) {
this.nodes = nodes;
this.outgoingEdges = outgoingEdges;
this.incomingEdges = incomingEdges;
}
/** Creates a new graph with no nodes or edges. */
public MutableDirectedGraph() {
this(new HashSet<T>(), HashMultimap.create(), HashMultimap.create());
}
public static <T> MutableDirectedGraph<T> createConcurrent() {
return new MutableDirectedGraph<>(
Collections.newSetFromMap(new ConcurrentHashMap<T, Boolean>()),
Multimaps.synchronizedSetMultimap(HashMultimap.create()),
Multimaps.synchronizedSetMultimap(HashMultimap.create()));
}
/** @return the number of nodes in the graph */
public int getNodeCount() {
return nodes.size();
}
/** @return the number of edges in the graph */
public int getEdgeCount() {
return outgoingEdges.size();
}
/** @return whether the specified node is present in the graph */
public boolean containsNode(T node) {
return nodes.contains(node);
}
/** @return whether an edge from the source to the sink is present in the graph */
public boolean containsEdge(T source, T sink) {
return outgoingEdges.containsEntry(source, sink);
}
/** Adds the specified node to the graph. */
public boolean addNode(T node) {
return nodes.add(node);
}
/** Removes the specified node from the graph. */
public boolean removeNode(T node) {
boolean isRemoved = nodes.remove(node);
Set<T> nodesReachableFromTheSpecifiedNode = outgoingEdges.removeAll(node);
for (T reachableNode : nodesReachableFromTheSpecifiedNode) {
incomingEdges.remove(reachableNode, node);
}
return isRemoved;
}
/**
* Adds an edge between {@code source} and {@code sink}. Adds the nodes to the graph if they are
* not already present.
*/
public void addEdge(T source, T sink) {
nodes.add(source);
nodes.add(sink);
outgoingEdges.put(source, sink);
incomingEdges.put(sink, source);
}
/**
* Removes the edge between {@code source} and {@code sink}. This does not remove {@code source}
* or {@code sink} from the graph. Note that this may leave either {@code source} or {@code sink}
* as unconnected nodes in the graph.
*/
public void removeEdge(T source, T sink) {
outgoingEdges.remove(source, sink);
incomingEdges.remove(sink, source);
}
@Override
public Iterable<T> getOutgoingNodesFor(T source) {
return outgoingEdges.get(source);
}
@Override
public Iterable<T> getIncomingNodesFor(T sink) {
return incomingEdges.get(sink);
}
public boolean hasIncomingEdges(T node) {
return this.incomingEdges.containsKey(node);
}
@Override
public Set<T> getNodes() {
return Collections.unmodifiableSet(nodes);
}
public boolean isAcyclic() {
return findCycles().isEmpty();
}
public ImmutableSet<ImmutableSet<T>> findCycles() {
Set<Set<T>> cycles =
Sets.filter(
findStronglyConnectedComponents(),
stronglyConnectedComponent -> stronglyConnectedComponent.size() > 1);
Iterable<ImmutableSet<T>> immutableCycles = Iterables.transform(cycles, ImmutableSet::copyOf);
// Tarjan's algorithm (as pseudo-coded on Wikipedia) does not appear to account for single-node
// cycles. Therefore, we must check for them exclusively.
ImmutableSet.Builder<ImmutableSet<T>> builder = ImmutableSet.builder();
builder.addAll(immutableCycles);
for (T node : nodes) {
if (containsEdge(node, node)) {
builder.add(ImmutableSet.of(node));
}
}
return builder.build();
}
/**
* For this graph, returns the set of strongly connected components using Tarjan's algorithm. Note
* this is {@code O(|V| + |E|)}.
*
* @return an unmodifiable {@link Set} of sets, each of which is also an unmodifiable {@link Set}
* and represents a strongly connected component.
*/
public Set<Set<T>> findStronglyConnectedComponents() {
Tarjan<T> tarjan = new Tarjan<T>(this);
return tarjan.findStronglyConnectedComponents();
}
@Override
public Iterable<T> getNodesWithNoIncomingEdges() {
return Sets.difference(nodes, incomingEdges.keySet());
}
@Override
public Iterable<T> getNodesWithNoOutgoingEdges() {
return Sets.difference(nodes, outgoingEdges.keySet());
}
ImmutableSet<T> createImmutableCopyOfNodes() {
return ImmutableSet.copyOf(nodes);
}
ImmutableSetMultimap<T, T> createImmutableCopyOfOutgoingEdges() {
return ImmutableSetMultimap.copyOf(outgoingEdges);
}
ImmutableSetMultimap<T, T> createImmutableCopyOfIncomingEdges() {
return ImmutableSetMultimap.copyOf(incomingEdges);
}
/**
* Implementation of
* http://en.wikipedia.org/wiki/Tarjan%E2%80%99s_strongly_connected_components_algorithm used to
* find cycles in the graph.
*/
private static class Tarjan<S> {
private final MutableDirectedGraph<S> graph;
private final Map<S, Integer> indexes;
private final Map<S, Integer> lowlinks;
private final Deque<S> nodeStack;
private final Set<Set<S>> stronglyConnectedComponents;
private int index;
private Tarjan(MutableDirectedGraph<S> graph) {
this.graph = graph;
this.indexes = new HashMap<>();
this.lowlinks = new HashMap<>();
this.nodeStack = new LinkedList<>();
this.stronglyConnectedComponents = Sets.newHashSet();
this.index = 0;
}
public Set<Set<S>> findStronglyConnectedComponents() {
for (S node : graph.nodes) {
if (!indexes.containsKey(node)) {
doStrongConnect(node);
}
}
return Collections.unmodifiableSet(stronglyConnectedComponents);
}
private void doStrongConnect(final S node) {
// Set the depth index for node to the smallest unused index.
indexes.put(node, index);
lowlinks.put(node, index);
index++;
nodeStack.push(node);
// Consider successors of node.
for (S sink : graph.getOutgoingNodesFor(node)) {
if (!indexes.containsKey(sink)) {
doStrongConnect(sink);
int lowlink =
Math.min(
Preconditions.checkNotNull(lowlinks.get(node)),
Preconditions.checkNotNull(lowlinks.get(sink)));
lowlinks.put(node, lowlink);
} else if (nodeStack.contains(sink)) {
// TODO(mbolin): contains() is O(N), consider maintaining an index so it is O(1)?
int lowlink =
Math.min(
Preconditions.checkNotNull(lowlinks.get(node)),
Preconditions.checkNotNull(indexes.get(sink)));
lowlinks.put(node, lowlink);
}
}
// If node is a root node, then pop the stack and generate a strongly connected component.
if (Preconditions.checkNotNull(lowlinks.get(node)).equals(indexes.get(node))) {
Set<S> stronglyConnectedComponent = Sets.newHashSet();
S componentElement;
do {
componentElement = nodeStack.pop();
stronglyConnectedComponent.add(componentElement);
} while (componentElement != node);
stronglyConnectedComponents.add(Collections.unmodifiableSet(stronglyConnectedComponent));
}
}
}
}