/*
* This file is part of JOP, the Java Optimized Processor
* see <http://www.jopdesign.com/>
*
* Copyright (C) 2011, Stefan Hepp (stefan@stefant.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.jopdesign.common.graphutils;
import org.jgrapht.DirectedGraph;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* This is an implementation of a DFS graph traverser for directed graphs which classifies edges
* and allows usage of an edge filter to create a subgraph view of a graph or to create the graph on the fly,
* and applies a visitor to all reached nodes
*
* This is an implementation of the algorithm found here:
* http://cs.wellesley.edu/~cs231/fall01/dfs.pdf
*
* @author Stefan Hepp (stefan@stefant.org)
*/
public class DFSTraverser<V,E> {
public enum DFSEdgeType {
ROOT, TREE_EDGE, FORWARD_EDGE, CROSS_EDGE, BACK_EDGE;
public boolean isFirstVisit() {
return this == ROOT || this == TREE_EDGE;
}
}
public interface DFSVisitor<V,E> {
/**
* Called before the childs of a node are visited. This method can tell the traverser to skip visiting childs.
* Although this could also be done by the filter by returning an empty outgoing edge set for this node,
* it is sometimes more efficient to let the visitor decide (too) and separates the view of the graph from
* the traversal algorithm.
*
* @param parent parent node of the edge. Null if edge type is ROOT.
* @param edge the edge from parent to the current node. Null if edge type is ROOT.
* @param node the currently visited node. If the edge type is not TREE_EDGE or ROOT, the node has already been
* visited.
* @param type the edge type.
* @param outEdges the outgoing edges of the current node as returned by the EdgeFilter.
* @param depth the number of edges up to the root along the currently processed nodes.
* @return true if the outgoing edges should be visited. If false is returned, the childs of the current node
* might still be visited from another node. If the current node has already been visited, the return
* value is (currently) ignored, as the traverser never descends from visited nodes.
*/
boolean visitNode(V parent, E edge, V node, DFSEdgeType type, Collection<E> outEdges, int depth);
/**
* Called after all childs of a node have been visited from this node.
*
* @param parent parent node of the edge. Null if edge type is ROOT.
* @param edge the edge from parent to the current node. Null if edge type is ROOT.
* @param node the currently visited node. If the edge type is not TREE_EDGE or ROOT, the node has already been
* visited.
* @param type the edge type.
* @param outEdges the outgoing edges of the current node as returned by the EdgeFilter.
* @param depth the number of edges up to the root along the currently processed nodes.
*/
void finishNode(V parent, E edge, V node, DFSEdgeType type, Collection<E> outEdges, int depth);
}
public static class EmptyDFSVisitor<V,E> implements DFSVisitor<V,E> {
@Override
public boolean visitNode(V parent, E edge, V node, DFSEdgeType type, Collection<E> outEdges, int depth) {
if (type.isFirstVisit()) {
// call simplified version of this method
preorder(node);
}
return true;
}
@Override
public void finishNode(V parent, E edge, V node, DFSEdgeType type, Collection<E> outEdges, int depth) {
postorder(node);
}
public void preorder(V node) {
}
public void postorder(V node) {
}
}
private final DFSVisitor<V,E> visitor;
// We use 'not contained' as WHITE, negative value as GREY, positive value as BLACK. Zero is not a valid timestamp.
private final Map<V, Integer> discovery;
private int time;
public DFSTraverser(DFSVisitor<V,E> visitor) {
this.visitor = visitor;
discovery = new LinkedHashMap<V, Integer>();
time = 0;
}
public DFSVisitor<V,E> getVisitor() {
return visitor;
}
public void reset() {
time = 0;
discovery.clear();
}
public void traverse(DirectedGraph<V,E> graph) {
List<V> roots = new ArrayList<V>();
// We could simply start at all nodes with color WHITE (immediatly before starting at that node)
// but we could get a lot of cross edges which are actually forward edges or tree edges.
for (V node : graph.vertexSet()) {
if (graph.inDegreeOf(node) == 0) {
roots.add(node);
}
}
traverse(new DefaultEdgeProvider<V,E>(graph), roots);
}
public void traverse(DirectedGraph<V,E> graph, Collection<V> roots) {
traverse(new DefaultEdgeProvider<V,E>(graph), roots);
}
public void traverse(EdgeProvider<V,E> provider, Collection<V> roots) {
for (V root : roots) {
traverse(provider, root);
}
}
public void traverse(EdgeProvider<V,E> provider, V node) {
// skip visited
if (discovery.containsKey(node)) return;
// start with the node as root..
traverse(provider, null, null, node, 0);
}
private void traverse(EdgeProvider<V,E> provider, V parent, E edge, V node, int depth) {
Integer ts = discovery.get(node);
// get type of edge parent->node
DFSEdgeType type;
if (parent == null) {
type = DFSEdgeType.ROOT;
} else if (ts == null) {
// WHITE
type = DFSEdgeType.TREE_EDGE;
} else if (ts < 0) {
// GREY
type = DFSEdgeType.BACK_EDGE;
} else {
// BLACK; parent node is always either GREY or null
int pts = -discovery.get(parent);
type = pts <= ts ? DFSEdgeType.FORWARD_EDGE : DFSEdgeType.CROSS_EDGE;
}
// Visit the node regardless of type.. We re-visit nodes over BACK/FORWARD/CROSS-edges so that the
// visitor is notified about those edges, but do not descend down
Collection<E> outEdges = provider.outgoingEdgesOf(node);
boolean descend = visitor.visitNode(parent, edge, node, type, outEdges, depth);
if (ts != null) {
// skip non-white nodes
return;
}
time++;
int currTime = time;
if (descend) {
// mark as GREY
discovery.put(node, -currTime);
for (E out : outEdges) {
traverse(provider, node, out, provider.getEdgeTarget(out), depth+1);
}
}
visitor.finishNode(parent, edge, node, type, outEdges, depth);
// mark as BLACK
discovery.put(node, currTime);
time++;
}
}