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); } }