com.salesforce.jgrapht.experimental.dag.DirectedAcyclicGraph Maven / Gradle / Ivy
Show all versions of AptSpringProcessor Show documentation
/*
* (C) Copyright 2008-2017, by Peter Giles and Contributors.
*
* JGraphT : a free Java graph-theory library
*
* 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.
*/
package com.salesforce.jgrapht.experimental.dag;
import java.io.*;
import java.util.*;
import com.salesforce.jgrapht.*;
import com.salesforce.jgrapht.graph.*;
import com.salesforce.jgrapht.traverse.*;
/**
*
* 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.
*
*
* @param the graph vertex type
* @param the graph edge type
*
* @author Peter Giles, [email protected]
*/
public class DirectedAcyclicGraph
extends SimpleDirectedGraph
implements Iterable
{
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();
/**
* Construct a directed acyclic graph.
*
* @param edgeClass the edge class
*/
public DirectedAcyclicGraph(Class extends E> edgeClass)
{
super(edgeClass);
initialize();
}
/**
* Construct a directed acyclic graph.
*
* @param ef the edge factory
*/
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.
*
* @param v the vertex to add
*/
@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 the vertex to add
* @param addToTop if true the vertex is added at the top of the topological ordering, if false
* at the bottom
*
* @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
*
*
* @param fromVertex from vertex
* @param toVertex to vertex
* @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;
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
*
*
* @param fromVertex the from vertex
* @param toVertex the to vertex
* @param e the edge
* @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)
{
return super.removeAllVertices(arg0);
}
/**
* 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
*
* @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 == 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
*/
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);
}
}
/**
* @param graph graph to look for ancestors in.
* @param vertex the vertex to get the ancestors of.
*
* @return {@link Set} of ancestors of the vertex in the given graph.
*/
public Set getAncestors(DirectedAcyclicGraph graph, V vertex)
{
EdgeReversedGraph reversedGraph = new EdgeReversedGraph<>(graph);
AbstractGraphIterator iterator = new DepthFirstIterator<>(reversedGraph, vertex);
Set ancestors = new HashSet<>();
// Do not add start vertex to result.
if (iterator.hasNext()) {
iterator.next();
}
while (iterator.hasNext()) {
ancestors.add(iterator.next());
}
return ancestors;
}
/**
* @param graph graph to look for descendants in.
* @param vertex the vertex to get the descendants of.
*
* @return {@link Set} of descendants of the vertex in the given graph.
*/
public Set getDescendants(DirectedAcyclicGraph graph, V vertex)
{
AbstractGraphIterator iterator = new DepthFirstIterator<>(graph, vertex);
Set descendants = new HashSet<>();
// Do not add start vertex to result.
if (iterator.hasNext()) {
iterator.next();
}
while (iterator.hasNext()) {
descendants.add(iterator.next());
}
return descendants;
}
/**
* For performance tuning, an interface for storing the topological ordering
*
* @param the graph vertex type
*
* @author Peter Giles
*/
public interface TopoOrderMapping
extends Serializable
{
/**
* Add a vertex at the given topological index.
*
* @param index the topological index
* @param vertex the vertex
*/
void putVertex(Integer index, V vertex);
/**
* Get the vertex at the given topological index.
*
* @param index the topological index
* @return vertex the vertex
*/
V getVertex(Integer index);
/**
* Get the topological index of the given vertex.
*
* @param vertex the vertex
* @return the index that the vertex is at, or null if the vertex isn't in the topological
* ordering
*/
Integer getTopologicalIndex(V vertex);
/**
* Remove the given vertex from the topological ordering
*
* @param vertex the vertex
* @return the index that the vertex was at, or null if the vertex wasn't in the topological
* ordering
*/
Integer removeVertex(V vertex);
/**
* Remove all vertices from the topological ordering
*/
void removeAllVertices();
}
/**
* A factory for {@link TopoOrderMapping}.
*
* @param the graph vertex type
*/
public interface TopoOrderMappingFactory
{
/**
* Create a new instance of a {@link TopoOrderMapping}.
*
* @return a new instance of a {@link TopoOrderMapping}
*/
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
*/
void setVisited(int index);
/**
* Has the given topological index been visited?
*
* @param index the topological index
* @return true if the given topological index been visited, false otherwise
*/
boolean getVisited(int index);
/**
* Clear the visited state of the given topological index
*
* @param index the 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.
*/
void clearVisited(int index)
throws UnsupportedOperationException;
}
/**
* Interface for a factory that vends visited implementations
*
* @author Peter Giles
*/
public interface VisitedFactory
extends Serializable
{
/**
* Create a new instance of {@link Visited}.
*
* @param affectedRegion the affected region
* @return a new instance of {@link Visited} for the affected region
*/
Visited getInstance(Region affectedRegion);
}
/**
* Note, this is a lazy and incomplete implementation, with assumptions that inputs are in the
* given topoIndexMap
*
* @param the graph vertex type
*
* @author Peter Giles
*/
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 Peter Giles
*/
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)
{
return vertexToTopo.get(vertex);
}
@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 Peter Giles
*/
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|.
*
* @return the ArrayList index
*/
private 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 Peter Giles
*/
public static class Region
implements Serializable
{
private static final long serialVersionUID = 1L;
public final int start;
public final int finish;
/**
* Construct a new region.
*
* @param start the start of the region
* @param finish the end of the region (inclusive)
*/
public Region(int start, int finish)
{
if (start > finish) {
throw new IllegalArgumentException("(start > finish): invariant broken");
}
this.start = start;
this.finish = finish;
}
/**
* Get the size of the region.
*
* @return the size of the region
*/
public int getSize()
{
return (finish - start) + 1;
}
/**
* Check if index is in the region.
*
* @param index the index to check
* @return true if the index is in the region, false otherwise
*/
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 John Sichi
*/
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|.
*
* @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 Peter Giles
*/
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)
{
return visited.get(translateIndex(index));
}
@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|.
*
* @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 Peter Giles
*/
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 Peter Giles
*/
public static class VisitedArrayImpl
implements Visited, VisitedFactory
{
private static final long serialVersionUID = 1L;
private final boolean[] visited;
private final Region region;
/**
* Constructs empty instance
*/
public VisitedArrayImpl()
{
this(null);
}
/**
* Construct an empty instance for a region.
*
* @param region the region
*/
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)
{
visited[index - region.start] = true;
}
@Override
public boolean getVisited(int index)
{
return visited[index - region.start];
}
@Override
public void clearVisited(int index)
throws UnsupportedOperationException
{
throw new UnsupportedOperationException();
}
}
/**
* Exception used in dfsF when a cycle is found
*
* @author Peter Giles
*/
public static class CycleFoundException
extends Exception
{
private static final long serialVersionUID = 5583471522212552754L;
}
/**
* iterator which follows topological order
*
* @author Peter Giles
*/
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;
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