package jas.common.helper.sort;
import jas.common.JASLog;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
/**
* Copied from FML TopologicalSort but with FML logging changed to JAS logging
*/
public class TopologicalSort {
public static class DirectedGraph<T> implements Iterable<T> {
private final Map<T, SortedSet<T>> graph = new HashMap<T, SortedSet<T>>();
private List<T> orderedNodes = new ArrayList<T>();
public boolean addNode(T node) {
// Ignore nodes already added
if (graph.containsKey(node)) {
return false;
}
orderedNodes.add(node);
graph.put(node, new TreeSet<T>(new Comparator<T>() {
public int compare(T o1, T o2) {
return orderedNodes.indexOf(o1) - orderedNodes.indexOf(o2);
}
}));
return true;
}
public void addEdge(T from, T to) {
if (!(graph.containsKey(from) && graph.containsKey(to))) {
throw new NoSuchElementException("Missing nodes from graph");
}
graph.get(from).add(to);
}
public void removeEdge(T from, T to) {
if (!(graph.containsKey(from) && graph.containsKey(to))) {
throw new NoSuchElementException("Missing nodes from graph");
}
graph.get(from).remove(to);
}
public boolean edgeExists(T from, T to) {
if (!(graph.containsKey(from) && graph.containsKey(to))) {
throw new NoSuchElementException("Missing nodes from graph");
}
return graph.get(from).contains(to);
}
public Set<T> edgesFrom(T from) {
if (!graph.containsKey(from)) {
throw new NoSuchElementException("Missing node from graph");
}
return Collections.unmodifiableSortedSet(graph.get(from));
}
@Override
public Iterator<T> iterator() {
return orderedNodes.iterator();
}
public int size() {
return graph.size();
}
public boolean isEmpty() {
return graph.isEmpty();
}
@Override
public String toString() {
return graph.toString();
}
}
/**
* Sort the input graph into a topologically sorted list
*
* Uses the reverse depth first search as outlined in ...
*
* @param graph
* @return The sorted mods list.
*/
public static <T> List<T> topologicalSort(DirectedGraph<T> graph) {
DirectedGraph<T> rGraph = reverse(graph);
List<T> sortedResult = new ArrayList<T>();
Set<T> visitedNodes = new HashSet<T>();
// A list of "fully explored" nodes. Leftovers in here indicate cycles in the graph
Set<T> expandedNodes = new HashSet<T>();
for (T node : rGraph) {
explore(node, rGraph, sortedResult, visitedNodes, expandedNodes);
}
return sortedResult;
}
public static <T> DirectedGraph<T> reverse(DirectedGraph<T> graph) {
DirectedGraph<T> result = new DirectedGraph<T>();
for (T node : graph) {
result.addNode(node);
}
for (T from : graph) {
for (T to : graph.edgesFrom(from)) {
result.addEdge(to, from);
}
}
return result;
}
public static <T> void explore(T node, DirectedGraph<T> graph, List<T> sortedResult, Set<T> visitedNodes,
Set<T> expandedNodes) {
// Have we been here before?
if (visitedNodes.contains(node)) {
// And have completed this node before
if (expandedNodes.contains(node)) {
// Then we're fine
return;
}
JASLog.log().severe("Topological Sorting failed.");
JASLog.log().severe("Visting node %s", node);
JASLog.log().severe("Current sorted list : %s", sortedResult);
JASLog.log().severe("Visited set for this node : %s", visitedNodes);
JASLog.log().severe("Explored node set : %s", expandedNodes);
SetView<T> cycleList = Sets.difference(visitedNodes, expandedNodes);
JASLog.log().severe("Likely cycle is in : %s", cycleList);
throw new TopologicalSortingException(
"There was a cycle detected in the input graph, sorting is not possible", node, cycleList);
}
// Visit this node
visitedNodes.add(node);
// Recursively explore inbound edges
for (T inbound : graph.edgesFrom(node)) {
explore(inbound, graph, sortedResult, visitedNodes, expandedNodes);
}
// Add ourselves now
sortedResult.add(node);
// And mark ourselves as explored
expandedNodes.add(node);
}
}