edu.umd.cs.findbugs.graph.AbstractDepthFirstSearch Maven / Gradle / Ivy
Show all versions of spotbugs Show documentation
/*
* Generic graph library
* Copyright (C) 2003,2004 University of Maryland
*
* 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; either
* version 2.1 of the License, or (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.umd.cs.findbugs.graph;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* Perform a depth first search on a graph. Algorithm based on Cormen, et. al,
* Introduction to Algorithms, p. 478. Currently, this class
*
* - assigns DFS edge types (see {@link DFSEdgeTypes})
*
- assigns discovery and finish times for each vertex
*
- produces a topological sort of the vertices,
* if and only if the graph is acyclic
*
*
*
* Concrete subclasses implement forward and reverse versions of depth first
* search.
*
* @author David Hovemeyer
* @see Graph
* @see DepthFirstSearch
* @see ReverseDepthFirstSearch
*/
public abstract class AbstractDepthFirstSearch, EdgeType extends GraphEdge, VertexType extends GraphVertex>
implements DFSEdgeTypes {
public final static boolean DEBUG = false;
private final GraphType graph;
private final int[] colorList;
private final int[] discoveryTimeList;
private final int[] finishTimeList;
private final int[] dfsEdgeTypeList;
private int timestamp;
private final LinkedList topologicalSortList;
private VertexChooser vertexChooser;
private SearchTreeCallback searchTreeCallback;
/**
* Color of a vertex which hasn't been visited yet.
*/
protected static final int WHITE = 0;
/**
* Color of a vertex which has been visited, but not all of whose
* descendents have been visited.
*/
protected static final int GRAY = 1;
/**
* Color of a vertex whose entire search tree has been visited.
*/
protected static final int BLACK = 2;
/**
* Constructor.
*
* @param graph
* the graph to be searched
* @throws IllegalArgumentException
* if the graph has not had edge ids assigned yet
*/
public AbstractDepthFirstSearch(GraphType graph) {
this.graph = graph;
int numBlocks = graph.getNumVertexLabels();
colorList = new int[numBlocks]; // initially all elements are WHITE
discoveryTimeList = new int[numBlocks];
finishTimeList = new int[numBlocks];
int maxEdgeId = graph.getNumEdgeLabels();
dfsEdgeTypeList = new int[maxEdgeId];
timestamp = 0;
topologicalSortList = new LinkedList<>();
}
// Abstract methods allow the concrete subclass to define
// the "polarity" of the depth first search. That way,
// this code can do normal DFS, or DFS of reversed GraphType.
/**
* Get Iterator over "logical" outgoing edges.
*/
protected abstract Iterator outgoingEdgeIterator(GraphType graph, VertexType vertex);
/**
* Get "logical" target of edge.
*/
protected abstract VertexType getTarget(EdgeType edge);
/**
* Get "logical" source of edge.
*/
protected abstract VertexType getSource(EdgeType edge);
/**
* Choose the next search tree root. By default, this method just scans for
* a WHITE vertex. Subclasses may override this method in order to choose
* which vertices are used as search tree roots.
*
* @return the next search tree root
*/
protected VertexType getNextSearchTreeRoot() {
// FIXME: slow linear search, should improve
for (Iterator i = graph.vertexIterator(); i.hasNext();) {
VertexType vertex = i.next();
if (visitMe(vertex)) {
return vertex;
}
}
return null;
}
public Collection unvisitedVertices() {
LinkedList result = new LinkedList<>();
for (Iterator i = graph.vertexIterator(); i.hasNext();) {
VertexType v = i.next();
if (getColor(v) == WHITE) {
result.add(v);
}
}
return result;
}
/**
* Specify a VertexChooser object to be used to selected which vertices are
* visited by the search. This is useful if you only want to search a subset
* of the vertices in the graph.
*
* @param vertexChooser
* the VertexChooser to use
*/
public void setVertexChooser(VertexChooser vertexChooser) {
this.vertexChooser = vertexChooser;
}
/**
* Set a search tree callback.
*
* @param searchTreeCallback
* the search tree callback
*/
public void setSearchTreeCallback(SearchTreeCallback searchTreeCallback) {
this.searchTreeCallback = searchTreeCallback;
}
/**
* Perform the depth first search.
*
* @return this object
*/
public AbstractDepthFirstSearch search() {
visitAll();
classifyUnknownEdges();
return this;
}
/**
* Return whether or not the graph contains a cycle: i.e., whether it
* contains any back edges. This should only be called after search() has
* been called (since that method actually executes the search).
*
* @return true if the graph contains a cycle, false otherwise
*/
public boolean containsCycle() {
for (Iterator i = graph.edgeIterator(); i.hasNext();) {
EdgeType edge = i.next();
if (getDFSEdgeType(edge) == BACK_EDGE) {
return true;
}
}
return false;
}
/**
* Get the type of edge in the depth first search.
*
* @param edge
* the edge
* @return the DFS type of edge: TREE_EDGE, FORWARD_EDGE, CROSS_EDGE, or
* BACK_EDGE
* @see DFSEdgeTypes
*/
public int getDFSEdgeType(EdgeType edge) {
return dfsEdgeTypeList[edge.getLabel()];
}
/**
* Return the timestamp indicating when the given vertex was discovered.
*
* @param vertex
* the vertex
*/
public int getDiscoveryTime(VertexType vertex) {
return discoveryTimeList[vertex.getLabel()];
}
/**
* Return the timestamp indicating when the given vertex was finished
* (meaning that all of its descendents were visited recursively).
*
* @param vertex
* the vertex
*/
public int getFinishTime(VertexType vertex) {
return finishTimeList[vertex.getLabel()];
}
/**
* Get array of finish times, indexed by vertex label.
*
* @return the array of finish times
*/
@SuppressFBWarnings("EI")
public int[] getFinishTimeList() {
return finishTimeList;
}
/**
* Get an iterator over the vertexs in topological sort order.
* This works if and only if the graph is acyclic.
*/
public Iterator topologicalSortIterator() {
return topologicalSortList.iterator();
}
private class Visit {
private final VertexType vertex;
private final Iterator outgoingEdgeIterator;
public Visit(VertexType vertex) {
if (vertex == null) {
throw new IllegalStateException();
}
this.vertex = vertex;
this.outgoingEdgeIterator = outgoingEdgeIterator(graph, vertex);
// Mark the vertex as visited, and set its timestamp
setColor(vertex, GRAY);
setDiscoveryTime(vertex, timestamp++);
}
public VertexType getVertex() {
return vertex;
}
public boolean hasNextEdge() {
return outgoingEdgeIterator.hasNext();
}
public EdgeType getNextEdge() {
return outgoingEdgeIterator.next();
}
}
private void visitAll() {
for (;;) {
VertexType searchTreeRoot = getNextSearchTreeRoot();
if (searchTreeRoot == null) {
// No more work to do
break;
}
if (searchTreeCallback != null) {
searchTreeCallback.startSearchTree(searchTreeRoot);
}
ArrayList stack = new ArrayList<>(graph.getNumVertexLabels());
stack.add(new Visit(searchTreeRoot));
while (!stack.isEmpty()) {
Visit visit = stack.get(stack.size() - 1);
if (visit.hasNextEdge()) {
// Continue visiting successors
EdgeType edge = visit.getNextEdge();
visitSuccessor(stack, edge);
} else {
// Finish the vertex
VertexType vertex = visit.getVertex();
setColor(vertex, BLACK);
topologicalSortList.addFirst(vertex);
setFinishTime(vertex, timestamp++);
stack.remove(stack.size() - 1);
}
}
}
}
private void visitSuccessor(ArrayList stack, EdgeType edge) {
// Get the successor
VertexType succ = getTarget(edge);
int succColor = getColor(succ);
// Classify edge type (if possible)
int dfsEdgeType = 0;
switch (succColor) {
case WHITE:
dfsEdgeType = TREE_EDGE;
break;
case GRAY:
dfsEdgeType = BACK_EDGE;
break;
case BLACK:
dfsEdgeType = UNKNOWN_EDGE;
break;// We can't distinguish between CROSS and FORWARD edges at
// this point
default:
assert false;
}
setDFSEdgeType(edge, dfsEdgeType);
// If successor hasn't been visited yet, visit it
if (visitMe(succ)) {
// Add to search tree (if a search tree callback exists)
if (searchTreeCallback != null) {
searchTreeCallback.addToSearchTree(getSource(edge), getTarget(edge));
}
// Add to visitation stack
stack.add(new Visit(succ));
}
}
// Classify CROSS and FORWARD edges
private void classifyUnknownEdges() {
Iterator edgeIter = graph.edgeIterator();
while (edgeIter.hasNext()) {
EdgeType edge = edgeIter.next();
int dfsEdgeType = getDFSEdgeType(edge);
if (dfsEdgeType == UNKNOWN_EDGE) {
int srcDiscoveryTime = getDiscoveryTime(getSource(edge));
int destDiscoveryTime = getDiscoveryTime(getTarget(edge));
if (srcDiscoveryTime < destDiscoveryTime) {
// If the source was visited earlier than the
// target, it's a forward edge.
dfsEdgeType = FORWARD_EDGE;
} else {
// If the source was visited later than the
// target, it's a cross edge.
dfsEdgeType = CROSS_EDGE;
}
setDFSEdgeType(edge, dfsEdgeType);
}
}
}
private void setColor(VertexType vertex, int color) {
colorList[vertex.getLabel()] = color;
}
/**
* Get the current color of given vertex.
*
* @param vertex
* the vertex
* @return the color (WHITE, BLACK, or GRAY)
*/
protected int getColor(VertexType vertex) {
return colorList[vertex.getLabel()];
}
/**
* Predicate to determine which vertices should be visited as the search
* progresses. Takes both vertex color and the vertex chooser (if any) into
* account.
*
* @param vertex
* the vertex to possibly be visited
* @return true if the vertex should be visited, false if not
*/
protected boolean visitMe(VertexType vertex) {
return (getColor(vertex) == WHITE) && (vertexChooser == null || vertexChooser.isChosen(vertex));
}
private void setDiscoveryTime(VertexType vertex, int ts) {
discoveryTimeList[vertex.getLabel()] = ts;
}
private void setFinishTime(VertexType vertex, int ts) {
finishTimeList[vertex.getLabel()] = ts;
}
private void setDFSEdgeType(EdgeType edge, int dfsEdgeType) {
dfsEdgeTypeList[edge.getLabel()] = dfsEdgeType;
}
}