CCM NG: Ported some classes from the com.arsdigita.util package which are used by other classes

git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@4252 8810af33-2d31-482b-a856-94f89814c4df
pull/2/head
jensp 2016-09-03 15:11:36 +00:00
parent 02b61b7ddc
commit 72b017faee
6 changed files with 1223 additions and 0 deletions

View File

@ -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 <a
* href="http://mathworld.wolfram.com/Graph.html">graphs</a> 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 <code>true</code> if the graph has this node.
**/
boolean hasNode(Object node);
/**
* Returns <code>true</code> 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 <code>addEdge(new Graph.Edge(tail, head,
* label))</code>.
*
* @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 <code>removeEdge(new Graph.Edge(tail, head,
* label))</code>.
*
* @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 <code>getOutgoingEdges(node).size()</code>.
*
* @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 <em>tail</em> and the second the
* <em>head</em>.
*
* <p>Implementing classes are expected to supply a constructor of the form
* <code>Graph.Edge(Object tail, Object head, Object label)</code>. </p>
**/
interface Edge {
/**
* Returns the tail node of the edge.
*
* @see #getHead()
**/
Object getTail();
/**
* Returns the head node of the edge.
*
* @see #getTail()
**/
Object getHead();
/**
* Returns the label associated with this edge. The label can be
* anything, depending on your particular graph. For example, if your
* nodes represent cities and edges represent freeways, then the label
* can be an <code>Float</code> representing the the distance (the
* length of the route).
**/
Object getLabel();
}
}

View File

@ -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());
}
}

View File

@ -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.
*
* <p>For example, if you choose to pretty-print your graph in the DOT
* language, then the graph attributes section may look like so:</p>
*
* <pre>
* 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.
*
* <pre>
* digraph tree {
* A -> B -> C -> D;
* B -> E -> F;
* C -> G;
* D -> I;
* D -> J -> H;
* }
* </pre>
*
* <p>Example implementation:</p>
*
* <pre>
* public String formatNode(Object node) {
* return node == null ? null : ((ObjectType) node).getName();
* }
* </pre>
**/
String nodeName(Object node);
/**
* Returns [bracketed] node attributes.
*
* <pre>
* digraph g {
* C<strong>[label="The C Language"]</strong>;
* J<strong[label="The Java Language"]</strong>;
* C -> J;
* }
* </pre>
**/
String nodeAttributes(Object node);
/**
* Returns a short textual label describing the edge.
**/
String edge(Object edge);
}

View File

@ -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 <code>equals(Object)</code> and <code>hashCode()</code>
* methods.
*
* <p>This class permits the <code>null</code> node.</p>
*
* <p><strong>This implementation is not synchronized.</strong>.</p>
*
* @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.
*
* <pre>
* 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"];
* }
* </pre>
*
* <p>Note that to get a neat printable representation, each node and edge
* label must have a short printable representation.</p>
**/
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();
}
}

View File

@ -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 <code>graph</code> 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 <code>null</code>.
**/
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 <code>graph</code> looking for path that starts
* with <code>startNode</code> and terminates with a node that satisfies the
* test specified by <code>terminator</code> and consists only of those
* edges that satisfy the test specified by <code>selector</code>.
*
* @return a path from start node to a node that satisfies the condition
* specified by <code>terminator</code>, such that all edges in the path
* satisfy the condition specified by <code>selector</code>. 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 <code>edgePath</code> is not a valid
* path. For example, <code>(a->b, c->d, d->e)</code> is invalid, because
* the edge between <code>b</code> and <code>c</code> is missing.
* @throws NullPointerException if <code>edgePath</code> is <code>null</code>
**/
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 <code>start</code>, including the
* <code>start</code> 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 <code>gg</code> 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 <a
* href="http://www.research.att.com/sw/tools/graphviz/refs.html">DOT
* language</a>.
*
* @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 <code>false</code> 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 <code>true</code> 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 <code>node</code> 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);
}
}
}

View File

@ -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 <a
* href="http://mathworld.wolfram.com/Tree.html">tree</a> abstraction. This
* implementation takes a recursive definition where a tree is a root node
* connected to other (sub)trees.
*
* <p>This implementation allows the same node to be used in more than position
* in the tree. For example, you can do something like this: </p>
*
* <pre>
* Tree aa = new Tree("a");
* Tree bb = aa.addChild("b");
* bb.addChild("a");
* aa.addChild("c");
* </pre>
*
* <p>This can be visualized as follows:</p>
*
* <pre>
* a
* / \
* / \
* b c
* \
* \
* a
* </pre>
*
* <p>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. </p>
*
* @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 <code>addChild(child, null)</code>.
*
* @see #addChild(Object, Object)
**/
public Tree addChild(Object child) {
return addChild(child, null);
}
/**
* Adds <code>subtree</code> to the root node of this tree.
*
* <p><span style="color: FireBrick;">Note</span>: This doesn't check for
* cycles. If you do something like,</p>
*
* <pre>
* tree.addSubtree(tree);
* </pre>
*
* <p>you're on your own. I'll add a check for cycles like this when I have
* the time. (This probably means never.)</p>
*
* @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 <code>addSubtree(subtree,null)</code>.
*
* @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
* <code>null</code>, 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.
*
* <p>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.</p>
**/
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;
}
}
}