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-94f89814c4dfpull/2/head
parent
02b61b7ddc
commit
72b017faee
|
|
@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue