13
0
geforkt von Mirrors/Velocity

Clean up and comment PluginDependencyUtils#sortCandidates

Dieser Commit ist enthalten in:
Andrew Steinborn 2021-10-31 18:56:13 -04:00
Ursprung cb8781b3c9
Commit 895eb1a424

Datei anzeigen

@ -30,6 +30,7 @@ import java.util.Deque;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
public class PluginDependencyUtils { public class PluginDependencyUtils {
@ -38,7 +39,7 @@ public class PluginDependencyUtils {
} }
/** /**
* Attempts to topographically sort all plugins for the proxy to load by dependencies using * Attempts to topographically sort all plugins for the proxy to load in dependency order using
* a depth-first search. * a depth-first search.
* *
* @param candidates the plugins to sort * @param candidates the plugins to sort
@ -71,7 +72,10 @@ public class PluginDependencyUtils {
} }
} }
// Now we do the depth-first search. // Now we do the depth-first search. The most accessible description of the algorithm is on
// Wikipedia: https://en.wikipedia.org/w/index.php?title=Topological_sorting&oldid=1036420482,
// section "Depth-first search." Apparently this algorithm originates from "Introduction to
// Algorithms" (2nd ed.)
List<PluginDescription> sorted = new ArrayList<>(); List<PluginDescription> sorted = new ArrayList<>();
Map<PluginDescription, Mark> marks = new HashMap<>(); Map<PluginDescription, Mark> marks = new HashMap<>();
@ -82,38 +86,41 @@ public class PluginDependencyUtils {
return sorted; return sorted;
} }
private static void visitNode(Graph<PluginDescription> dependencyGraph, PluginDescription node, private static void visitNode(Graph<PluginDescription> dependencyGraph, PluginDescription current,
Map<PluginDescription, Mark> marks, List<PluginDescription> sorted, Map<PluginDescription, Mark> visited, List<PluginDescription> sorted,
Deque<PluginDescription> currentIteration) { Deque<PluginDescription> currentDependencyScanStack) {
Mark mark = marks.getOrDefault(node, Mark.NOT_VISITED); Mark mark = visited.getOrDefault(current, Mark.NOT_VISITED);
if (mark == Mark.PERMANENT) { if (mark == Mark.VISITED) {
// Visited this node already, nothing to do.
return; return;
} else if (mark == Mark.TEMPORARY) { } else if (mark == Mark.VISITING) {
// A circular dependency has been detected. // A circular dependency has been detected. (Specifically, if we are visiting any dependency
currentIteration.addLast(node); // and a dependency we are looking at depends on any dependency being visited, we have a
StringBuilder loopGraph = new StringBuilder(); // circular dependency, thus we do not have a directed acyclic graph and therefore no
for (PluginDescription description : currentIteration) { // topological sort is possible.)
loopGraph.append(description.getId()); currentDependencyScanStack.addLast(current);
loopGraph.append(" -> "); final String loop = currentDependencyScanStack.stream().map(PluginDescription::getId)
} .collect(Collectors.joining(" -> "));
loopGraph.setLength(loopGraph.length() - 4); throw new IllegalStateException("Circular dependency detected: " + loop);
throw new IllegalStateException("Circular dependency detected: " + loopGraph.toString());
} }
currentIteration.addLast(node); // Visiting this node. Mark this node as having a visit in progress and scan its edges.
marks.put(node, Mark.TEMPORARY); currentDependencyScanStack.addLast(current);
for (PluginDescription edge : dependencyGraph.successors(node)) { visited.put(current, Mark.VISITING);
visitNode(dependencyGraph, edge, marks, sorted, currentIteration); for (PluginDescription edge : dependencyGraph.successors(current)) {
visitNode(dependencyGraph, edge, visited, sorted, currentDependencyScanStack);
} }
marks.put(node, Mark.PERMANENT); // All other dependency nodes were visited. We are clear to mark as visited and add to the
currentIteration.removeLast(); // sorted list.
sorted.add(node); visited.put(current, Mark.VISITED);
currentDependencyScanStack.removeLast();
sorted.add(current);
} }
private enum Mark { private enum Mark {
NOT_VISITED, NOT_VISITED,
TEMPORARY, VISITING,
PERMANENT VISITED
} }
} }