/*
* This file is part of JOP, the Java Optimized Processor
* see <http://www.jopdesign.com/>
*
* Copyright (C) 2008, Benedikt Huber (benedikt.huber@gmail.com)
*
* 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 com.jopdesign.common.misc.BadGraphException;
import org.jgrapht.DirectedGraph;
import org.jgrapht.alg.BellmanFordShortestPath;
import org.jgrapht.alg.ConnectivityInspector;
import org.jgrapht.graph.DirectedSubgraph;
import org.jgrapht.graph.EdgeReversedGraph;
import org.jgrapht.traverse.DepthFirstIterator;
import org.jgrapht.traverse.TopologicalOrderIterator;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/**
* Given a rooted, directed graph, identify back-edges.
* A back-edge is an edge <code>B -> A</code> s.t. A dominates B w.r.t to Entry.
* If removing the back-edges leads to a DAG, compute a topological order using that graph.
*
* @param <V> node type
* @param <E> edge type
*/
public class TopOrder<V, E> {
private DirectedGraph<V, E> graph;
private List<V> dfsOrder = null;
private List<E> backEdges = null;
private V startVertex;
private Dominators<V, E> dominators;
private List<V> topTraversal;
/* Iterator detecting back edges using DFS search.
* This works for reducible graphs only.
*/
private class BackEdgeDetector extends DepthFirstIterator<V, E> {
public BackEdgeDetector(DirectedGraph<V, E> g, V startVertex) {
super(g, startVertex);
}
// Topological order
@Override
protected void encounterVertex(V vertex, E edge) {
super.encounterVertex(vertex, edge);
dfsOrder.add(vertex);
}
@Override
protected void encounterVertexAgain(V vertex, E edge) {
super.encounterVertexAgain(vertex, edge);
if (getSeenData(vertex) != VisitColor.GRAY) {
} else {
backEdges.add(edge);
}
}
}
public TopOrder(DirectedGraph<V, E> graph, V startVertex) throws BadGraphException {
this.graph = graph;
this.startVertex = startVertex;
analyse(false);
}
public TopOrder(DirectedGraph<V, E> graph, V startVertex, boolean isAcyclic) throws BadGraphException {
this.graph = graph;
this.startVertex = startVertex;
analyse(isAcyclic);
}
private void analyse(boolean isAcyclic) throws BadGraphException {
backEdges = new ArrayList<E>();
dfsOrder = new ArrayList<V>();
BackEdgeDetector iter = new BackEdgeDetector(graph, startVertex);
while (iter.hasNext()) iter.next();
if (isAcyclic && !backEdges.isEmpty()) {
E e1 = backEdges.get(0);
List<E> cycle = BellmanFordShortestPath.findPathBetween(graph, graph.getEdgeTarget(e1), graph.getEdgeSource(e1));
throw new BadGraphException("Expected acyclic graph, but found cycle: " + cycle);
}
this.dominators = new Dominators<V, E>(this.graph, dfsOrder);
checkReducible();
}
/**
* An edge B->A is a back-edge if A dominates B w.r.t. <code>Entry</code>.
*
* @return the back-edges of this graph
*/
public List<E> getBackEdges() {
return backEdges;
}
/**
* Return a traversal of the graph in topological order of the corresponding back-edge
* free graph.
*
* @return The traversal as list of nodes
*/
public List<V> getTopologicalTraversal() {
if (topTraversal != null) return topTraversal;
topTraversal = new LinkedList<V>();
Set<E> edgeSet = new LinkedHashSet<E>(graph.edgeSet());
for (E backEdge : this.getBackEdges()) {
edgeSet.remove(backEdge);
}
DirectedSubgraph<V, E> subgraph =
new DirectedSubgraph<V, E>(graph, graph.vertexSet(), edgeSet);
TopologicalOrderIterator<V, E> iter =
new TopologicalOrderIterator<V, E>((DirectedGraph<V, E>) subgraph);
while (iter.hasNext()) {
topTraversal.add(iter.next());
}
return topTraversal;
}
/**
* Get a DFS traversal of the graph
*
* @return
*/
public List<V> getDFSTraversal() {
return dfsOrder;
}
/**
* @return the {@link Dominators} of the graph
*/
public Dominators<V, E> getDominators() {
return this.dominators;
}
/* Reducability condition: For every backedge (n,h), h dominates n
* @throws Exception if the graph isn't reducible
*/
private void checkReducible() throws BadGraphException {
for (E backEdge : getBackEdges()) {
V n = graph.getEdgeSource(backEdge);
V hol = graph.getEdgeTarget(backEdge);
if (!dominators.dominates(hol, n)) {
throw new BadGraphException(hol + " should dominate " + n);
}
}
}
/**
* Get the connected components of the graph
*
* @param graph
* @return
*/
public static <V, E> List<Set<V>> getComponents(DirectedGraph<V, E> graph) {
return new ConnectivityInspector<V, E>(graph).connectedSets();
}
/**
* Check wheter the given graph is (weakly) connected.
* <p> This is the case if
* <code>|weakly connected components| = 1</code>
* </p>
*
* @param graph
* @throws BadGraphException if the graph is empty, or there is more than one weakly connected component
*/
public static <V, E> void checkConnected(DirectedGraph<V, E> graph)
throws BadGraphException {
List<Set<V>> comps = getComponents(graph);
if (comps.size() != 1) {
throw new BadGraphException("Expected graph with one component, but the given one has " + comps);
}
}
/**
* Find nodes which aren't reachable from <code>entry</code>.
* <p> A nodes is unreachable with respect to <code>entry</code>, if there is no path
* from <code>entry</code> to <code>n</code>.
* </p>
*
* @param graph the given graph
* @param entry the entry node
* @return a list of unreachable nodes
*/
public static <V, E> Set<V> findDeadNodes(DirectedGraph<V, E> graph, V entry) {
/* CAVEAT: Do not use ConnectivityInspector; it considers graphs as undirected */
BellmanFordShortestPath<V, E> bfsp = new BellmanFordShortestPath<V, E>(graph, entry);
Set<V> deads = new LinkedHashSet<V>();
for (V node : graph.vertexSet()) {
if (node == entry) continue;
if (bfsp.getPathEdgeList(node) == null) {
deads.add(node);
}
}
return deads;
}
/**
* Find nodes which which have no path to exit.
*
* @param graph the given graph
* @param exit the exit node
* @return a list of stuck nodes
*/
public static <V, E> Set<V> findStuckNodes(DirectedGraph<V, E> graph, V exit) {
/* CAVEAT: Do not use ConnectivityInspector; it considers graphs as undirected */
BellmanFordShortestPath<V, E> bfspRev = new BellmanFordShortestPath<V, E>(
new EdgeReversedGraph<V, E>(graph),
exit);
Set<V> stucks = new LinkedHashSet<V>();
for (V node : graph.vertexSet()) {
if (node == exit) continue;
if (bfspRev.getPathEdgeList(node) == null) {
stucks.add(node);
}
}
return stucks;
}
/**
* Check that the given graph is a flowgraph:
* <ul>
* <li/> There is a path from the entry to every nodes, and entry dominates all nodes
* <li/> There is a path from every node to exit, and exit postdominates all nodes
* </ul>
*
* @param graph the graph to check
* @param entry the entry node
* @param exit the exit node
* @throws BadGraphException if the graph is not a flow graph
*/
public static <V, E> void checkIsFlowGraph(DirectedGraph<V, E> graph, V entry, V exit)
throws BadGraphException {
Set<V> deads = findDeadNodes(graph, entry);
Set<V> stucks = findStuckNodes(graph, exit);
if (!deads.isEmpty()) {
throw new BadGraphException("checkIsFlowGraph: There is no path from entry to " + deads);
}
if (!stucks.isEmpty()) {
throw new BadGraphException("checkIsFlowGraph: There is no path to exit from " + stucks);
}
Dominators<V, E> doms = new Dominators<V, E>(graph, entry);
Dominators<V, E> rdoms = new Dominators<V, E>(new EdgeReversedGraph<V, E>(graph), exit);
for (V node : graph.vertexSet()) {
if (!doms.dominates(entry, node)) {
throw new BadGraphException("checkIsFlowGraph: Entry does not dominate " + node);
}
if (!rdoms.dominates(exit, node)) {
throw new BadGraphException("checkIsFlowGraph: Exit does not postdominate " + node);
}
}
}
}