diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/TopologicalSort.java b/jetty-util/src/main/java/org/eclipse/jetty/util/TopologicalSort.java new file mode 100644 index 00000000000..9d2a78b416d --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/TopologicalSort.java @@ -0,0 +1,110 @@ +package org.eclipse.jetty.util; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + + + +/** + * Topological Sort a list or array. + *

The algorithm has the additional characteristic that dependency sets are + * sorted by the original list order so that order is preserved when possible + * @param The type to be sorted. + */ +public class TopologicalSort +{ + private final Map> _dependencies = new HashMap<>(); + + public void addDependency(T dependent, T dependency) + { + Set set = _dependencies.get(dependent); + if (set==null) + { + set=new HashSet<>(); + _dependencies.put(dependent,set); + } + set.add(dependency); + } + + public void sort(T[] list) + { + List sorted = new ArrayList<>(); + Set visited = new HashSet<>(); + Comparator comparator = new InitialOrderComparitor<>(list); + for (T t : list) + visit(t,visited,sorted,comparator); + + sorted.toArray(list); + } + + public void sort(Collection list) + { + List sorted = new ArrayList<>(); + Set visited = new HashSet<>(); + Comparator comparator = new InitialOrderComparitor<>(list); + + for (T t : list) + visit(t,visited,sorted,comparator); + + list.clear(); + list.addAll(sorted); + } + + private void visit(T t, Set visited, List sorted,Comparator comparator) + { + if( !visited.contains(t) ) + { + visited.add( t ); + + Set dependencies = _dependencies.get(t); + if (dependencies!=null) + { + SortedSet ordered_deps = new TreeSet<>(comparator); + ordered_deps.addAll(dependencies); + for (T d:ordered_deps) + visit(d,visited,sorted,comparator); + } + sorted.add(t); + } + else if (!sorted.contains(t)) + throw new IllegalStateException("cyclic"); + } + + private static class InitialOrderComparitor implements Comparator + { + private final Map _indexes = new HashMap<>(); + InitialOrderComparitor(T[] initial) + { + int i=0; + for (T t : initial) + _indexes.put(t,i++); + } + + InitialOrderComparitor(Collection initial) + { + int i=0; + for (T t : initial) + _indexes.put(t,i++); + } + + @Override + public int compare(T o1, T o2) + { + Integer i1=_indexes.get(o1); + Integer i2=_indexes.get(o2); + if (i1==null || i2==null || i1.equals(o2)) + return 0; + if (i1 ts = new TopologicalSort<>(); + ts.sort(s); + + Assert.assertThat(s,Matchers.arrayContaining("D","E","C","B","A")); + } + + @Test + public void testSimpleLinear() + { + String[] s = { "D","E","C","B","A" }; + TopologicalSort ts = new TopologicalSort<>(); + ts.addDependency("B","A"); + ts.addDependency("C","B"); + ts.addDependency("D","C"); + ts.addDependency("E","D"); + + ts.sort(s); + + Assert.assertThat(s,Matchers.arrayContaining("A","B","C","D","E")); + } + + @Test + public void testDisjoint() + { + String[] s = { "A","C","B","CC","AA","BB"}; + + TopologicalSort ts = new TopologicalSort<>(); + ts.addDependency("B","A"); + ts.addDependency("C","B"); + ts.addDependency("BB","AA"); + ts.addDependency("CC","BB"); + + ts.sort(s); + + Assert.assertThat(s,Matchers.arrayContaining("A","B","C","AA","BB","CC")); + } + + @Test + public void testDisjointReversed() + { + String[] s = { "CC","AA","BB","A","C","B"}; + + TopologicalSort ts = new TopologicalSort<>(); + ts.addDependency("B","A"); + ts.addDependency("C","B"); + ts.addDependency("BB","AA"); + ts.addDependency("CC","BB"); + + ts.sort(s); + + Assert.assertThat(s,Matchers.arrayContaining("AA","BB","CC","A","B","C")); + } + + @Test + public void testDisjointMixed() + { + String[] s = { "CC","A","AA","C","BB","B"}; + + TopologicalSort ts = new TopologicalSort<>(); + ts.addDependency("B","A"); + ts.addDependency("C","B"); + ts.addDependency("BB","AA"); + ts.addDependency("CC","BB"); + + ts.sort(s); + + // Check direct ordering + Assert.assertThat(indexOf(s,"A"),lessThan(indexOf(s,"B"))); + Assert.assertThat(indexOf(s,"B"),lessThan(indexOf(s,"C"))); + Assert.assertThat(indexOf(s,"AA"),lessThan(indexOf(s,"BB"))); + Assert.assertThat(indexOf(s,"BB"),lessThan(indexOf(s,"CC"))); + } + + @Test + public void testTree() + { + String[] s = { "LeafA0","LeafB0","LeafA1","Root","BranchA","LeafB1","BranchB"}; + + TopologicalSort ts = new TopologicalSort<>(); + ts.addDependency("BranchB","Root"); + ts.addDependency("BranchA","Root"); + ts.addDependency("LeafA1","BranchA"); + ts.addDependency("LeafA0","BranchA"); + ts.addDependency("LeafB0","BranchB"); + ts.addDependency("LeafB1","BranchB"); + + ts.sort(s); + + // Check direct ordering + Assert.assertThat(indexOf(s,"Root"),lessThan(indexOf(s,"BranchA"))); + Assert.assertThat(indexOf(s,"Root"),lessThan(indexOf(s,"BranchB"))); + Assert.assertThat(indexOf(s,"BranchA"),lessThan(indexOf(s,"LeafA0"))); + Assert.assertThat(indexOf(s,"BranchA"),lessThan(indexOf(s,"LeafA1"))); + Assert.assertThat(indexOf(s,"BranchB"),lessThan(indexOf(s,"LeafB0"))); + Assert.assertThat(indexOf(s,"BranchB"),lessThan(indexOf(s,"LeafB1"))); + + // check remnant ordering of original list + Assert.assertThat(indexOf(s,"BranchA"),lessThan(indexOf(s,"BranchB"))); + Assert.assertThat(indexOf(s,"LeafA0"),lessThan(indexOf(s,"LeafA1"))); + Assert.assertThat(indexOf(s,"LeafB0"),lessThan(indexOf(s,"LeafB1"))); + } + + @Test + public void testPreserveOrder() + { + String[] s = { "Deep","Foobar","Wibble","Bozo","XXX","12345","Test"}; + + TopologicalSort ts = new TopologicalSort<>(); + ts.addDependency("Deep","Test"); + ts.addDependency("Deep","Wibble"); + ts.addDependency("Deep","12345"); + ts.addDependency("Deep","XXX"); + ts.addDependency("Deep","Foobar"); + ts.addDependency("Deep","Bozo"); + + ts.sort(s); + Assert.assertThat(s,Matchers.arrayContaining("Foobar","Wibble","Bozo","XXX","12345","Test","Deep")); + } + + @Test + public void testSimpleLoop() + { + String[] s = { "A","B","C","D","E" }; + TopologicalSort ts = new TopologicalSort<>(); + ts.addDependency("B","A"); + ts.addDependency("A","B"); + + try + { + ts.sort(s); + Assert.fail(); + } + catch(IllegalStateException e) + { + Assert.assertThat(e.getMessage(),Matchers.containsString("cyclic")); + } + } + + @Test + public void testDeepLoop() + { + String[] s = { "A","B","C","D","E" }; + TopologicalSort ts = new TopologicalSort<>(); + ts.addDependency("B","A"); + ts.addDependency("C","B"); + ts.addDependency("D","C"); + ts.addDependency("E","D"); + ts.addDependency("A","E"); + try + { + ts.sort(s); + Assert.fail(); + } + catch(IllegalStateException e) + { + Assert.assertThat(e.getMessage(),Matchers.containsString("cyclic")); + } + } + + private int indexOf(String[] list,String s) + { + for (int i=0;i