/* * 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(']'); return tmp.toString(); } } /* * 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. */ /** * A directed, weighted edge in a graph * * @author Scott.Stark@jboss.org * @version $Revision$ * @param <T> */ class Edge<T> { private Vertex<T> from; private Vertex<T> to; private int cost; private boolean mark; /** * Create a zero cost edge between from and to * * @param from * the starting vertex * @param to * the ending vertex */ public Edge(Vertex<T> from, Vertex<T> to) { this(from, to, 0); } /** * Create an edge between from and to with the given cost. * * @param from * the starting vertex * @param to * the ending vertex * @param cost * the cost of the edge */ public Edge(Vertex<T> from, Vertex<T> to, int cost) { this.from = from; this.to = to; this.cost = cost; mark = false; } /** * Get the ending vertex * * @return ending vertex */ public Vertex<T> getTo() { return to; } /** * Get the starting vertex * * @return starting vertex */ public Vertex<T> getFrom() { return from; } /** * Get the cost of the edge * * @return cost of the edge */ public int getCost() { return cost; } /** * Set the mark flag of the edge * */ public void mark() { mark = true; } /** * Clear the edge mark flag * */ public void clearMark() { mark = false; } /** * Get the edge mark flag * * @return edge mark flag */ public boolean isMarked() { return mark; } /** * String rep of edge * * @return string rep with from/to vertex names and cost */ public String toString() { StringBuffer tmp = new StringBuffer("Edge[from: "); tmp.append(from.getName()); tmp.append(",to: "); tmp.append(to.getName()); tmp.append(", cost: "); tmp.append(cost); tmp.append("]"); return tmp.toString(); } } /* * 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. */ /** * A named graph vertex with optional data. * * @author Scott.Stark@jboss.org * @version $Revision$ * @param <T> */ @SuppressWarnings("unchecked") class Vertex<T> { private List<Edge<T>> incomingEdges; private List<Edge<T>> outgoingEdges; private String name; private boolean mark; private int markState; private T data; /** * Calls this(null, null). */ public Vertex() { this(null, null); } /** * Create a vertex with the given name and no data * * @param n */ public Vertex(String n) { this(n, null); } /** * Create a Vertex with name n and given data * * @param n - * name of vertex * @param data - * data associated with vertex */ public Vertex(String n, T data) { incomingEdges = new ArrayList<Edge<T>>(); outgoingEdges = new ArrayList<Edge<T>>(); name = n; mark = false; this.data = data; } /** * @return the possibly null name of the vertex */ public String getName() { return name; } /** * @return the possibly null data of the vertex */ public T getData() { return this.data; } /** * @param data * The data to set. */ public void setData(T data) { this.data = data; } /** * Add an edge to the vertex. If edge.from is this vertex, its an outgoing * edge. If edge.to is this vertex, its an incoming edge. If neither from or * to is this vertex, the edge is not added. * * @param e - * the edge to add * @return true if the edge was added, false otherwise */ public boolean addEdge(Edge<T> e) { if (e.getFrom() == this) outgoingEdges.add(e); else if (e.getTo() == this) incomingEdges.add(e); else return false; return true; } /** * Add an outgoing edge ending at to. * * @param to - * the destination vertex * @param cost * the edge cost */ public void addOutgoingEdge(Vertex<T> to, int cost) { Edge<T> out = new Edge<T>(this, to, cost); outgoingEdges.add(out); } /** * Add an incoming edge starting at from * * @param from - * the starting vertex * @param cost * the edge cost */ public void addIncomingEdge(Vertex<T> from, int cost) { Edge<T> out = new Edge<T>(this, from, cost); incomingEdges.add(out); } /** * Check the vertex for either an incoming or outgoing edge mathcing e. * * @param e * the edge to check * @return true it has an edge */ public boolean hasEdge(Edge<T> e) { if (e.getFrom() == this) return incomingEdges.contains(e); else if (e.getTo() == this) return outgoingEdges.contains(e); else return false; } /** * Remove an edge from this vertex * * @param e - * the edge to remove * @return true if the edge was removed, false if the edge was not connected * to this vertex */ public boolean remove(Edge<T> e) { if (e.getFrom() == this) incomingEdges.remove(e); else if (e.getTo() == this) outgoingEdges.remove(e); else return false; return true; } /** * * @return the count of incoming edges */ public int getIncomingEdgeCount() { return incomingEdges.size(); } /** * Get the ith incoming edge * * @param i * the index into incoming edges * @return ith incoming edge */ public Edge<T> getIncomingEdge(int i) { return incomingEdges.get(i); } /** * Get the incoming edges * * @return incoming edge list */ public List getIncomingEdges() { return this.incomingEdges; } /** * * @return the count of incoming edges */ public int getOutgoingEdgeCount() { return outgoingEdges.size(); } /** * Get the ith outgoing edge * * @param i * the index into outgoing edges * @return ith outgoing edge */ public Edge<T> getOutgoingEdge(int i) { return outgoingEdges.get(i); } /** * Get the outgoing edges * * @return outgoing edge list */ public List getOutgoingEdges() { return this.outgoingEdges; } /** * Search the outgoing edges looking for an edge whose's edge.to == dest. * * @param dest * the destination * @return the outgoing edge going to dest if one exists, null otherwise. */ public Edge<T> findEdge(Vertex<T> dest) { for (Edge<T> e : outgoingEdges) { if (e.getTo() == dest) return e; } return null; } /** * Search the outgoing edges for a match to e. * * @param e - * the edge to check * @return e if its a member of the outgoing edges, null otherwise. */ public Edge<T> findEdge(Edge<T> e) { if (outgoingEdges.contains(e)) return e; else return null; } /** * What is the cost from this vertext to the dest vertex. * * @param dest - * the destination vertex. * @return Return Integer.MAX_VALUE if we have no edge to dest, 0 if dest is * this vertex, the cost of the outgoing edge otherwise. */ public int cost(Vertex<T> dest) { if (dest == this) return 0; Edge<T> e = findEdge(dest); int cost = Integer.MAX_VALUE; if (e != null) cost = e.getCost(); return cost; } /** * Is there an outgoing edge ending at dest. * * @param dest - * the vertex to check * @return true if there is an outgoing edge ending at vertex, false * otherwise. */ public boolean hasEdge(Vertex<T> dest) { return (findEdge(dest) != null); } /** * Has this vertex been marked during a visit * * @return true is visit has been called */ public boolean visited() { return mark; } /** * Set the vertex mark flag. * */ public void mark() { mark = true; } /** * Set the mark state to state. * * @param state * the state */ public void setMarkState(int state) { markState = state; } /** * Get the mark state value. * * @return the mark state */ public int getMarkState() { return markState; } /** * Visit the vertex and set the mark flag to true. * */ public void visit() { mark(); } /** * Clear the visited mark flag. * */ public void clearMark() { mark = false; } /** * @return a string form of the vertex with in and out edges. */ public String toString() { StringBuffer tmp = new StringBuffer("Vertex("); tmp.append(name); tmp.append(", data="); tmp.append(data); tmp.append("), in:["); for (int i = 0; i < incomingEdges.size(); i++) { Edge<T> e = incomingEdges.get(i); if (i > 0) tmp.append(','); tmp.append('{'); tmp.append(e.getFrom().name); tmp.append(','); tmp.append(e.getCost()); tmp.append('}'); } tmp.append("], out:["); for (int i = 0; i < outgoingEdges.size(); i++) { Edge<T> e = outgoingEdges.get(i); if (i > 0) tmp.append(','); tmp.append('{'); tmp.append(e.getTo().name); tmp.append(','); tmp.append(e.getCost()); tmp.append('}'); } tmp.append(']'); return tmp.toString(); } } /* * 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. */ /** * A graph visitor interface. * * @author Scott.Stark@jboss.org * @version $Revision$ * @param <T> */ interface Visitor<T> { /** * Called by the graph traversal methods when a vertex is first visited. * * @param g - * the graph * @param v - * the vertex being visited. */ public void visit(Graph<T> g, Vertex<T> v); } /* * 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. */ /** * A graph visitor interface that can throw an exception during a visit * callback. * * @author Scott.Stark@jboss.org * @version $Revision$ * @param <T> * @param <E> */ interface VisitorEX<T, E extends Exception> { /** * Called by the graph traversal methods when a vertex is first visited. * * @param g - * the graph * @param v - * the vertex being visited. * @throws E * exception for any error */ public void visit(Graph<T> g, Vertex<T> v) throws E; } /* * 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. */ /** * A spanning tree visitor callback interface * * @see Graph#dfsSpanningTree(Vertex, DFSVisitor) * * @author Scott.Stark@jboss.org * @version $Revision$ * @param <T> */ interface DFSVisitor<T> { /** * Called by the graph traversal methods when a vertex is first visited. * * @param g - * the graph * @param v - * the vertex being visited. */ public void visit(Graph<T> g, Vertex<T> v); /** * Used dfsSpanningTree to notify the visitor of each outgoing edge to an * unvisited vertex. * * @param g - * the graph * @param v - * the vertex being visited * @param e - * the outgoing edge from v */ public void visit(Graph<T> g, Vertex<T> v, Edge<T> e); }