/** * Copyright 2011 meltmedia * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.xchain.framework.util; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Iterator; import java.util.SortedSet; import java.util.TreeSet; /** * @author Christian Trimble * @author Jason Rose * @author Devon Tackett */ public class DependencySorter<T> { /** The map of labels to vertices for this graph. */ protected LinkedHashMap<T, Vertex<T>> vertexMap = new LinkedHashMap<T, Vertex<T>>(); protected Comparator<T> elementComparator = null; public DependencySorter( Comparator<T> elementComparator ) { this.elementComparator = elementComparator; } /** * Adds a label to the dependency graph if it is not already present. */ public void add( T label ) { getOrAddVertex( label ); } /** * Retrieve the vertex for the given label. If the label did not already have a vertex, this will * create a new vertex for the label. * * @param label The label to use. * @return The vertex for the given label. */ protected Vertex<T> getOrAddVertex( T label ) { Vertex<T> vertex = vertexMap.get(label); if( vertex == null ) { vertex = new Vertex<T>(label); vertexMap.put(label, vertex); } return vertex; } /** * Adds a dependency from label to dependencyLabel. If there is not a vertex for either label in the dependency * graph, then one is added. */ public void addDependency( T label, T dependencyLabel ) { addEdge(getOrAddVertex(label), getOrAddVertex(dependencyLabel)); } /** * Internal implementation of adding an edge to the graph. */ protected void addEdge( Vertex<T> out, Vertex<T> in ) { out.getOutSet().add(in); in.getInSet().add(out); } /** * The implementation of a vertex in the graph. It holds a set of outbound edges and inbound edges. */ protected static class Vertex<T> { /** The label for this vertex.*/ protected T label; /** The set of outbound dependencies for this vertex (things that depend on this vertex.) */ protected Set<Vertex<T>> outSet = new HashSet<Vertex<T>>(); /** The set of inbound dependencies for this vertex (things this vertex depends on.) */ protected Set<Vertex<T>> inSet = new HashSet<Vertex<T>>(); /** Creates a new vertex for the given label. */ protected Vertex(T label) { this.label = label; } /** * Returns the label for this vertex. */ public T getLabel() { return label; } public int hashCode() { return label.hashCode(); } public boolean equals( Object o ) { if( o instanceof Vertex ) { return ((Vertex)o).getLabel().equals(label); } return false; } /** * Returns the set of out bound dependencies. */ public Set<Vertex<T>> getOutSet() { return outSet; } /** * Returns the set of inbound dependencies. */ public Set<Vertex<T>> getInSet() { return inSet; } } protected static class VertexComparator<T> implements Comparator<Vertex<T>> { protected Comparator<T> elementComparator; public VertexComparator(Comparator<T> elementComparator) { this.elementComparator = elementComparator; } public Comparator<T> getLabelComparator() { return this.elementComparator; } public int compare( Vertex<T> v1, Vertex<T> v2 ) { return elementComparator.compare(v1.getLabel(), v2.getLabel()); } public boolean equals( Object o ) { if( !(o instanceof VertexComparator) ) { return false; } return elementComparator.equals(((VertexComparator)o).getLabelComparator()); } } public List<T> sort() throws DependencyCycleException { // create a set of all the List<T> sorted = new ArrayList<T>(); LinkedList<Vertex<T>> vertexQueue = new LinkedList<Vertex<T>>(); SortedSet<Vertex<T>> deterministicSet = new TreeSet<Vertex<T>>(new VertexComparator(elementComparator)); // seed the vertex queue with all vertexes that do not have any incoming edges. Iterator<Map.Entry<T,Vertex<T>>> vertexIterator = vertexMap.entrySet().iterator(); while( vertexIterator.hasNext() ) { Map.Entry<T, Vertex<T>> vertexEntry = vertexIterator.next(); if( vertexEntry.getValue().getInSet().isEmpty()) { vertexQueue.add(vertexEntry.getValue()); } } // while there are items in the queue. while( !vertexQueue.isEmpty() ) { // empty the queue and do a sort of its contents. This will give a determanistic order to the elements // that otherwise would come in a non deterministic order. deterministicSet.addAll(vertexQueue); vertexQueue.clear(); for( Vertex<T> current : deterministicSet ) { sorted.add(current.getLabel()); Iterator<Vertex<T>> outIterator = current.getOutSet().iterator(); while( outIterator.hasNext() ) { Vertex<T> out = outIterator.next(); // remove the current vertex from the out list of the current node. outIterator.remove(); // remove the other side of the relationship. out.getInSet().remove(current); // if there are no more in nodes for this out node, then remove it from the graph. if( out.getInSet().isEmpty() ) { vertexQueue.add(out); } } //remove the current node from the vertex map. vertexMap.remove(current.getLabel()); } // clean up the deterministic set. deterministicSet.clear(); } if( !vertexMap.isEmpty() ) { // There are entries left in the vertex map. A circular dependency must exist. Map<T, Set<T>> cycle = new HashMap<T, Set<T>>(); // minimize the nodes to just the cycles by remove all the dependency leaves. vertexQueue.clear(); for( Map.Entry<T, Vertex<T>> entry : vertexMap.entrySet() ) { if( entry.getValue().getOutSet().isEmpty() ) { vertexQueue.add(entry.getValue()); } } while( !vertexQueue.isEmpty() ) { Vertex<T> current = vertexQueue.removeFirst(); Iterator<Vertex<T>> inIterator = current.getInSet().iterator(); while( inIterator.hasNext() ) { Vertex<T> in = inIterator.next(); inIterator.remove(); in.getOutSet().remove(current); if( in.getOutSet().isEmpty() ) { vertexQueue.add(in); } } vertexMap.remove(current.getLabel()); } // map add the remaining entries into the cycle mapping and clear the vertex map. vertexIterator = vertexMap.entrySet().iterator(); while( vertexIterator.hasNext() ) { Map.Entry<T, Vertex<T>> entry = vertexIterator.next(); Set<T> outLabelSet = new HashSet<T>(); for( Vertex<T> out : entry.getValue().getOutSet() ) { outLabelSet.add(out.getLabel()); } cycle.put(entry.getKey(), outLabelSet); vertexIterator.remove(); } throw new DependencyCycleException("The following nodes have a cyclic dependency: ", cycle); } return sorted; } }