276 lines
8.2 KiB
Java
276 lines
8.2 KiB
Java
/*
|
|
* Minecraft Forge
|
|
* Copyright (c) 2016-2018.
|
|
*
|
|
* 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 Software Foundation version 2.1
|
|
* of the License.
|
|
*
|
|
* 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 A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* 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 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
package net.minecraftforge.fml.loading.toposort;
|
|
|
|
import com.google.common.collect.Sets;
|
|
import net.minecraftforge.fml.loading.EarlyLoadingException;
|
|
import org.apache.logging.log4j.message.Message;
|
|
import org.apache.logging.log4j.util.StringBuilderFormattable;
|
|
|
|
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 java.util.function.Function;
|
|
import java.util.stream.Collectors;
|
|
|
|
/**
|
|
* Topological sort for mod loading
|
|
*
|
|
* Based on a variety of sources, including http://keithschwarz.com/interesting/code/?dir=topological-sort
|
|
* @author cpw
|
|
*
|
|
*/
|
|
public class TopologicalSort
|
|
{
|
|
public static class DirectedGraph<T> implements Iterable<T>
|
|
{
|
|
private final Map<T, SortedSet<T>> graph = new HashMap<>();
|
|
private List<T> orderedNodes = new ArrayList<>();
|
|
|
|
public boolean addNode(T node)
|
|
{
|
|
// Ignore nodes already added
|
|
if (graph.containsKey(node))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
orderedNodes.add(node);
|
|
graph.put(node, new TreeSet<>(Comparator.comparingInt(o -> orderedNodes.indexOf(o))));
|
|
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<>();
|
|
Set<T> visitedNodes = new HashSet<>();
|
|
// A list of "fully explored" nodes. Leftovers in here indicate cycles in the graph
|
|
Set<T> expandedNodes = new HashSet<>();
|
|
|
|
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<>();
|
|
|
|
for (T node : graph)
|
|
{
|
|
result.addNode(node);
|
|
}
|
|
|
|
for (T from : graph)
|
|
{
|
|
for (T to : graph.edgesFrom(from))
|
|
{
|
|
result.addEdge(to, from);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private 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;
|
|
}
|
|
|
|
throw new TopoSortException(new TopoSortException.TopoSortExceptionData<>(node, sortedResult, visitedNodes, expandedNodes));
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
public static class TopoSortException extends RuntimeException {
|
|
private final TopoSortExceptionData<?> topoSortData;
|
|
|
|
public static class TopoSortExceptionData<T> implements Message, StringBuilderFormattable
|
|
{
|
|
private final List<T> sortedResult;
|
|
private final Set<T> visitedNodes;
|
|
private final Set<T> expandedNodes;
|
|
private final T node;
|
|
|
|
TopoSortExceptionData(T node, List<T> sortedResult, Set<T> visitedNodes, Set<T> expandedNodes)
|
|
{
|
|
this.node = node;
|
|
this.sortedResult = sortedResult;
|
|
this.visitedNodes = visitedNodes;
|
|
this.expandedNodes = expandedNodes;
|
|
}
|
|
|
|
@Override
|
|
public String getFormattedMessage()
|
|
{
|
|
return "";
|
|
}
|
|
|
|
@Override
|
|
public String getFormat()
|
|
{
|
|
return "";
|
|
}
|
|
|
|
@Override
|
|
public Object[] getParameters()
|
|
{
|
|
return new Object[0];
|
|
}
|
|
|
|
@Override
|
|
public Throwable getThrowable()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void formatTo(StringBuilder buffer)
|
|
{
|
|
buffer.append("Mod Sorting failed.\n");
|
|
buffer.append("Visiting node {}\n").append(String.valueOf(node));
|
|
buffer.append("Current sorted list : {}\n").append(String.valueOf(sortedResult));
|
|
buffer.append("Visited set for this node : {}\n").append(String.valueOf(visitedNodes));
|
|
buffer.append("Explored node set : {}\n").append(expandedNodes);
|
|
buffer.append("Likely cycle is in : {}\n").append(Sets.difference(visitedNodes, expandedNodes));
|
|
}
|
|
|
|
public List<EarlyLoadingException.ExceptionData> toExceptionData(Function<T, String> nodeMapper) {
|
|
return Collections.singletonList(
|
|
new EarlyLoadingException.ExceptionData("fml.messages.cycleproblem",
|
|
nodeMapper.apply(node),
|
|
visitedNodes.stream().map(nodeMapper).collect(Collectors.joining(","))));
|
|
}
|
|
}
|
|
|
|
public <T> TopoSortException(TopoSortExceptionData<T> data)
|
|
{
|
|
this.topoSortData = data;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
public <T> TopoSortExceptionData<T> getData() {
|
|
return (TopoSortExceptionData<T>)this.topoSortData;
|
|
}
|
|
}
|
|
}
|