org.jgrapht.experimental.dag.DirectedAcyclicGraph Maven / Gradle / Ivy
/* ==========================================
* JGraphT : a free Java graph-theory library
* ==========================================
*
* Project Info: http://jgrapht.sourceforge.net/
* Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh)
*
* (C) Copyright 2003-2008, by Barak Naveh and Contributors.
*
* This program and the accompanying materials are dual-licensed under
* either
*
* (a) the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation, or (at your option) any
* later version.
*
* or (per the licensee's choosing)
*
* (b) the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation.
*/
/* -------------------
* DirectedAcyclicGraph.java
* -------------------
* (C) Copyright 2008-2008, by Peter Giles and Contributors.
*
* Original Author: Peter Giles
* Contributor(s): John V. Sichi
*
* $Id$
*
* Changes
* -------
* 17-Mar-2008 : Initial revision (PG);
* 23-Aug-2008 : Added VisitedBitSetImpl and made it the default (JVS);
*
*/
package org.jgrapht.experimental.dag;
import java.io.*;
import java.util.*;
import org.jgrapht.*;
import org.jgrapht.graph.*;
/**
* DirectedAcyclicGraph implements a DAG that can be modified (vertices &
* edges added and removed), is guaranteed to remain acyclic, and provides fast
* topological order iteration.
*
* This is done using a dynamic topological sort which is based on the
* algorithm PK described in "D. Pearce & P. Kelly, 2007: A Dynamic
* Topological Sort Algorithm for Directed Acyclic Graphs", (see Paper or ACM link for details).
*
*
* The implementation differs from the algorithm specified in the above paper
* in some ways, perhaps most notably in that the topological ordering is stored
* by default using two HashMaps, which will have some effects on runtime, but
* also allows for vertex addition and removal, and other operations which are
* helpful for manipulating or combining DAGs. This storage mechanism is
* pluggable for subclassers.
*
* This class makes no claims to thread safety, and concurrent usage from
* multiple threads will produce undefined results.
*
* @author Peter Giles, [email protected]
*/
public class DirectedAcyclicGraph
extends SimpleDirectedGraph
{
private static final long serialVersionUID = 4522128427004938150L;
private TopoComparator topoComparator;
private TopoOrderMapping topoOrderMap;
private int maxTopoIndex = 0;
private int minTopoIndex = 0;
// this update count is used to keep internal topological iterators honest
private long topologyUpdateCount = 0;
/**
* Pluggable VisitedFactory implementation
*/
private VisitedFactory visitedFactory = new VisitedBitSetImpl();
/**
* Pluggable TopoOrderMappingFactory implementation
*/
private TopoOrderMappingFactory topoOrderFactory = new TopoVertexBiMap();
public DirectedAcyclicGraph(Class extends E> arg0)
{
super(arg0);
initialize();
}
public DirectedAcyclicGraph(EdgeFactory ef)
{
super(ef);
initialize();
}
DirectedAcyclicGraph(
Class extends E> arg0,
VisitedFactory visitedFactory,
TopoOrderMappingFactory topoOrderFactory)
{
super(arg0);
if (visitedFactory != null) {
this.visitedFactory = visitedFactory;
}
if (topoOrderFactory != null) {
this.topoOrderFactory = topoOrderFactory;
}
initialize();
}
/**
* set the topoOrderMap based on the current factory, and create the
* comparator;
*/
private void initialize()
{
topoOrderMap = topoOrderFactory.getTopoOrderMapping();
topoComparator = new TopoComparator(topoOrderMap);
}
/**
* iterator will traverse the vertices in topological order, meaning that
* for a directed graph G = (V,E), if there exists a path from vertex va to
* vertex vb then va is guaranteed to come before vertex vb in the iteration
* order.
*
* @return an iterator that will traverse the graph in topological order
*/
public Iterator iterator()
{
return new TopoIterator();
}
/**
* adds the vertex if it wasn't already in the graph, and puts it at the top
* of the internal topological vertex ordering
*/
@Override public boolean addVertex(V v)
{
boolean added = super.addVertex(v);
if (added) {
// add to the top
++maxTopoIndex;
topoOrderMap.putVertex(maxTopoIndex, v);
++topologyUpdateCount;
}
return added;
}
/**
* adds the vertex if it wasn't already in the graph, and puts it either at
* the top or the bottom of the topological ordering, depending on the value
* of addToTop. This may provide useful optimizations for merging
* DirectedAcyclicGraphs that become connected.
*
* @param v
* @param addToTop
*
* @return whether new vertex was added
*/
public boolean addVertex(V v, boolean addToTop)
{
boolean added = super.addVertex(v);
if (added) {
int insertIndex;
// add to the top
if (addToTop) {
insertIndex = ++maxTopoIndex;
} else {
insertIndex = --minTopoIndex;
}
topoOrderMap.putVertex(insertIndex, v);
++topologyUpdateCount;
}
return added;
}
/**
* Adds the given edge and updates the internal topological order for
* consistency IFF
*
*
* - there is not already an edge (fromVertex, toVertex) in the graph
*
- the edge does not induce a cycle in the graph
*
*
* @return null if the edge is already in the graph, else the created edge
* is returned
*
* @throws IllegalArgumentException If either fromVertex or toVertex is not
* a member of the graph
* @throws CycleFoundException if the edge would induce a cycle in the graph
*
* @see Graph#addEdge(Object, Object, Object)
*/
public E addDagEdge(V fromVertex, V toVertex)
throws CycleFoundException
{
updateDag(fromVertex, toVertex);
return super.addEdge(fromVertex, toVertex);
}
/**
* identical to {@link #addDagEdge(Object, Object)}, except an unchecked
* {@link IllegalArgumentException} is thrown if a cycle would have been
* induced by this edge
*/
@Override public E addEdge(V sourceVertex, V targetVertex)
{
E result = null;
try {
result = addDagEdge(sourceVertex, targetVertex);
} catch (CycleFoundException e) {
throw new IllegalArgumentException(e);
}
return result;
}
/**
* Adds the given edge and updates the internal topological order for
* consistency IFF
*
*
* - the given edge is not already a member of the graph
*
- there is not already an edge (fromVertex, toVertex) in the graph
*
- the edge does not induce a cycle in the graph
*
*
* @return true if the edge was added to the graph
*
* @throws CycleFoundException if adding an edge (fromVertex, toVertex) to
* the graph would induce a cycle.
*
* @see Graph#addEdge(Object, Object, Object)
*/
public boolean addDagEdge(V fromVertex, V toVertex, E e)
throws CycleFoundException
{
if (e == null) {
throw new NullPointerException();
} else if (containsEdge(e)) {
return false;
}
updateDag(fromVertex, toVertex);
return super.addEdge(fromVertex, toVertex, e);
}
private void updateDag(V fromVertex, V toVertex)
throws CycleFoundException
{
Integer lb = topoOrderMap.getTopologicalIndex(toVertex);
Integer ub = topoOrderMap.getTopologicalIndex(fromVertex);
if ((lb == null) || (ub == null)) {
throw new IllegalArgumentException(
"vertices must be in the graph already!");
}
if (lb < ub) {
Set df = new HashSet();
Set db = new HashSet();
// Discovery
Region affectedRegion = new Region(lb, ub);
Visited visited = visitedFactory.getInstance(affectedRegion);
// throws CycleFoundException if there is a cycle
dfsF(toVertex, df, visited, affectedRegion);
dfsB(fromVertex, db, visited, affectedRegion);
reorder(df, db, visited);
++topologyUpdateCount; // if we do a reorder, than the topology has
// been updated
}
}
/**
* identical to {@link #addDagEdge(Object, Object, Object)}, except an
* unchecked {@link IllegalArgumentException} is thrown if a cycle would
* have been induced by this edge
*/
@Override public boolean addEdge(V sourceVertex, V targetVertex, E edge)
{
boolean result;
try {
result = addDagEdge(sourceVertex, targetVertex, edge);
} catch (CycleFoundException e) {
throw new IllegalArgumentException(e);
}
return result;
}
// note that this can leave holes in the topological ordering, which
// (depending on the TopoOrderMap implementation) can degrade performance
// for certain operations over time
@Override public boolean removeVertex(V v)
{
boolean removed = super.removeVertex(v);
if (removed) {
Integer topoIndex = topoOrderMap.removeVertex(v);
// contract minTopoIndex as we are able
if (topoIndex == minTopoIndex) {
while (
(minTopoIndex < 0)
&& (null == topoOrderMap.getVertex(minTopoIndex)))
{
++minTopoIndex;
}
}
// contract maxTopoIndex as we are able
if (topoIndex == maxTopoIndex) {
while (
(maxTopoIndex > 0)
&& (null == topoOrderMap.getVertex(maxTopoIndex)))
{
--maxTopoIndex;
}
}
++topologyUpdateCount;
}
return removed;
}
@Override public boolean removeAllVertices(Collection extends V> arg0)
{
boolean removed = super.removeAllVertices(arg0);
topoOrderMap.removeAllVertices();
maxTopoIndex = 0;
minTopoIndex = 0;
++topologyUpdateCount;
return removed;
}
/**
* Depth first search forward, building up the set (df) of forward-connected
* vertices in the Affected Region
*
* @param vertex the vertex being visited
* @param df the set we are populating with forward connected vertices in
* the Affected Region
* @param visited a simple data structure that lets us know if we already
* visited a node with a given topo index
* @param topoIndexMap for quick lookups, a map from vertex to topo index in
* the AR
* @param ub the topo index of the original fromVertex -- used for cycle
* detection
*
* @throws CycleFoundException if a cycle is discovered
*/
private void dfsF(
V vertex,
Set df,
Visited visited,
Region affectedRegion)
throws CycleFoundException
{
int topoIndex = topoOrderMap.getTopologicalIndex(vertex);
// Assumption: vertex is in the AR and so it will be in visited
visited.setVisited(topoIndex);
df.add(vertex);
for (E outEdge : outgoingEdgesOf(vertex)) {
V nextVertex = getEdgeTarget(outEdge);
Integer nextVertexTopoIndex =
topoOrderMap.getTopologicalIndex(nextVertex);
if (nextVertexTopoIndex.intValue() == affectedRegion.finish) {
// reset visited
try {
for (V visitedVertex : df) {
visited.clearVisited(
topoOrderMap.getTopologicalIndex(visitedVertex));
}
} catch (UnsupportedOperationException e) {
// okay, fine, some implementations (ones that automatically
// clear themselves out) don't work this way
}
throw new CycleFoundException();
}
// note, order of checks is important as we need to make sure the
// vertex is in the affected region before we check its visited
// status (otherwise we will be causing an
// ArrayIndexOutOfBoundsException).
if (affectedRegion.isIn(nextVertexTopoIndex)
&& !visited.getVisited(nextVertexTopoIndex))
{
dfsF(nextVertex, df, visited, affectedRegion); // recurse
}
}
}
/**
* Depth first search backward, building up the set (db) of back-connected
* vertices in the Affected Region
*
* @param vertex the vertex being visited
* @param db the set we are populating with back-connected vertices in the
* AR
* @param visited
* @param topoIndexMap
*/
private void dfsB(
V vertex,
Set db,
Visited visited,
Region affectedRegion)
{
// Assumption: vertex is in the AR and so we will get a topoIndex from
// the map
int topoIndex = topoOrderMap.getTopologicalIndex(vertex);
visited.setVisited(topoIndex);
db.add(vertex);
for (E inEdge : incomingEdgesOf(vertex)) {
V previousVertex = getEdgeSource(inEdge);
Integer previousVertexTopoIndex =
topoOrderMap.getTopologicalIndex(previousVertex);
// note, order of checks is important as we need to make sure the
// vertex is in the affected region before we check its visited
// status (otherwise we will be causing an
// ArrayIndexOutOfBoundsException).
if (affectedRegion.isIn(previousVertexTopoIndex)
&& !visited.getVisited(previousVertexTopoIndex))
{
// if prevousVertexTopoIndex != null, the vertex is in the
// Affected Region according to our topoIndexMap
dfsB(previousVertex, db, visited, affectedRegion);
}
}
}
@SuppressWarnings("unchecked")
private void reorder(Set df, Set db, Visited visited)
{
List topoDf = new ArrayList(df);
List topoDb = new ArrayList(db);
Collections.sort(topoDf, topoComparator);
Collections.sort(topoDb, topoComparator);
// merge these suckers together in topo order
SortedSet availableTopoIndices = new TreeSet();
// we have to cast to the generic type, can't do "new V[size]" in java
// 5;
V [] bigL = (V []) new Object[df.size() + db.size()];
int lIndex = 0; // this index is used for the sole purpose of pushing
// into
// the correct index of bigL
// assume (for now) that we are resetting visited
boolean clearVisited = true;
for (V vertex : topoDb) {
Integer topoIndex = topoOrderMap.getTopologicalIndex(vertex);
// add the available indices to the set
availableTopoIndices.add(topoIndex);
bigL[lIndex++] = vertex;
if (clearVisited) { // reset visited status if supported
try {
visited.clearVisited(topoIndex);
} catch (UnsupportedOperationException e) {
clearVisited = false;
}
}
}
for (V vertex : topoDf) {
Integer topoIndex = topoOrderMap.getTopologicalIndex(vertex);
// add the available indices to the set
availableTopoIndices.add(topoIndex);
bigL[lIndex++] = vertex;
if (clearVisited) { // reset visited status if supported
try {
visited.clearVisited(topoIndex);
} catch (UnsupportedOperationException e) {
clearVisited = false;
}
}
}
lIndex = 0; // reusing lIndex
for (Integer topoIndex : availableTopoIndices) {
// assign the indexes to the elements of bigL in order
V vertex = bigL[lIndex++]; // note the post-increment
topoOrderMap.putVertex(topoIndex, vertex);
}
}
/**
* For performance tuning, an interface for storing the topological ordering
*
* @author gilesp
*/
public interface TopoOrderMapping
extends Serializable
{
/**
* add a vertex at the given topological index.
*
* @param index
* @param vertex
*/
public void putVertex(Integer index, V vertex);
/**
* get the vertex at the given topological index.
*
* @param index
*
* @return vertex
*/
public V getVertex(Integer index);
/**
* get the topological index of the given vertex.
*
* @param vertex
*
* @return the index that the vertex is at, or null if the vertex isn't
* in the topological ordering
*/
public Integer getTopologicalIndex(V vertex);
/**
* remove the given vertex from the topological ordering
*
* @param vertex
*
* @return the index that the vertex was at, or null if the vertex
* wasn't in the topological ordering
*/
public Integer removeVertex(V vertex);
/**
* remove all vertices from the topological ordering
*/
public void removeAllVertices();
}
public interface TopoOrderMappingFactory
{
public TopoOrderMapping getTopoOrderMapping();
}
/**
* this interface allows specification of a strategy for marking vertices as
* visited (based on their topological index, so the vertex type isn't part
* of the interface).
*/
public interface Visited
{
/**
* mark the given topological index as visited
*
* @param index the topological index
*/
public void setVisited(int index);
/**
* has the given topological index been visited?
*
* @param index the topological index
*/
public boolean getVisited(int index);
/**
* Clear the visited state of the given topological index
*
* @param index
*
* @throws UnsupportedOperationException if the implementation doesn't
* support (or doesn't need) clearance. For example, if the factory
* vends a new instance every time, it is a waste of cycles to clear the
* state after the search of the Affected Region is done, so an
* UnsupportedOperationException *should* be thrown.
*/
public void clearVisited(int index)
throws UnsupportedOperationException;
}
/**
* interface for a factory that vends Visited implementations
*
* @author gilesp
*/
public interface VisitedFactory
extends Serializable
{
public Visited getInstance(Region affectedRegion);
}
/**
* Note, this is a lazy and incomplete implementation, with assumptions that
* inputs are in the given topoIndexMap
*
* @param
*
* @author gilesp
*/
private static class TopoComparator
implements Comparator,
Serializable
{
/**
*/
private static final long serialVersionUID = 1L;
private TopoOrderMapping topoOrderMap;
public TopoComparator(TopoOrderMapping topoOrderMap)
{
this.topoOrderMap = topoOrderMap;
}
@Override public int compare(V o1, V o2)
{
return topoOrderMap.getTopologicalIndex(o1).compareTo(
topoOrderMap.getTopologicalIndex(o2));
}
}
/**
* a dual HashMap implementation
*
* @author gilesp
*/
private class TopoVertexBiMap
implements TopoOrderMapping,
TopoOrderMappingFactory
{
/**
*/
private static final long serialVersionUID = 1L;
private final Map topoToVertex = new HashMap();
private final Map vertexToTopo = new HashMap();
@Override public void putVertex(Integer index, V vertex)
{
topoToVertex.put(index, vertex);
vertexToTopo.put(vertex, index);
}
@Override public V getVertex(Integer index)
{
return topoToVertex.get(index);
}
@Override public Integer getTopologicalIndex(V vertex)
{
Integer topoIndex = vertexToTopo.get(vertex);
return topoIndex;
}
@Override public Integer removeVertex(V vertex)
{
Integer topoIndex = vertexToTopo.remove(vertex);
if (topoIndex != null) {
topoToVertex.remove(topoIndex);
}
return topoIndex;
}
@Override public void removeAllVertices()
{
vertexToTopo.clear();
topoToVertex.clear();
}
@Override public TopoOrderMapping getTopoOrderMapping()
{
return this;
}
}
/**
* For performance and flexibility uses an ArrayList for topological index
* to vertex mapping, and a HashMap for vertex to topological index mapping.
*
* @author gilesp
*/
public class TopoVertexMap
implements TopoOrderMapping,
TopoOrderMappingFactory
{
/**
*/
private static final long serialVersionUID = 1L;
private final List topoToVertex = new ArrayList();
private final Map vertexToTopo = new HashMap();
@Override public void putVertex(Integer index, V vertex)
{
int translatedIndex = translateIndex(index);
// grow topoToVertex as needed to accommodate elements
while ((translatedIndex + 1) > topoToVertex.size()) {
topoToVertex.add(null);
}
topoToVertex.set(translatedIndex, vertex);
vertexToTopo.put(vertex, index);
}
@Override public V getVertex(Integer index)
{
return topoToVertex.get(translateIndex(index));
}
@Override public Integer getTopologicalIndex(V vertex)
{
return vertexToTopo.get(vertex);
}
@Override public Integer removeVertex(V vertex)
{
Integer topoIndex = vertexToTopo.remove(vertex);
if (topoIndex != null) {
topoToVertex.set(translateIndex(topoIndex), null);
}
return topoIndex;
}
@Override public void removeAllVertices()
{
vertexToTopo.clear();
topoToVertex.clear();
}
@Override public TopoOrderMapping getTopoOrderMapping()
{
return this;
}
/**
* We translate the topological index to an ArrayList index. We have to
* do this because topological indices can be negative, and we want to
* do it because we can make better use of space by only needing an
* ArrayList of size |AR|.
*
* @param unscaledIndex
*
* @return the ArrayList index
*/
private final int translateIndex(int index)
{
if (index >= 0) {
return 2 * index;
}
return -1 * ((index * 2) - 1);
}
}
/**
* Region is an *inclusive* range of indices. Esthetically displeasing, but
* convenient for our purposes.
*
* @author gilesp
*/
public static class Region
implements Serializable
{
/**
*/
private static final long serialVersionUID = 1L;
public final int start;
public final int finish;
public Region(int start, int finish)
{
if (start > finish) {
throw new IllegalArgumentException(
"(start > finish): invariant broken");
}
this.start = start;
this.finish = finish;
}
public int getSize()
{
return (finish - start) + 1;
}
public boolean isIn(int index)
{
return (index >= start) && (index <= finish);
}
}
/**
* This implementation is close to the performance of VisitedArrayListImpl,
* with 1/8 the memory usage.
*
* @author perfecthash
*/
public static class VisitedBitSetImpl
implements Visited,
VisitedFactory
{
/**
*/
private static final long serialVersionUID = 1L;
private final BitSet visited = new BitSet();
private Region affectedRegion;
@Override public Visited getInstance(Region affectedRegion)
{
this.affectedRegion = affectedRegion;
return this;
}
@Override public void setVisited(int index)
{
visited.set(translateIndex(index), true);
}
@Override public boolean getVisited(int index)
{
return visited.get(translateIndex(index));
}
@Override public void clearVisited(int index)
throws UnsupportedOperationException
{
visited.clear(translateIndex(index));
}
/**
* We translate the topological index to an ArrayList index. We have to
* do this because topological indices can be negative, and we want to
* do it because we can make better use of space by only needing an
* ArrayList of size |AR|.
*
* @param unscaledIndex
*
* @return the ArrayList index
*/
private int translateIndex(int index)
{
return index - affectedRegion.start;
}
}
/**
* This implementation seems to offer the best performance in most cases. It
* grows the internal ArrayList as needed to be as large as |AR|, so it will
* be more memory intensive than the HashSet implementation, and unlike the
* Array implementation, it will hold on to that memory (it expands, but
* never contracts).
*
* @author gilesp
*/
public static class VisitedArrayListImpl
implements Visited,
VisitedFactory
{
/**
*/
private static final long serialVersionUID = 1L;
private final List visited = new ArrayList();
private Region affectedRegion;
@Override public Visited getInstance(Region affectedRegion)
{
// Make sure visited is big enough
int minSize = (affectedRegion.finish - affectedRegion.start) + 1;
/* plus one because the region range is inclusive of both indices */
while (visited.size() < minSize) {
visited.add(Boolean.FALSE);
}
this.affectedRegion = affectedRegion;
return this;
}
@Override public void setVisited(int index)
{
visited.set(translateIndex(index), Boolean.TRUE);
}
@Override public boolean getVisited(int index)
{
Boolean result = null;
result = visited.get(translateIndex(index));
return result;
}
@Override public void clearVisited(int index)
throws UnsupportedOperationException
{
visited.set(translateIndex(index), Boolean.FALSE);
}
/**
* We translate the topological index to an ArrayList index. We have to
* do this because topological indices can be negative, and we want to
* do it because we can make better use of space by only needing an
* ArrayList of size |AR|.
*
* @param unscaledIndex
*
* @return the ArrayList index
*/
private int translateIndex(int index)
{
return index - affectedRegion.start;
}
}
/**
* This implementation doesn't seem to perform as well, though I can imagine
* circumstances where it should shine (lots and lots of vertices). It also
* should have the lowest memory footprint as it only uses storage for
* indices that have been visited.
*
* @author gilesp
*/
public static class VisitedHashSetImpl
implements Visited,
VisitedFactory
{
/**
*/
private static final long serialVersionUID = 1L;
private final Set visited = new HashSet();
@Override public Visited getInstance(Region affectedRegion)
{
visited.clear();
return this;
}
@Override public void setVisited(int index)
{
visited.add(index);
}
@Override public boolean getVisited(int index)
{
return visited.contains(index);
}
@Override public void clearVisited(int index)
throws UnsupportedOperationException
{
throw new UnsupportedOperationException();
}
}
/**
* This implementation, somewhat to my surprise, is slower than the
* ArrayList version, probably due to its reallocation of the underlying
* array for every topology reorder that is required.
*
* @author gilesp
*/
public static class VisitedArrayImpl
implements Visited,
VisitedFactory
{
/**
*/
private static final long serialVersionUID = 1L;
private final boolean [] visited;
private final Region region;
/**
* Constructs empty factory instance
*/
public VisitedArrayImpl()
{
this(null);
}
public VisitedArrayImpl(Region region)
{
if (region == null) { // make empty instance
this.visited = null;
this.region = null;
} else { // fill in the needed pieces
this.region = region;
// initialized to all false by default
visited = new boolean[region.getSize()];
}
}
@Override public Visited getInstance(Region affectedRegion)
{
return new VisitedArrayImpl(affectedRegion);
}
@Override public void setVisited(int index)
{
try {
visited[index - region.start] = true;
} catch (ArrayIndexOutOfBoundsException e) {
/*
log.error("Visited set operation out of region boundaries", e);
*/
throw e;
}
}
@Override public boolean getVisited(int index)
{
try {
return visited[index - region.start];
} catch (ArrayIndexOutOfBoundsException e) {
/*
log.error("Visited set operation out of region boundaries", e);
*/
throw e;
}
}
@Override public void clearVisited(int index)
throws UnsupportedOperationException
{
throw new UnsupportedOperationException();
}
}
/**
* Exception used in dfsF when a cycle is found
*
* @author gilesp
*/
public static class CycleFoundException
extends Exception
{
private static final long serialVersionUID = 5583471522212552754L;
}
/**
* iterator which follows topological order
*
* @author gilesp
*/
private class TopoIterator
implements Iterator
{
private int currentTopoIndex;
private final long updateCountAtCreation;
private Integer nextIndex = null;
public TopoIterator()
{
updateCountAtCreation = topologyUpdateCount;
currentTopoIndex = minTopoIndex - 1;
}
@Override public boolean hasNext()
{
if (updateCountAtCreation != topologyUpdateCount) {
throw new ConcurrentModificationException();
}
nextIndex = getNextIndex();
return nextIndex != null;
}
@Override public V next()
{
if (updateCountAtCreation != topologyUpdateCount) {
throw new ConcurrentModificationException();
}
if (nextIndex == null) {
// find nextIndex
nextIndex = getNextIndex();
}
if (nextIndex == null) {
throw new NoSuchElementException();
}
currentTopoIndex = nextIndex;
nextIndex = null;
return topoOrderMap.getVertex(currentTopoIndex); //topoToVertex.get(currentTopoIndex);
}
@Override public void remove()
{
if (updateCountAtCreation != topologyUpdateCount) {
throw new ConcurrentModificationException();
}
V vertexToRemove = null;
if (null
!= (vertexToRemove =
topoOrderMap.getVertex(
currentTopoIndex)))
{
topoOrderMap.removeVertex(vertexToRemove);
} else {
// should only happen if next() hasn't been called
throw new IllegalStateException();
}
}
private Integer getNextIndex()
{
for (int i = currentTopoIndex + 1; i <= maxTopoIndex; i++) {
if (null != topoOrderMap.getVertex(i)) {
return i;
}
}
return null;
}
}
}
// End DirectedAcyclicGraph.java