org.eclipse.jetty.util.TopologicalSort Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
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.
* A Topological sort is used when you have a partial ordering expressed as
* dependencies between elements (also often represented as edges in a directed
* acyclic graph). A Topological sort should not be used when you have a total
* ordering expressed as a {@link Comparator} over the items. The algorithm has
* the additional characteristic that dependency sets are sorted by the original
* list order so that order is preserved when possible.
*
* The sort algorithm works by recursively visiting every item, once and
* only once. On each visit, the items dependencies are first visited and then the
* item is added to the sorted list. Thus the algorithm ensures that dependency
* items are always added before dependent items.
*
* @param The type to be sorted. It must be able to be added to a {@link HashSet}
*/
public class TopologicalSort
{
// IMPORTANT NOTE: This class cannot use Logging, as this class is used by jetty-start
private final Map> _dependencies = new HashMap<>();
/**
* Add a dependency to be considered in the sort.
*
* @param dependent The dependent item will be sorted after all its dependencies
* @param dependency The dependency item, will be sorted before its dependent item
*/
public void addDependency(T dependent, T... dependency)
{
Set set = _dependencies.get(dependent);
if (set == null)
{
set = new HashSet<>();
_dependencies.put(dependent, set);
}
for (T d : dependency)
{
set.add(d);
}
}
/**
* An alternative to {@link #addDependency(Object, Object[])}, which is
* equivalent to addDependency(after,before) as the after item is dependent
* of the before item.
*
* @param before The item will be sorted before the after
* @param after The item will be sorted after the before
*/
public void addBeforeAfter(T before, T after)
{
addDependency(after, before);
}
/**
* Sort the passed array according to dependencies previously set with
* {@link #addDependency(Object, Object[])}. Where possible, ordering will be
* preserved if no dependency
*
* @param array The array to be sorted.
*/
public void sort(T[] array)
{
List sorted = new ArrayList<>();
Set visited = new HashSet<>();
Comparator comparator = new InitialOrderComparator<>(array);
// Visit all items in the array
for (T t : array)
{
visit(t, visited, sorted, comparator);
}
sorted.toArray(array);
}
/**
* Sort the passed list according to dependencies previously set with
* {@link #addDependency(Object, Object[])}. Where possible, ordering will be
* preserved if no dependency
*
* @param list The list to be sorted.
*/
public void sort(Collection list)
{
List sorted = new ArrayList<>();
Set visited = new HashSet<>();
Comparator comparator = new InitialOrderComparator<>(list);
// Visit all items in the list
for (T t : list)
{
visit(t, visited, sorted, comparator);
}
list.clear();
list.addAll(sorted);
}
/**
* Visit an item to be sorted.
*
* @param item The item to be visited
* @param visited The Set of items already visited
* @param sorted The list to sort items into
* @param comparator A comparator used to sort dependencies.
*/
private void visit(T item, Set visited, List sorted, Comparator comparator)
{
// If the item has not been visited
if (!visited.contains(item))
{
// We are visiting it now, so add it to the visited set
visited.add(item);
// Lookup the items dependencies
Set dependencies = _dependencies.get(item);
if (dependencies != null)
{
// Sort the dependencies
SortedSet orderedDeps = new TreeSet<>(comparator);
orderedDeps.addAll(dependencies);
// recursively visit each dependency
try
{
for (T d : orderedDeps)
{
visit(d, visited, sorted, comparator);
}
}
catch (CyclicException e)
{
throw new CyclicException(item, e);
}
}
// Now that we have visited all our dependencies, they and their
// dependencies will have been added to the sorted list. So we can
// now add the current item and it will be after its dependencies
sorted.add(item);
}
else if (!sorted.contains(item))
// If we have already visited an item, but it has not yet been put in the
// sorted list, then we must be in a cycle!
throw new CyclicException(item);
}
/**
* A comparator that is used to sort dependencies in the order they
* were in the original list. This ensures that dependencies are visited
* in the original order and no needless reordering takes place.
*/
private static class InitialOrderComparator implements Comparator
{
private final Map _indexes = new HashMap<>();
InitialOrderComparator(T[] initial)
{
int i = 0;
for (T t : initial)
{
_indexes.put(t, i++);
}
}
InitialOrderComparator(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 < i2)
return -1;
return 1;
}
}
@Override
public String toString()
{
return "TopologicalSort " + _dependencies;
}
private static class CyclicException extends IllegalStateException
{
CyclicException(Object item)
{
super("cyclic at " + item);
}
CyclicException(Object item, CyclicException e)
{
super("cyclic at " + item, e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy