diff --git a/ccm-core/src/main/java/com/arsdigita/util/Graph.java b/ccm-core/src/main/java/com/arsdigita/util/Graph.java
new file mode 100755
index 000000000..24e0492de
--- /dev/null
+++ b/ccm-core/src/main/java/com/arsdigita/util/Graph.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved.
+ *
+ * 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 com.arsdigita.util;
+
+import java.util.List;
+
+/**
+ * The graph class allows you to build graphs of objects.
+ *
+ * @author Archit Shah (ashah@mit.edu)
+ * @author Vadim Nasardinov (vadimn@redhat.com)
+ * @version $Date$
+ * @since 2003-01-22
+ **/
+public interface Graph {
+ /**
+ * Creates a copy of this graph.
+ **/
+ Graph copy();
+
+ /**
+ * Sets the graph's label.
+ **/
+ void setLabel(String label);
+
+ /**
+ * Returns the graph's label.
+ **/
+ String getLabel();
+
+ /**
+ * Adds a node to the graph.
+ **/
+ void addNode(Object node);
+
+ /**
+ * Returns true if the graph has this node.
+ **/
+ boolean hasNode(Object node);
+
+
+ /**
+ * Returns true if the graph has this edge.
+ *
+ * @pre hasNode(edge.getTail()) && hasNode(edge.getHead())
+ **/
+ boolean hasEdge(Graph.Edge edge);
+
+ /**
+ * Returns the count of nodes in this graph.
+ **/
+ int nodeCount();
+
+ /**
+ * Adds an edge to the graph.
+ **/
+ void addEdge(Graph.Edge edge);
+
+ /**
+ * A convenient shortcut for addEdge(new Graph.Edge(tail, head,
+ * label)).
+ *
+ * @see #addEdge(Graph.Edge)
+ **/
+ void addEdge(Object tail, Object head, Object label);
+
+ /**
+ * Returns a list of nodes that this graph has. (Todo: this should probably
+ * return a Set.)
+ **/
+ List getNodes();
+
+ /**
+ * Removes specified node and all edges incident to it.
+ *
+ * @returns true if this Graph contains the specified node
+ **/
+ boolean removeNode(Object node);
+
+ /**
+ * A convenient shortcut for removeEdge(new Graph.Edge(tail, head,
+ * label)).
+ *
+ * @see #removeEdge(Graph.Edge)
+ **/
+ boolean removeEdge(Object tail, Object head, Object label);
+
+ /**
+ * Removes specified edge.
+ *
+ * @returns true if this Graph has the specified edge
+ **/
+ boolean removeEdge(Graph.Edge edge);
+
+ /**
+ * Removes all nodes and edges.
+ **/
+ void removeAll();
+
+ /**
+ * Returns a list of outgoing edges leaving this node.
+ **/
+ List getOutgoingEdges(Object node);
+
+ /**
+ * Returns the number of outgoing edges this node has. A convenient shortcut
+ * for getOutgoingEdges(node).size().
+ *
+ * @see #getOutgoingEdges(Object)
+ **/
+ int outgoingEdgeCount(Object node);
+
+ /**
+ * @see #outgoingEdgeCount(Object)
+ **/
+ int incomingEdgeCount(Object node);
+
+ /**
+ * @see #getOutgoingEdges(Object)
+ **/
+ List getIncomingEdges(Object node);
+
+ /**
+ * An edge is an ordered pair of nodes with a label attached to it. The
+ * first node of the pair is called the tail and the second the
+ * head.
+ *
+ *
Implementing classes are expected to supply a constructor of the form
+ * Graph.Edge(Object tail, Object head, Object label).
Float representing the the distance (the
+ * length of the route).
+ **/
+ Object getLabel();
+
+ }
+}
diff --git a/ccm-core/src/main/java/com/arsdigita/util/GraphEdge.java b/ccm-core/src/main/java/com/arsdigita/util/GraphEdge.java
new file mode 100755
index 000000000..f969436f4
--- /dev/null
+++ b/ccm-core/src/main/java/com/arsdigita/util/GraphEdge.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved.
+ *
+ * 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 com.arsdigita.util;
+
+/**
+ * Graph edge.
+ *
+ * @author Archit Shah (ashah@mit.edut)
+ * @author Vadim Nasardinov (vadimn@redhat.com)
+ * @since 2003-01-22
+ * @version $Revision$ $Date$
+ **/
+public final class GraphEdge implements Graph.Edge {
+ private Object m_tail;
+ private Object m_head;
+ private Object m_label;
+
+ /**
+ * @pre tail != null
+ * @pre head != null
+ **/
+ public GraphEdge(Object tail, Object head, Object label) {
+ Assert.exists(tail, Object.class);
+ Assert.exists(head, Object.class);
+ m_tail = tail;
+ m_head = head;
+ m_label = label;
+ }
+
+ /**
+ * @set return != null
+ **/
+ public Object getTail() {
+ return m_tail;
+ }
+
+ /**
+ * @set return != null
+ **/
+ public Object getHead() {
+ return m_head;
+ }
+
+ public Object getLabel() {
+ return m_label;
+ }
+
+ public String toString() {
+ StringBuffer buf = new StringBuffer();
+ buf.append(m_tail).append(" -> ").append(m_head);
+ buf.append("[").append(m_label).append("]");
+ return buf.toString();
+ }
+
+ public boolean equals(Object obj) {
+ if ( obj == null ) return false;
+
+ if (obj instanceof Graph.Edge) {
+ Graph.Edge that = (Graph.Edge) obj;
+ Object thatLabel = that.getLabel();
+
+ boolean equalLabels =
+ (m_label == null && thatLabel == null ) ||
+ (m_label != null && m_label.equals(thatLabel)) ||
+ (thatLabel != null && thatLabel.equals(m_label));
+
+ return
+ m_tail.equals(that.getTail()) &&
+ m_head.equals(that.getHead()) &&
+ equalLabels;
+ } else {
+ return false;
+ }
+ }
+
+ public int hashCode() {
+ return m_tail.hashCode() + m_head.hashCode() +
+ (m_label == null ? 0 : m_label.hashCode());
+ }
+}
diff --git a/ccm-core/src/main/java/com/arsdigita/util/GraphFormatter.java b/ccm-core/src/main/java/com/arsdigita/util/GraphFormatter.java
new file mode 100755
index 000000000..e499fa193
--- /dev/null
+++ b/ccm-core/src/main/java/com/arsdigita/util/GraphFormatter.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved.
+ *
+ * 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 com.arsdigita.util;
+
+/**
+ * Implementations of this interface can be used for pretty-printing graphs.
+ *
+ * @author Vadim Nasardinov (vadimn@redhat.com)
+ * @version $Date$
+ * @since 2003-01-23
+ **/
+public interface GraphFormatter {
+
+ /**
+ * Returns graph attributes.
+ *
+ * For example, if you choose to pretty-print your graph in the DOT + * language, then the graph attributes section may look like so:
+ * + *
+ * digraph mygraph {
+ * // the following two lines are graph attributes
+ * node[shape=box,fontsize=8,fontname=verdana,height=0.2,width=0.2,style=filled];
+ * ranksep=0.05;
+ *
+ * // the following lines are nodes and edges
+ * A -> B -> C -> D;
+ * B -> E -> F;
+ * C -> G;
+ * D -> I;
+ * D -> J -> H;
+ * }
+
+ **/
+ String graphAttributes(Graph graph);
+
+ /**
+ * Returns a textual representation of the node, preferably a short one that
+ * can be used in the following plain-text representation of the tree.
+ *
+ *
+ * digraph tree {
+ * A -> B -> C -> D;
+ * B -> E -> F;
+ * C -> G;
+ * D -> I;
+ * D -> J -> H;
+ * }
+ *
+ *
+ * Example implementation:
+ *
+ *
+ * public String formatNode(Object node) {
+ * return node == null ? null : ((ObjectType) node).getName();
+ * }
+ *
+ **/
+ String nodeName(Object node);
+
+ /**
+ * Returns [bracketed] node attributes.
+ *
+ *
+ * digraph g {
+ * C[label="The C Language"];
+ * J;
+ * C -> J;
+ * }
+ *
+ **/
+ String nodeAttributes(Object node);
+
+ /**
+ * Returns a short textual label describing the edge.
+ **/
+ String edge(Object edge);
+}
diff --git a/ccm-core/src/main/java/com/arsdigita/util/GraphSet.java b/ccm-core/src/main/java/com/arsdigita/util/GraphSet.java
new file mode 100755
index 000000000..3d4b514a8
--- /dev/null
+++ b/ccm-core/src/main/java/com/arsdigita/util/GraphSet.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved.
+ *
+ * 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 com.arsdigita.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.arsdigita.util.Assert;
+
+/**
+ * A Set-based implementation of the {@link com.arsdigita.util.Graph} interface.
+ * Once you've added a node to this graph, you must not mutate the node in a way
+ * that affects its equals(Object) and hashCode()
+ * methods.
+ *
+ * This class permits the null node.
+ *
+ * This implementation is not synchronized..
+ *
+ * @author Archit Shah (ashah@mit.edut)
+ * @author Vadim Nasardinov (vadimn@redhat.com)
+ * @since 2003-01-22
+ * @version $Date$
+ **/
+public class GraphSet implements Graph {
+ private final static String LINE_SEP = System.getProperty("line.separator");
+
+ private Set m_nodes = new HashSet();
+ private Map m_outgoingEdges = new HashMap();
+ private Map m_incomingEdges = new HashMap();
+ private String m_label = "directed_graph";
+
+ public Graph copy() {
+ Graph newGraph = new GraphSet();
+ for (Iterator nodes = getNodes().iterator(); nodes.hasNext(); ) {
+ Object node = nodes.next();
+ newGraph.addNode(node);
+ for (Iterator edges=getOutgoingEdges(node).iterator(); edges.hasNext(); ) {
+ Graph.Edge edge = (Graph.Edge) edges.next();
+ newGraph.addEdge(edge.getTail(), edge.getHead(), edge.getLabel());
+ }
+ }
+ newGraph.setLabel(getLabel());
+ return newGraph;
+ }
+
+ public void setLabel(String label) {
+ Assert.isTrue(label !=null, "label is not null");
+ m_label = label;
+ }
+
+ public String getLabel() {
+ return m_label;
+ }
+
+ public void addNode(Object name) {
+ m_nodes.add(name);
+ }
+
+ public boolean hasNode(Object nodeName) {
+ return m_nodes.contains(nodeName);
+ }
+
+ /**
+ * @pre hasNode(edge.getTail()) && hasNode(edge.getHead())
+ **/
+ public boolean hasEdge(Graph.Edge edge) {
+ return outgoingEdges(edge.getTail()).contains(edge);
+ }
+
+ public int nodeCount() {
+ return m_nodes.size();
+ }
+
+ public void addEdge(Graph.Edge edge) {
+ m_nodes.add(edge.getTail());
+ m_nodes.add(edge.getHead());
+ outgoingEdges(edge.getTail()).add(edge);
+ incomingEdges(edge.getHead()).add(edge);
+ }
+
+ public void addEdge(Object tail, Object head, Object label) {
+ addEdge(new GraphEdge(tail, head, label));
+ }
+
+ public List getNodes() {
+ return new ArrayList(m_nodes);
+ }
+
+ public boolean removeNode(Object nodeName) {
+ boolean hasNode = m_nodes.remove(nodeName);
+ if (hasNode) {
+ Set out = (Set) m_outgoingEdges.remove(nodeName);
+ if (out != null) {
+ for (Iterator it = out.iterator(); it.hasNext(); ) {
+ Graph.Edge e = (Graph.Edge) it.next();
+ incomingEdges(e.getHead()).remove(e);
+ }
+ }
+ Set in = (Set) m_incomingEdges.remove(nodeName);
+ if (in != null) {
+ for (Iterator it = in.iterator(); it.hasNext(); ) {
+ Graph.Edge e = (Graph.Edge) it.next();
+ outgoingEdges(e.getTail()).remove(e);
+ }
+ }
+ }
+ return hasNode;
+ }
+
+ public boolean removeEdge(Object tail, Object head, Object label) {
+ return removeEdge(new GraphEdge(tail, head, label));
+ }
+
+ public boolean removeEdge(Graph.Edge edge) {
+ boolean hasEdge = outgoingEdges(edge.getTail()).remove(edge);
+ if (hasEdge) { incomingEdges(edge.getHead()).remove(edge); }
+ return hasEdge;
+ }
+
+ public void removeAll() {
+ m_nodes.clear();
+ m_outgoingEdges.clear();
+ m_incomingEdges.clear();
+ }
+
+ private Set outgoingEdges(Object nodeName) {
+ Set edges = (Set) m_outgoingEdges.get(nodeName);
+ if (edges == null) {
+ edges = new HashSet(4);
+ m_outgoingEdges.put(nodeName, edges);
+ }
+
+ return edges;
+ }
+
+ private static String objToString(Object obj) {
+ return obj == null ? "null" : obj.toString();
+ }
+
+ public List getOutgoingEdges(Object node) {
+ Assert.isTrue(hasNode(node), objToString(node));
+ return new ArrayList(outgoingEdges(node));
+ }
+
+ public int outgoingEdgeCount(Object node) {
+ Assert.isTrue(hasNode(node), objToString(node));
+ return outgoingEdges(node).size();
+ }
+
+ public int incomingEdgeCount(Object node) {
+ Assert.isTrue(hasNode(node), objToString(node));
+ return incomingEdges(node).size();
+ }
+
+ private Set incomingEdges(Object nodeName) {
+ Set edges = (Set) m_incomingEdges.get(nodeName);
+ if ( edges == null ) {
+ edges = new HashSet();
+ m_incomingEdges.put(nodeName, edges);
+ }
+ return edges;
+ }
+
+ public List getIncomingEdges(Object node) {
+ Assert.isTrue(hasNode(node), objToString(node));
+ return new ArrayList(incomingEdges(node));
+ }
+
+ /**
+ * Returns a printable representation of the graph that has the following
+ * form.
+ *
+ *
+ * digraph foo {
+ * Boston -> New_York [label="214 miles"];
+ * Boston -> Chicago [label="983 miles"];
+ * New_York -> Chicago [label="787 miles"];
+ * Boston -> Westford [label="35 miles"];
+ * Raleigh -> Westford [label="722 miles"];
+ * }
+ *
+ *
+ * Note that to get a neat printable representation, each node and edge
+ * label must have a short printable representation.
+ **/
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("graph ").append(getLabel()).append(" {");
+ sb.append(LINE_SEP);
+ List sortedNodes = new ArrayList(m_nodes);
+ Collections.sort(sortedNodes);
+ for (Iterator nodes=sortedNodes.iterator(); nodes.hasNext(); ) {
+ Object node = nodes.next();
+ for (Iterator edges = getOutgoingEdges(node).iterator(); edges.hasNext(); ) {
+ Graph.Edge edge = (Graph.Edge) edges.next();
+ sb.append(" ");
+ sb.append(objToString(node)).append(" -> ");
+ sb.append(objToString(edge.getHead()));
+ sb.append("[label=\"").append(edge.getLabel());
+ sb.append("\"];");
+ sb.append(LINE_SEP);
+ }
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+}
diff --git a/ccm-core/src/main/java/com/arsdigita/util/Graphs.java b/ccm-core/src/main/java/com/arsdigita/util/Graphs.java
new file mode 100755
index 000000000..e92130e66
--- /dev/null
+++ b/ccm-core/src/main/java/com/arsdigita/util/Graphs.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved.
+ *
+ * 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 com.arsdigita.util;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ * A collection of methods that operate on {@link com.arsdigita.util.Graph
+ * graphs}.
+ *
+ * @author Archit Shah (ashah@mit.edu)
+ * @author Vadim Nasardinov (vadimn@redhat.com)
+ * @version $Date$
+ * @since 2003-01-22
+ **/
+public class Graphs {
+
+ /**
+ * A simple implementation of the {@link Graphs.EdgeSelector} interface that
+ * selects outgoing edges.
+ **/
+ public final static EdgeSelector FORWARD_SELECTOR = new EdgeSelector() {
+ public boolean test(Graph.Edge edge, boolean forward) {
+ return forward;
+ }
+ };
+
+ private static final String INDENT = " ";
+
+ private Graphs() {}
+
+ /**
+ * Finds a path in graph from begin to end
+ *
+ * @see #findPath(Graph, Object, Graphs.EdgeSelector, Graphs.NodeSelector)
+ * @returns list of edges representing the found path
+ * @throws NullPointerException if any of the three arguments is null.
+ **/
+ public static final List findPath(Graph graph, Object begin, Object end) {
+ NodeSelector terminator = new EqualityTerminator(end);
+ return findPath(graph, begin, FORWARD_SELECTOR, terminator);
+ }
+
+ /**
+ * Performs a traversal of graph looking for path that starts
+ * with startNode and terminates with a node that satisfies the
+ * test specified by terminator and consists only of those
+ * edges that satisfy the test specified by selector.
+ *
+ * @return a path from start node to a node that satisfies the condition
+ * specified by terminator, such that all edges in the path
+ * satisfy the condition specified by selector. An empty list
+ * is returned, if no such path can be found.
+ **/
+ public static List findPath(Graph graph, Object startNode,
+ EdgeSelector selector,
+ NodeSelector terminator) {
+
+ Stack path = new Stack();
+ findPathRecur
+ (graph, startNode, selector, terminator, new HashSet(), path);
+ return path;
+ }
+
+
+ private static final boolean findPathRecur
+ (Graph graph, Object start,
+ EdgeSelector selector, NodeSelector terminator,
+ Set searched, Stack path) {
+
+ Iterator it = graph.getOutgoingEdges(start).iterator();
+ searched.add(start);
+
+ while (it.hasNext()) {
+ Graph.Edge edge = (Graph.Edge) it.next();
+ if ( !selector.test(edge, true) ) { continue; }
+
+ path.push(edge);
+ final Object node = edge.getHead();
+
+ if (terminator.test(node)) {
+ return true;
+ }
+
+ if (!searched.contains(node)) {
+ boolean found = findPathRecur
+ (graph, node, selector, terminator, searched, path);
+
+ if ( found ) { return true; }
+ }
+ path.pop();
+ }
+ return false;
+ }
+
+ /**
+ * @param edgePath a list of edges such as the one returned by
+ * {@link #findPath(Graph, Object, Object)}.
+ * @return the same path represented as a list of nodes rather than edges.
+ *
+ * @throws IllegalStateException if edgePath is not a valid
+ * path. For example, (a->b, c->d, d->e) is invalid, because
+ * the edge between b and c is missing.
+ * @throws NullPointerException if edgePath is null
+ **/
+ public static final List edgePathToNodePath(List edgePath) {
+ List path = new ArrayList();
+ Graph.Edge lastEdge = null;
+ for (Iterator edges = edgePath.iterator(); edges.hasNext(); ) {
+ Graph.Edge edge = (Graph.Edge) edges.next();
+ if ( lastEdge != null ) {
+ if ( !lastEdge.getHead().equals(edge.getTail()) ) {
+ throw new IllegalArgumentException
+ ("non-contiguous path segment:\n" + lastEdge + "\n" +
+ edge);
+ }
+ }
+ path.add(edge.getTail());
+ lastEdge = edge;
+ }
+ if ( lastEdge != null ) {
+ path.add(lastEdge.getHead());
+ }
+ return path;
+ }
+
+ private static String objToString(Object obj) {
+ return obj == null ? "null" : obj.toString();
+ }
+
+ /**
+ * @return nodes reachable from start, including the
+ * start node itself.
+ *
+ * @pre graph.hasNode(start)
+ **/
+ public static Graph nodesReachableFrom(Graph graph, Object start) {
+ Assert.isTrue(graph.hasNode(start));
+ Graph result = new GraphSet();
+ result.addNode(start);
+ Set processedTails = new HashSet();
+ nodesReachableRecurse(graph, start, processedTails, result);
+ return result;
+ }
+
+ private static void nodesReachableRecurse(Graph gg, Object currentNode,
+ Set processedTails,
+ Graph accumulator) {
+
+ processedTails.add(currentNode);
+
+ for (Iterator edges=gg.getOutgoingEdges(currentNode).iterator(); edges.hasNext(); ) {
+ Graph.Edge edge = (Graph.Edge) edges.next();
+ if ( processedTails.contains(edge.getHead()) ) {
+ continue;
+ }
+ accumulator.addEdge(edge);
+ nodesReachableRecurse
+ (gg, edge.getHead(), processedTails, accumulator);
+ }
+ }
+
+ /**
+ * Returns a list of nodes in gg that have no outgoing edges.
+ **/
+ public static List getSinkNodes(Graph gg) {
+ List result = new ArrayList();
+ for (Iterator nodes = gg.getNodes().iterator(); nodes.hasNext(); ) {
+ Object node = nodes.next();
+ if ( gg.getOutgoingEdges(node).size() == 0 ) {
+ result.add(node);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Pretty-prints the tree in a format patterned off of the DOT
+ * language.
+ *
+ * @pre tree != null
+ * @pre fmtr != null
+ * @pre writer != null
+ **/
+ public static void printTree(Tree tree, GraphFormatter fmtr,
+ PrintWriter writer) {
+
+ Assert.exists(tree, "tree");
+ Assert.exists(fmtr, "formatter");
+ Assert.exists(writer, "writer");
+
+ writer.println("digraph " + tree.getLabel() + " {");
+ printTreeRecurse(tree, fmtr, writer);
+ writer.println("}");
+ }
+
+ private static void printTreeRecurse(Tree tree, GraphFormatter fmtr,
+ PrintWriter writer) {
+
+ String root = fmtr.nodeName(tree.getRoot());
+ for (Iterator ii=tree.getSubtrees().iterator(); ii.hasNext(); ) {
+ Tree.EdgeTreePair pair = (Tree.EdgeTreePair) ii.next();
+ String edge = fmtr.edge(pair.getEdge());
+ String child = fmtr.nodeName(pair.getTree().getRoot());
+ writer.print(INDENT + root + " -> " + child);
+ if ( edge != null ) {
+ writer.print("[label=\"" + edge + "\"]");
+ }
+ writer.println(";");
+ printTreeRecurse(pair.getTree(), fmtr, writer);
+ }
+ }
+
+ /**
+ * Pretty-prints the graph.
+ *
+ * @see #printTree(Tree, GraphFormatter, PrintWriter)
+ * @pre graph != null
+ * @pre fmtr != null
+ * @pre writer != null
+ **/
+ public static void printGraph(Graph graph, GraphFormatter fmtr,
+ PrintWriter writer) {
+
+ Assert.exists(graph, "tree");
+ Assert.exists(fmtr, "formatter");
+ Assert.exists(writer, "writer");
+
+ writer.println("digraph " + graph.getLabel() + " {");
+ String graphAttrs = fmtr.graphAttributes(graph);
+ if ( graphAttrs != null ) {
+ writer.println(graphAttrs);
+ }
+ for (Iterator nodes=graph.getNodes().iterator(); nodes.hasNext(); ) {
+ Object node = nodes.next();
+ int nodeCount = graph.outgoingEdgeCount(node) +
+ graph.incomingEdgeCount(node);
+
+ String nodeName = fmtr.nodeName(node);
+ String nodeAttrs = fmtr.nodeAttributes(node);
+
+ if ( nodeCount==0 || nodeAttrs != null ) {
+ writer.print(INDENT + nodeName);
+
+ if ( nodeAttrs == null ) {
+ writer.println(";");
+ } else {
+ writer.println(nodeAttrs + ";");
+ }
+ }
+
+ if (graph.outgoingEdgeCount(node) == 0) {
+ // we'll print this node when we print the outgoing edges of
+ // some other node
+ continue;
+ }
+ Iterator edges = graph.getOutgoingEdges(node).iterator();
+ while (edges.hasNext()) {
+ Graph.Edge edge = (Graph.Edge) edges.next();
+ StringBuffer sb = new StringBuffer();
+ sb.append(INDENT).append(fmtr.nodeName(edge.getTail()));
+ sb.append(" -> ").append(fmtr.nodeName(edge.getHead()));
+ if ( edge.getLabel() != null ) {
+ sb.append("[label=\"");
+ sb.append(fmtr.edge(edge.getLabel()));
+ sb.append("\"]");
+ }
+ sb.append(";");
+ writer.println(sb.toString());
+ }
+ }
+ writer.println("}");
+ }
+
+ /**
+ * @see #findPath(Graph, Object, Graphs.EdgeSelector, Graphs.NodeSelector)
+ **/
+ public interface EdgeSelector {
+ /**
+ * @param edge the edge to test
+ * @param forward the value of false indicates that an edge
+ * is to be traversed backwards. Useful, when you want to find a path
+ * starting with the destination and working your way back to the
+ * source. The value true indicates that edge is being
+ * considered for traversal in the natural, forward direction.
+ **/
+ boolean test(Graph.Edge edge, boolean forward);
+ }
+
+ /**
+ * @see #findPath(Graph, Object, Graphs.EdgeSelector, Graphs.NodeSelector)
+ **/
+ public interface NodeSelector {
+ boolean test(Object node);
+ }
+
+ public final static class EqualityTerminator implements NodeSelector {
+ private Object m_node;
+
+ /**
+ * @throws NullPointerException if node is null
+ **/
+ public EqualityTerminator(Object node) {
+ if (node==null) { throw new NullPointerException("node"); }
+
+ m_node = node;
+ }
+
+ public boolean test(Object node) {
+ return m_node.equals(node);
+ }
+ }
+
+}
diff --git a/ccm-core/src/main/java/com/arsdigita/util/Tree.java b/ccm-core/src/main/java/com/arsdigita/util/Tree.java
new file mode 100755
index 000000000..cfdc21e0a
--- /dev/null
+++ b/ccm-core/src/main/java/com/arsdigita/util/Tree.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved.
+ *
+ * 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 com.arsdigita.util;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This class a represents the tree abstraction. This
+ * implementation takes a recursive definition where a tree is a root node
+ * connected to other (sub)trees.
+ *
+ * This implementation allows the same node to be used in more than position
+ * in the tree. For example, you can do something like this:
+ *
+ *
+ * Tree aa = new Tree("a");
+ * Tree bb = aa.addChild("b");
+ * bb.addChild("a");
+ * aa.addChild("c");
+ *
+ *
+ * This can be visualized as follows:
+ *
+ *
+ * a
+ * / \
+ * / \
+ * b c
+ * \
+ * \
+ * a
+ *
+ *
+ * The only ways to add node to the tree is through the {@link #Tree(Object)
+ * constructor} and the {@link #addChild(Object)} {@link #addChild(Object,
+ * Object)} methods.
+ *
+ * @author Vadim Nasardinov (vadimn@redhat.com)
+ * @version $Date$
+ * @since 2003-01-23
+ **/
+public class Tree {
+ private Tree m_parent;
+ private final Object m_root;
+ private final List m_children;
+ private String m_label;
+
+ /**
+ * @param root the root node of the tree
+ * @pre root != null
+ **/
+ public Tree(Object root) {
+ this(null, root);
+ }
+
+ private Tree(Tree parent, Object root) {
+ m_parent = parent;
+ m_root = root;
+ m_children = new ArrayList();
+ }
+
+ public void setLabel(String label) {
+ m_label = label;
+ }
+
+ public String getLabel() {
+ return m_label;
+ }
+
+ /**
+ * Returns the root of this tree.
+ **/
+ public Object getRoot() {
+ return m_root;
+ }
+
+ /**
+ * Adds a child element to the root of this tree. Returns the subtree rooted
+ * at the newly created node.
+ **/
+ public Tree addChild(Object child, Object edge) {
+ Tree tree = new Tree(this, child);
+ m_children.add(new EdgeTreePair(edge, tree));
+ return tree;
+ }
+
+ /**
+ * A shortcut for addChild(child, null).
+ *
+ * @see #addChild(Object, Object)
+ **/
+ public Tree addChild(Object child) {
+ return addChild(child, null);
+ }
+
+ /**
+ * Adds subtree to the root node of this tree.
+ *
+ * Note: This doesn't check for
+ * cycles. If you do something like,
+ *
+ *
+ * tree.addSubtree(tree);
+ *
+ *
+ * you're on your own. I'll add a check for cycles like this when I have
+ * the time. (This probably means never.)
+ *
+ * @pre subtree != null && subtree.getParent() == 0
+ **/
+ public void addSubtree(Tree subtree, Object edge) {
+ Assert.exists(subtree, "subtree");
+ Assert.isTrue(subtree.getParent() == null, "parent must be null");
+ subtree.m_parent = this;
+ m_children.add(new EdgeTreePair(edge, subtree));
+ }
+
+ /**
+ * A shortcut for addSubtree(subtree,null).
+ *
+ * @see #addSubtree(Tree, Object)
+ **/
+ public void addSubtree(Tree subtree) {
+ addSubtree(subtree, null);
+ }
+
+ /**
+ * Returns the tree rooted at the parent node of the root of this tree or
+ * null, if the root of this tree has no parent node.
+ *
+ **/
+ public Tree getParent() {
+ return m_parent;
+ }
+
+ /**
+ * Returns the list of {@link Tree.EdgeTreePair edge-tree pairs} parented to
+ * the root node of this tree in the order in which they were initially
+ * added. Manipulating the returned list does not affect this tree. For
+ * example, if you remove an element from the list, no changes are made to
+ * this tree.
+ **/
+ public List getSubtrees() {
+ return new ArrayList(m_children);
+ }
+
+ /**
+ * Returns a copy of this tree. The returned copy does not have a parent
+ * tree. In other words, the returned tree is no longer a part of a bigger
+ * tree, even if this tree was.
+ *
+ @ return.getParent() == null
+ **/
+ public Tree copy() {
+ Tree result = new Tree(getRoot());
+ copyRecurse(this, result);
+ return result;
+ }
+
+ private static void copyRecurse(Tree from, Tree to) {
+ Iterator children = from.getSubtrees().iterator();
+ while ( children.hasNext() ) {
+ EdgeTreePair pair = (EdgeTreePair) children.next();
+ Tree result = to.addChild(pair.getTree().getRoot(), pair.getEdge());
+ copyRecurse(pair.getTree(), result);
+ }
+ }
+
+ public int nodeCount() {
+ return nodeCountRecurse(this);
+ }
+
+ private static int nodeCountRecurse(Tree tree) {
+ int result = 1;
+ for ( Iterator ii=tree.getSubtrees().iterator(); ii.hasNext(); ) {
+ EdgeTreePair pair = (EdgeTreePair) ii.next();
+ result += nodeCountRecurse(pair.getTree());
+ }
+ return result;
+ }
+
+ /**
+ * Returns the maximum depth of the tree.
+ **/
+ public int depth() {
+ return depthRecurse(this);
+ }
+
+ private static int depthRecurse(Tree tree) {
+ if ( tree.getSubtrees().size() == 0 ) {
+ return 1;
+ }
+ int maxDepth = 0;
+ for (Iterator ii=tree.getSubtrees().iterator(); ii.hasNext(); ) {
+ EdgeTreePair pair = (EdgeTreePair) ii.next();
+ int depth = depthRecurse(pair.getTree());
+ if ( depth > maxDepth ) {
+ maxDepth = depth;
+ }
+ }
+ return maxDepth + 1;
+ }
+
+ /**
+ * Returns the list of trees such that each of the returned trees is rooted
+ * at the ancestor nodes of this tree or an empty list, if the root of this
+ * tree has no ancestors. The closer ancestors appear first in the list.
+ **/
+ public List getAncestors() {
+ List result = new ArrayList();
+ if ( getParent() == null ) {
+ return result;
+ }
+ ancestorsRecurse(getParent(), result);
+ return result;
+ }
+
+ private static void ancestorsRecurse(Tree node, List accumulator) {
+ accumulator.add(node);
+ if ( node.getParent() != null ) {
+ ancestorsRecurse(node.getParent(), accumulator);
+ }
+ }
+
+ /**
+ * Takes a list of trees and returns a new list where each tree from the
+ * passed in list is replaced with its root node.
+ *
+ * @pre trees != null
+ **/
+ public static List treesToNodes(List trees) {
+ Assert.exists(trees, "trees");
+ List result = new ArrayList();
+ for (Iterator ii=trees.iterator(); ii.hasNext(); ) {
+ result.add(((Tree) ii.next()).getRoot());
+ }
+ return result;
+ }
+
+ /**
+ * Nodes in a tree are connected with edges. An edge can be object that
+ * characterizes the relationship between the parent and the child nodes.
+ * For example, if you use the tree to represent the composition structure
+ * of XSLT stylesheets, then the edge object may be a string with one of two
+ * possible values: "xsl:import" and "xsl:include", allowing you to
+ * distinguish the method by which the parent stylesheet incorporates the
+ * child.
+ *
+ * The edge-tree pair class represents an order pair where the first
+ * element is the edge, and the second is the subtree rooted at the head
+ * node of the edge.
+ **/
+ public static class EdgeTreePair {
+ private Object m_edge;
+ private Tree m_tree;
+
+ private EdgeTreePair(Object edge, Tree tree) {
+ m_edge = edge;
+ m_tree = tree;
+ }
+
+ public Object getEdge() {
+ return m_edge;
+ }
+
+ /**
+ * Returns the subtree rooted at the head node of the edge.
+ **/
+ public Tree getTree() {
+ return m_tree;
+ }
+ }
+}