package org.panlab.software.fstoolkit.workflowcomposer;
/*
* JBoss, Home of Professional Open Source
* Copyright 2006, Red Hat Middleware LLC, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This 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 software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
/**
* A directed graph data structure.
*
* @author Scott.Stark@jboss.org
* @version $Revision$
* @param <T>
*/
@SuppressWarnings("unchecked")
public class Graph<T> {
/** Color used to mark unvisited nodes */
public static final int VISIT_COLOR_WHITE = 1;
/** Color used to mark nodes as they are first visited in DFS order */
public static final int VISIT_COLOR_GREY = 2;
/** Color used to mark nodes after descendants are completely visited */
public static final int VISIT_COLOR_BLACK = 3;
/** Vector<Vertex> of graph verticies */
private List<Vertex<T>> verticies;
/** Vector<Edge> of edges in the graph */
private List<Edge<T>> edges;
/** The vertex identified as the root of the graph */
private Vertex<T> rootVertex;
/**
* Construct a new graph without any vertices or edges
*/
public Graph() {
verticies = new ArrayList<Vertex<T>>();
edges = new ArrayList<Edge<T>>();
}
/**
* Are there any verticies in the graph
*
* @return true if there are no verticies in the graph
*/
public boolean isEmpty() {
return verticies.size() == 0;
}
/**
* Add a vertex to the graph
*
* @param v
* the Vertex to add
* @return true if the vertex was added, false if it was already in the graph.
*/
public boolean addVertex(Vertex<T> v) {
boolean added = false;
if (verticies.contains(v) == false) {
added = verticies.add(v);
}
return added;
}
/**
* Get the vertex count.
*
* @return the number of verticies in the graph.
*/
public int size() {
return verticies.size();
}
/**
* Get the root vertex
*
* @return the root vertex if one is set, null if no vertex has been set as
* the root.
*/
public Vertex<T> getRootVertex() {
return rootVertex;
}
/**
* Set a root vertex. If root does no exist in the graph it is added.
*
* @param root -
* the vertex to set as the root and optionally add if it does not
* exist in the graph.
*/
public void setRootVertex(Vertex<T> root) {
this.rootVertex = root;
if (verticies.contains(root) == false)
this.addVertex(root);
}
/**
* Get the given Vertex.
*
* @param n
* the index [0, size()-1] of the Vertex to access
* @return the nth Vertex
*/
public Vertex<T> getVertex(int n) {
return verticies.get(n);
}
/**
* Get the graph verticies
*
* @return the graph verticies
*/
public List<Vertex<T>> getVerticies() {
return this.verticies;
}
/**
* Insert a directed, weighted Edge<T> into the graph.
*
* @param from -
* the Edge<T> starting vertex
* @param to -
* the Edge<T> ending vertex
* @param cost -
* the Edge<T> weight/cost
* @return true if the Edge<T> was added, false if from already has this Edge<T>
* @throws IllegalArgumentException
* if from/to are not verticies in the graph
*/
public boolean addEdge(Vertex<T> from, Vertex<T> to, int cost) throws IllegalArgumentException {
if (verticies.contains(from) == false)
throw new IllegalArgumentException("from is not in graph");
if (verticies.contains(to) == false)
throw new IllegalArgumentException("to is not in graph");
Edge<T> e = new Edge<T>(from, to, cost);
if (from.findEdge(to) != null)
return false;
else {
from.addEdge(e);
to.addEdge(e);
edges.add(e);
return true;
}
}
/**
* Insert a bidirectional Edge<T> in the graph
*
* @param from -
* the Edge<T> starting vertex
* @param to -
* the Edge<T> ending vertex
* @param cost -
* the Edge<T> weight/cost
* @return true if edges between both nodes were added, false otherwise
* @throws IllegalArgumentException
* if from/to are not verticies in the graph
*/
public boolean insertBiEdge(Vertex<T> from, Vertex<T> to, int cost)
throws IllegalArgumentException {
return addEdge(from, to, cost) && addEdge(to, from, cost);
}
/**
* Get the graph edges
*
* @return the graph edges
*/
public List<Edge<T>> getEdges() {
return this.edges;
}
/**
* Remove a vertex from the graph
*
* @param v
* the Vertex to remove
* @return true if the Vertex was removed
*/
public boolean removeVertex(Vertex<T> v) {
if (!verticies.contains(v))
return false;
verticies.remove(v);
if (v == rootVertex)
rootVertex = null;
// Remove the edges associated with v
for (int n = 0; n < v.getOutgoingEdgeCount(); n++) {
Edge<T> e = v.getOutgoingEdge(n);
v.remove(e);
Vertex<T> to = e.getTo();
to.remove(e);
edges.remove(e);
}
for (int n = 0; n < v.getIncomingEdgeCount(); n++) {
Edge<T> e = v.getIncomingEdge(n);
v.remove(e);
Vertex<T> predecessor = e.getFrom();
predecessor.remove(e);
}
return true;
}
/**
* Remove an Edge<T> from the graph
*
* @param from -
* the Edge<T> starting vertex
* @param to -
* the Edge<T> ending vertex
* @return true if the Edge<T> exists, false otherwise
*/
public boolean removeEdge(Vertex<T> from, Vertex<T> to) {
Edge<T> e = from.findEdge(to);
if (e == null)
return false;
else {
from.remove(e);
to.remove(e);
edges.remove(e);
return true;
}
}
/**
* Clear the mark state of all verticies in the graph by calling clearMark()
* on all verticies.
*
* @see Vertex#clearMark()
*/
public void clearMark() {
for (Vertex<T> w : verticies)
w.clearMark();
}
/**
* Clear the mark state of all edges in the graph by calling clearMark() on
* all edges.
*/
public void clearEdges() {
for (Edge<T> e : edges)
e.clearMark();
}
/**
* Perform a depth first serach using recursion.
*
* @param v -
* the Vertex to start the search from
* @param visitor -
* the vistor to inform prior to
* @see Visitor#visit(Graph, Vertex)
*/
public void depthFirstSearch(Vertex<T> v, final Visitor<T> visitor) {
VisitorEX<T, RuntimeException> wrapper = new VisitorEX<T, RuntimeException>() {
public void visit(Graph<T> g, Vertex<T> v) throws RuntimeException {
if (visitor != null)
visitor.visit(g, v);
}
};
this.depthFirstSearch(v, wrapper);
}
/**
* Perform a depth first serach using recursion. The search may be cut short
* if the visitor throws an exception.
*
* @param <E>
*
* @param v -
* the Vertex to start the search from
* @param visitor -
* the vistor to inform prior to
* @see Visitor#visit(Graph, Vertex)
* @throws E
* if visitor.visit throws an exception
*/
public <E extends Exception> void depthFirstSearch(Vertex<T> v, VisitorEX<T, E> visitor) throws E {
if (visitor != null)
visitor.visit(this, v);
v.visit();
for (int i = 0; i < v.getOutgoingEdgeCount(); i++) {
Edge<T> e = v.getOutgoingEdge(i);
if (!e.getTo().visited()) {
depthFirstSearch(e.getTo(), visitor);
}
}
}
/**
* Perform a breadth first search of this graph, starting at v.
*
* @param v -
* the search starting point
* @param visitor -
* the vistor whose vist method is called prior to visting a vertex.
*/
public void breadthFirstSearch(Vertex<T> v, final Visitor<T> visitor) {
VisitorEX<T, RuntimeException> wrapper = new VisitorEX<T, RuntimeException>() {
public void visit(Graph<T> g, Vertex<T> v) throws RuntimeException {
if (visitor != null)
visitor.visit(g, v);
}
};
this.breadthFirstSearch(v, wrapper);
}
/**
* Perform a breadth first search of this graph, starting at v. The vist may
* be cut short if visitor throws an exception during a vist callback.
*
* @param <E>
*
* @param v -
* the search starting point
* @param visitor -
* the vistor whose vist method is called prior to visting a vertex.
* @throws E
* if vistor.visit throws an exception
*/
public <E extends Exception> void breadthFirstSearch(Vertex<T> v, VisitorEX<T, E> visitor)
throws E {
LinkedList<Vertex<T>> q = new LinkedList<Vertex<T>>();
q.add(v);
if (visitor != null)
visitor.visit(this, v);
v.visit();
while (q.isEmpty() == false) {
v = q.removeFirst();
for (int i = 0; i < v.getOutgoingEdgeCount(); i++) {
Edge<T> e = v.getOutgoingEdge(i);
Vertex<T> to = e.getTo();
if (!to.visited()) {
q.add(to);
if (visitor != null)
visitor.visit(this, to);
to.visit();
}
}
}
}
/**
* Find the spanning tree using a DFS starting from v.
*
* @param v -
* the vertex to start the search from
* @param visitor -
* visitor invoked after each vertex is visited and an edge is added
* to the tree.
*/
public void dfsSpanningTree(Vertex<T> v, DFSVisitor<T> visitor) {
v.visit();
if (visitor != null)
visitor.visit(this, v);
for (int i = 0; i < v.getOutgoingEdgeCount(); i++) {
Edge<T> e = v.getOutgoingEdge(i);
if (!e.getTo().visited()) {
if (visitor != null)
visitor.visit(this, v, e);
e.mark();
dfsSpanningTree(e.getTo(), visitor);
}
}
}
/**
* Search the verticies for one with name.
*
* @param name -
* the vertex name
* @return the first vertex with a matching name, null if no matches are found
*/
public Vertex<T> findVertexByName(String name) {
Vertex<T> match = null;
for (Vertex<T> v : verticies) {
if (name.equals(v.getName())) {
match = v;
break;
}
}
return match;
}
/**
* Search the verticies for one with data.
*
* @param data -
* the vertex data to match
* @param compare -
* the comparator to perform the match
* @return the first vertex with a matching data, null if no matches are found
*/
public Vertex<T> findVertexByData(T data, Comparator<T> compare) {
Vertex<T> match = null;
for (Vertex<T> v : verticies) {
if (compare.compare(data, v.getData()) == 0) {
match = v;
break;
}
}
return match;
}
/**
* Search the graph for cycles. In order to detect cycles, we use a modified
* depth first search called a colored DFS. All nodes are initially marked
* white. When a node is encountered, it is marked grey, and when its
* descendants are completely visited, it is marked black. If a grey node is
* ever encountered, then there is a cycle.
*
* @return the edges that form cycles in the graph. The array will be empty if
* there are no cycles.
*/
public Edge<T>[] findCycles() {
ArrayList<Edge<T>> cycleEdges = new ArrayList<Edge<T>>();
// Mark all verticies as white
for (int n = 0; n < verticies.size(); n++) {
Vertex<T> v = getVertex(n);
v.setMarkState(VISIT_COLOR_WHITE);
}
for (int n = 0; n < verticies.size(); n++) {
Vertex<T> v = getVertex(n);
visit(v, cycleEdges);
}
Edge<T>[] cycles = new Edge[cycleEdges.size()];
cycleEdges.toArray(cycles);
return cycles;
}
private void visit(Vertex<T> v, ArrayList<Edge<T>> cycleEdges) {
v.setMarkState(VISIT_COLOR_GREY);
int count = v.getOutgoingEdgeCount();
for (int n = 0; n < count; n++) {
Edge<T> e = v.getOutgoingEdge(n);
Vertex<T> u = e.getTo();
if (u.getMarkState() == VISIT_COLOR_GREY) {
// A cycle Edge<T>
cycleEdges.add(e);
} else if (u.getMarkState() == VISIT_COLOR_WHITE) {
visit(u, cycleEdges);
}
}
v.setMarkState(VISIT_COLOR_BLACK);
}
public String toString() {
StringBuffer tmp = new StringBuffer("Graph[");
for (Vertex<T> v : verticies){
tmp.append(v);
tmp.append('\n');
}
tmp.append(']');
return tmp.toString();
}
}