package org.ggp.base.util.gdl.model;
import java.util.Collection;
import java.util.Deque;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Queues;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
/**
* When dealing with GDL, dependency graphs are often useful. DependencyGraphs
* offers a variety of functionality for dealing with dependency graphs expressed
* in the form of SetMultimaps.
*
* These multimaps are paired with sets of all nodes, to account for the
* possibility of nodes not included in the multimap representation.
*
* All methods assume that keys in multimaps depend on their associated values,
* or in other words are downstream of or are children of those values.
*/
public class DependencyGraphs {
private DependencyGraphs() {}
/**
* Returns all elements of the dependency graph that match the
* given predicate, and any elements upstream of those matching
* elements.
*
* The graph may contain cycles.
*
* Each key in the dependency graph depends on/is downstream of
* its associated values.
*/
public static <T> ImmutableSet<T> getMatchingAndUpstream(
Set<T> allNodes,
SetMultimap<T, T> dependencyGraph,
Predicate<T> matcher) {
Set<T> results = Sets.newHashSet();
Deque<T> toTry = Queues.newArrayDeque();
toTry.addAll(Collections2.filter(allNodes, matcher));
while (!toTry.isEmpty()) {
T curElem = toTry.remove();
if (!results.contains(curElem)) {
results.add(curElem);
toTry.addAll(dependencyGraph.get(curElem));
}
}
return ImmutableSet.copyOf(results);
}
/**
* Returns all elements of the dependency graph that match the
* given predicate, and any elements downstream of those matching
* elements.
*
* The graph may contain cycles.
*
* Each key in the dependency graph depends on/is downstream of
* its associated values.
*/
public static <T> ImmutableSet<T> getMatchingAndDownstream(
Set<T> allNodes,
SetMultimap<T, T> dependencyGraph,
Predicate<T> matcher) {
return getMatchingAndUpstream(allNodes, reverseGraph(dependencyGraph), matcher);
}
public static <T> SetMultimap<T, T> reverseGraph(SetMultimap<T, T> graph) {
return Multimaps.invertFrom(graph, HashMultimap.<T, T>create());
}
/**
* Given a dependency graph, return a topologically sorted
* ordering of its components, stratified in a way that allows
* for recursion and cycles. (Each set in the list is one
* unordered "stratum" of elements. Elements may depend on elements
* in earlier strata or the same stratum, but not in later
* strata.)
*
* If there are no cycles, the result will be a list of singleton
* sets, topologically sorted.
*
* Each key in the given dependency graph depends on/is downstream of
* its associated values.
*/
public static <T> List<Set<T>> toposortSafe(
Set<T> allElements,
Multimap<T, T> dependencyGraph) {
Set<Set<T>> strataToAdd = createAllStrata(allElements);
SetMultimap<Set<T>, Set<T>> strataDependencyGraph = createStrataDependencyGraph(dependencyGraph);
List<Set<T>> ordering = Lists.newArrayList();
while (!strataToAdd.isEmpty()) {
Set<T> curStratum = strataToAdd.iterator().next();
addOrMergeStratumAndAncestors(curStratum, ordering,
strataToAdd, strataDependencyGraph,
Lists.<Set<T>>newArrayList());
}
return ordering;
}
private static <T> void addOrMergeStratumAndAncestors(Set<T> curStratum,
List<Set<T>> ordering, Set<Set<T>> toAdd,
SetMultimap<Set<T>, Set<T>> strataDependencyGraph,
List<Set<T>> downstreamStrata) {
if (downstreamStrata.contains(curStratum)) {
int mergeStartIndex = downstreamStrata.indexOf(curStratum);
List<Set<T>> toMerge = downstreamStrata.subList(mergeStartIndex, downstreamStrata.size());
mergeStrata(Sets.newHashSet(toMerge), toAdd, strataDependencyGraph);
return;
}
downstreamStrata.add(curStratum);
for (Set<T> parent : ImmutableList.copyOf(strataDependencyGraph.get(curStratum))) {
//We could merge away the parent here, so we protect against CMEs and
//make sure the parent is still in toAdd before recursing.
if (toAdd.contains(parent)) {
addOrMergeStratumAndAncestors(parent, ordering, toAdd, strataDependencyGraph, downstreamStrata);
}
}
downstreamStrata.remove(curStratum);
// - If we've added all our parents, we will still be in toAdd
// and none of our dependencies will be in toAdd. Add to the ordering.
// - If there was a merge upstream that we weren't involved in,
// we will still be in toAdd, but we will have (possibly new)
// dependencies that are still in toAdd. Do nothing.
// - If there was a merge upstream that we were involved in,
// we won't be in toAdd anymore. Do nothing.
if (!toAdd.contains(curStratum)) {
return;
}
for (Set<T> parent : strataDependencyGraph.get(curStratum)) {
if (toAdd.contains(parent)) {
return;
}
}
ordering.add(curStratum);
toAdd.remove(curStratum);
}
//Replace the old strata with the new stratum in toAdd and strataDependencyGraph.
private static <T> void mergeStrata(Set<Set<T>> toMerge,
Set<Set<T>> toAdd,
SetMultimap<Set<T>, Set<T>> strataDependencyGraph) {
Set<T> newStratum = ImmutableSet.copyOf(Iterables.concat(toMerge));
for (Set<T> oldStratum : toMerge) {
toAdd.remove(oldStratum);
}
toAdd.add(newStratum);
//Change the keys
for (Set<T> oldStratum : toMerge) {
Collection<Set<T>> parents = strataDependencyGraph.get(oldStratum);
strataDependencyGraph.putAll(newStratum, parents);
strataDependencyGraph.removeAll(oldStratum);
}
//Change the values
for (Entry<Set<T>, Set<T>> entry : ImmutableList.copyOf(strataDependencyGraph.entries())) {
if (toMerge.contains(entry.getValue())) {
strataDependencyGraph.remove(entry.getKey(), entry.getValue());
strataDependencyGraph.put(entry.getKey(), newStratum);
}
}
}
private static <T> Set<Set<T>> createAllStrata(Set<T> allElements) {
Set<Set<T>> result = Sets.newHashSet();
for (T element : allElements) {
result.add(ImmutableSet.of(element));
}
return result;
}
private static <T> SetMultimap<Set<T>, Set<T>> createStrataDependencyGraph(
Multimap<T, T> dependencyGraph) {
SetMultimap<Set<T>, Set<T>> strataDependencyGraph = HashMultimap.create();
for (Entry<T, T> entry : dependencyGraph.entries()) {
strataDependencyGraph.put(ImmutableSet.of(entry.getKey()), ImmutableSet.of(entry.getValue()));
}
return strataDependencyGraph;
}
}