/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2016, Open Source Geospatial Foundation (OSGeo) * * 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. */ package org.geotools; import java.util.AbstractSet; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; /** * Set of elements having a partial order, established by setting before/after relationship calling * {@link #setOrder(Object, Object)} * * @author Andrea Aime */ class PartiallyOrderedSet<E> extends AbstractSet<E> { private Map<E, DirectedGraphNode<E>> elementsToNodes = new LinkedHashMap<>(); @Override public boolean add(E e) { if(elementsToNodes.containsKey(e)) { return false; } else { elementsToNodes.put(e, new DirectedGraphNode<E>(e)); return true; } } @Override public boolean remove(Object o) { DirectedGraphNode<E> node = elementsToNodes.remove(o); if(node == null) { return false; } else { node.clear(); return true; } } public void setOrder(E source, E target) { DirectedGraphNode<E> sourceNode = elementsToNodes.get(source); DirectedGraphNode<E> targetNode = elementsToNodes.get(target); if(sourceNode == null) { throw new IllegalArgumentException("Could not find source node in the set: " + source); } if(targetNode == null) { throw new IllegalArgumentException("Could not find target node in the set: " + target); } sourceNode.addOutgoing(targetNode); } public void clearOrder(E source, E target) { DirectedGraphNode<E> sourceNode = elementsToNodes.get(source); DirectedGraphNode<E> targetNode = elementsToNodes.get(target); if(sourceNode == null) { throw new IllegalArgumentException("Could not find source node in the set: " + source); } if(targetNode == null) { throw new IllegalArgumentException("Could not find target node in the set: " + target); } // clear both directions to be sure sourceNode.removeOutgoing(targetNode); targetNode.removeOutgoing(sourceNode); } @Override public Iterator<E> iterator() { return new TopologicalSortIterator(); } @Override public int size() { return elementsToNodes.size(); } /** * A graph node with ingoing and outgoing edges to other nodes * * @param <E> */ class DirectedGraphNode<E> { E element; Set<DirectedGraphNode<E>> outgoings = new HashSet<>(); Set<DirectedGraphNode<E>> ingoings = new HashSet<>(); public DirectedGraphNode(E element) { this.element = element; } /** * Clean up all ingoing and outgoing edges */ public void clear() { outgoings.clear(); ingoings.clear(); } public boolean removeOutgoing(DirectedGraphNode<E> targetNode) { targetNode.ingoings.remove(this); return outgoings.remove(targetNode); } public void addOutgoing(DirectedGraphNode<E> targetNode) { // keep the link between two nodes going in a single direction targetNode.ingoings.add(this); targetNode.outgoings.remove(this); outgoings.add(targetNode); } public Collection<DirectedGraphNode<E>> getOutgoings() { return outgoings; } public E getValue() { return element; } public int getInDegree() { return ingoings.size(); } @Override public String toString() { StringBuilder sb = new StringBuilder("["); sb.append(element); if(outgoings.size() > 0) { sb.append(" -> ("); for (DirectedGraphNode<E> outgoing : outgoings) { sb.append(outgoing.element).append(","); } // remove last comma and close parens sb.setCharAt(sb.length() - 1, ')'); } sb.append("]"); return sb.toString(); } } /** * Simple count down object */ static final class Countdown { int value; public Countdown(int value) { this.value = value; } public int decrement() { return --value; } } /** * An iterator returning elements based on the partial order relationship we have * in the directed nodes, starting from the sources and moving towards the sinks */ class TopologicalSortIterator implements Iterator<E> { // lists of nodes with zero residual inDegrees (aka sources) LinkedList<DirectedGraphNode<E>> sources = new LinkedList<>(); Map<DirectedGraphNode<E>, Countdown> residualInDegrees = new LinkedHashMap<>(); public TopologicalSortIterator() { for (DirectedGraphNode<E> node : elementsToNodes.values()) { int inDegree = node.getInDegree(); if(inDegree == 0) { sources.add(node); } else { residualInDegrees.put(node, new Countdown(inDegree)); } } if(sources.size() == 0) { throwLoopException(); } } private void throwLoopException() { String message = "Some of the partial order relationship form a loop: " + residualInDegrees.keySet(); throw new IllegalStateException(message); } @Override public boolean hasNext() { if(sources.isEmpty() && !residualInDegrees.isEmpty()) { throwLoopException(); } return !sources.isEmpty(); } @Override public E next() { if(!hasNext()) { throw new NoSuchElementException(); } DirectedGraphNode<E> next = sources.removeFirst(); // lower the residual inDegree of all nodes after this one, // creating a new set of sources for (DirectedGraphNode<E> out : next.getOutgoings()) { Countdown countdown = residualInDegrees.get(out); if(countdown.decrement() == 0) { sources.add(out); residualInDegrees.remove(out); } } return next.getValue(); } } }