001 /*
002 * The FML Forge Mod Loader suite.
003 * Copyright (C) 2012 cpw
004 *
005 * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
006 * Software Foundation; either version 2.1 of the License, or any later version.
007 *
008 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
009 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
010 *
011 * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51
012 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
013 */
014 package cpw.mods.fml.common.toposort;
015
016 import java.util.ArrayList;
017 import java.util.Collections;
018 import java.util.Comparator;
019 import java.util.HashMap;
020 import java.util.HashSet;
021 import java.util.Iterator;
022 import java.util.List;
023 import java.util.Map;
024 import java.util.NoSuchElementException;
025 import java.util.Set;
026 import java.util.SortedSet;
027 import java.util.TreeSet;
028
029 /**
030 * Topological sort for mod loading
031 *
032 * Based on a variety of sources, including http://keithschwarz.com/interesting/code/?dir=topological-sort
033 * @author cpw
034 *
035 */
036 public class TopologicalSort
037 {
038 public static class DirectedGraph<T> implements Iterable<T>
039 {
040 private final Map<T, SortedSet<T>> graph = new HashMap<T, SortedSet<T>>();
041 private List<T> orderedNodes = new ArrayList<T>();
042
043 public boolean addNode(T node)
044 {
045 // Ignore nodes already added
046 if (graph.containsKey(node))
047 {
048 return false;
049 }
050
051 orderedNodes.add(node);
052 graph.put(node, new TreeSet<T>(new Comparator<T>()
053 {
054 public int compare(T o1, T o2) {
055 return orderedNodes.indexOf(o1)-orderedNodes.indexOf(o2);
056 }
057 }));
058 return true;
059 }
060
061 public void addEdge(T from, T to)
062 {
063 if (!(graph.containsKey(from) && graph.containsKey(to)))
064 {
065 throw new NoSuchElementException("Missing nodes from graph");
066 }
067
068 graph.get(from).add(to);
069 }
070
071 public void removeEdge(T from, T to)
072 {
073 if (!(graph.containsKey(from) && graph.containsKey(to)))
074 {
075 throw new NoSuchElementException("Missing nodes from graph");
076 }
077
078 graph.get(from).remove(to);
079 }
080
081 public boolean edgeExists(T from, T to)
082 {
083 if (!(graph.containsKey(from) && graph.containsKey(to)))
084 {
085 throw new NoSuchElementException("Missing nodes from graph");
086 }
087
088 return graph.get(from).contains(to);
089 }
090
091 public Set<T> edgesFrom(T from)
092 {
093 if (!graph.containsKey(from))
094 {
095 throw new NoSuchElementException("Missing node from graph");
096 }
097
098 return Collections.unmodifiableSortedSet(graph.get(from));
099 }
100 @Override
101 public Iterator<T> iterator()
102 {
103 return orderedNodes.iterator();
104 }
105
106 public int size()
107 {
108 return graph.size();
109 }
110
111 public boolean isEmpty()
112 {
113 return graph.isEmpty();
114 }
115
116 @Override
117 public String toString()
118 {
119 return graph.toString();
120 }
121 }
122
123 /**
124 * Sort the input graph into a topologically sorted list
125 *
126 * Uses the reverse depth first search as outlined in ...
127 * @param graph
128 * @return The sorted mods list.
129 */
130 public static <T> List<T> topologicalSort(DirectedGraph<T> graph)
131 {
132 DirectedGraph<T> rGraph = reverse(graph);
133 List<T> sortedResult = new ArrayList<T>();
134 Set<T> visitedNodes = new HashSet<T>();
135 // A list of "fully explored" nodes. Leftovers in here indicate cycles in the graph
136 Set<T> expandedNodes = new HashSet<T>();
137
138 for (T node : rGraph)
139 {
140 explore(node, rGraph, sortedResult, visitedNodes, expandedNodes);
141 }
142
143 return sortedResult;
144 }
145
146 public static <T> DirectedGraph<T> reverse(DirectedGraph<T> graph)
147 {
148 DirectedGraph<T> result = new DirectedGraph<T>();
149
150 for (T node : graph)
151 {
152 result.addNode(node);
153 }
154
155 for (T from : graph)
156 {
157 for (T to : graph.edgesFrom(from))
158 {
159 result.addEdge(to, from);
160 }
161 }
162
163 return result;
164 }
165
166 public static <T> void explore(T node, DirectedGraph<T> graph, List<T> sortedResult, Set<T> visitedNodes, Set<T> expandedNodes)
167 {
168 // Have we been here before?
169 if (visitedNodes.contains(node))
170 {
171 // And have completed this node before
172 if (expandedNodes.contains(node))
173 {
174 // Then we're fine
175 return;
176 }
177
178 System.out.printf("%s: %s\n%s\n%s\n", node, sortedResult, visitedNodes, expandedNodes);
179 throw new ModSortingException("There was a cycle detected in the input graph, sorting is not possible", node, visitedNodes);
180 }
181
182 // Visit this node
183 visitedNodes.add(node);
184
185 // Recursively explore inbound edges
186 for (T inbound : graph.edgesFrom(node))
187 {
188 explore(inbound, graph, sortedResult, visitedNodes, expandedNodes);
189 }
190
191 // Add ourselves now
192 sortedResult.add(node);
193 // And mark ourselves as explored
194 expandedNodes.add(node);
195 }
196 }