package prefuse.data; import java.util.Iterator; import java.util.logging.Logger; import prefuse.util.PrefuseConfig; import prefuse.util.collections.IntIterator; /** * <p>Graph subclass that models a tree structure of hierarchical * parent-child relationships. For each edge, the source node is considered * the parent, and the target node is considered the child. For the tree * structure to be valid, each node can have at most one parent, and hence * only one edge for which that node is the target. In addition to the methods * of the Graph class, the tree also supports methods for navigating the tree * structure, such as accessing parent or children nodes and next or previous * sibling nodes (siblings are children nodes with a shared parent). Unlike the * graph class, the default source and target key field names are renamed to * {@link #DEFAULT_SOURCE_KEY} and {@link #DEFAULT_TARGET_KEY}. * Like the {@link Graph} class, Trees are backed by node and edge * tables, and use {@link prefuse.data.Node} and * {@link prefuse.data.Edge} instances to provide object-oriented access * to nodes and edges.</p> * * <p>The Tree class does not currently enforce that the graph structure remain * a valid tree. This is to allow a chain of editing operations that may break * the tree structure at some point before repairing it. Use the * {@link #isValidTree()} method to test the validity of a tree.</p> * * <p>By default, the {@link #getSpanningTree()} method simply returns a * reference to this Tree instance. However, if a spanning tree is created at a * new root u sing the {@link #getSpanningTree(Node)} method, a new * {@link SpanningTree} instance is generated.</p> * * @author <a href="http://jheer.org">jeffrey heer</a> */ public class Tree extends Graph { private static final Logger s_logger = Logger.getLogger(Tree.class.getName()); /** Default data field used to denote the source node in an edge table */ public static final String DEFAULT_SOURCE_KEY = PrefuseConfig.get("data.tree.sourceKey"); /** Default data field used to denote the target node in an edge table */ public static final String DEFAULT_TARGET_KEY = PrefuseConfig.get("data.tree.targetKey"); // implement as graph with limitations on edge settings // catch external modification events and throw exceptions as necessary /** The node table row number for the root node of the tree. */ protected int m_root = -1; // ------------------------------------------------------------------------ // Constructors /** * Create a new, empty Tree. */ public Tree() { super(new Table(), false); } /** * Create a new Tree. * @param nodes the backing table to use for node data. * Node instances of this graph will get their data from this table. * @param edges the backing table to use for edge data. * Edge instances of this graph will get their data from this table. */ public Tree(Table nodes, Table edges) { this(nodes, edges, DEFAULT_SOURCE_KEY, DEFAULT_TARGET_KEY); } /** * Create a new Tree. * @param nodes the backing table to use for node data. * Node instances of this graph will get their data from this table. * @param edges the backing table to use for edge data. * Edge instances of this graph will get their data from this table. * @param sourceKey data field used to denote the source node in an edge * table * @param targetKey data field used to denote the target node in an edge * table */ public Tree(Table nodes, Table edges, String sourceKey, String targetKey) { this(nodes, edges, DEFAULT_NODE_KEY, sourceKey, targetKey); } /** * Create a new Tree. * @param nodes the backing table to use for node data. * Node instances of this graph will get their data from this table. * @param edges the backing table to use for edge data. * Edge instances of this graph will get their data from this table. * @param nodeKey data field used to uniquely identify a node. If this * field is null, the node table row numbers will be used * @param sourceKey data field used to denote the source node in an edge * table * @param targetKey data field used to denote the target node in an edge * table */ public Tree(Table nodes, Table edges, String nodeKey, String sourceKey, String targetKey) { super(nodes, edges, false, nodeKey, sourceKey, targetKey); for ( IntIterator rows = nodes.rows(); rows.hasNext(); ) { int n = rows.nextInt(); if ( getParent(n) < 0 ) { m_root = n; break; } } } /** * Internal method for setting the root node. * @param root the root node to set */ void setRoot(Node root) { m_root = root.getRow(); } /** * @see prefuse.data.Graph#createLinkTable() */ protected Table createLinkTable() { Table links = super.createLinkTable(); links.addColumns(TREE_LINKS_SCHEMA); return links; } /** * @see prefuse.data.Graph#updateDegrees(int, int, int, int) */ protected void updateDegrees(int e, int s, int t, int incr) { super.updateDegrees(e, s, t, incr); int od = getOutDegree(s); if ( incr > 0 ) { // if added, child index is the last index in child array m_links.setInt(t, CHILDINDEX, od-1); } else if ( incr < 0 ) { // if removed, we renumber each child in the array int[] links = (int[])m_links.get(s, OUTLINKS); for ( int i=0; i<od; ++i ) { int n = getTargetNode(links[i]); m_links.setInt(n, CHILDINDEX, i); } m_links.setInt(t, CHILDINDEX, -1); } } // ------------------------------------------------------------------------ // Tree Mutators /** * Add a new root node to an empty Tree. * @return the node id (node table row number) of the new root node. */ public int addRootRow() { if ( getNodeCount() != 0 ) { throw new IllegalStateException( "Can only add a root node to an empty tree"); } return (m_root = addNodeRow()); } /** * Add a new root node to an empty Tree. * @return the newly added root Node */ public Node addRoot() { return getNode(addRootRow()); } /** * Add a child node to the given parent node. An edge between the two * will also be created. * @param parent the parent node id (node table row number) * @return the added child node id */ public int addChild(int parent) { int child = super.addNodeRow(); addChildEdge(parent, child); return child; } /** * Add a child node to the given parent node. An edge between the two * will also be created. * @param parent the parent node * @return the added child node */ public Node addChild(Node parent) { nodeCheck(parent, true); return getNode(addChild(parent.getRow())); } /** * Add a child edge between the given nodes. * @param parent the parent node id (node table row number) * @param child the child node id (node table row number) * @return the added child edge id */ public int addChildEdge(int parent, int child) { return super.addEdge(parent, child); } /** * Add a child edge between the given nodes. * @param parent the parent node * @param child the child node * @return the added child edge */ public Edge addChildEdge(Node parent, Node child) { nodeCheck(parent, true); nodeCheck(child, true); return getEdge(addChildEdge(parent.getRow(), child.getRow())); } /** * Remove a child edge from the Tree. The child node and its subtree * will also be removed from the Tree. * @param edge the edge id (edge table row number) of the edge to remove * @return true if the edge and attached subtree is successfully removed, * false otherwise */ public boolean removeChildEdge(int edge) { return removeChild(getTargetNode(edge)); } /** * Remove a child edge from the Tree. The child node and its subtree * will also be removed from the Tree. * @param e the edge to remove * @return true if the edge and attached subtree is successfully removed, * false otherwise */ public boolean removeChildEdge(Edge e) { edgeCheck(e, true); return removeChild(getTargetNode(e.getRow())); } /** * Remove a node and its entire subtree rooted at the node from the tree. * @param node the node id (node table row number) to remove * @return true if the node and its subtree is successfully removed, * false otherwise */ public boolean removeChild(int node) { while ( getChildCount(node) > 0 ) { removeChild(getLastChildRow(node)); } return removeNode(node); } /** * Remove a node and its entire subtree rooted at the node from the tree. * @param n the node to remove * @return true if the node and its subtree is successfully removed, * false otherwise */ public boolean removeChild(Node n) { nodeCheck(n, true); return removeChild(n.getRow()); } // ------------------------------------------------------------------------ // Tree Accessors /** * Get the root node id (node table row number). * @return the root node id */ public int getRootRow() { return m_root; } /** * Get the root node. * @return the root Node */ public Node getRoot() { return (Node)m_nodeTuples.getTuple(m_root); } /** * Get the child node id at the given index. * @param node the parent node id (node table row number) * @param idx the child index * @return the child node id (node table row number) */ public int getChildRow(int node, int idx) { int cc = getChildCount(node); if ( idx < 0 || idx >= cc ) return -1; int[] links = (int[])m_links.get(node, OUTLINKS); return getTargetNode(links[idx]); } /** * Get the child node at the given index. * @param node the parent Node * @param idx the child index * @return the child Node */ public Node getChild(Node node, int idx) { int c = getChildRow(node.getRow(), idx); return ( c<0 ? null : getNode(c) ); } /** * Get the child index (order number of the child) for the given parent * node id and child node id. * @param parent the parent node id (node table row number) * @param child the child node id (node table row number) * @return the index of the child, or -1 if the given child node is not * actually a child of the given parent node, or either node is * invalud. */ public int getChildIndex(int parent, int child) { if ( getParent(child) != parent ) return -1; return m_links.getInt(child, CHILDINDEX); } /** * Get the child index (order number of the child) for the given parent * and child nodes. * @param p the parent Node * @param c the child Node * @return the index of the child, or -1 if the given child node is not * actually a child of the given parent node, or either node is * invalud. */ public int getChildIndex(Node p, Node c) { return getChildIndex(p.getRow(), c.getRow()); } /** * Get the node id of the first child of the given parent node id. * @param node the parent node id (node table row number) * @return the node id of the first child */ public int getFirstChildRow(int node) { return getChildRow(node, 0); } /** * Get the first child node of the given parent node. * @param node the parent Node * @return the first child Node */ public Node getFirstChild(Node node) { return getChild(node, 0); } /** * Get the node id of the last child of the given parent node id. * @param node the parent node id (node table row number) * @return the node id of the last child */ public int getLastChildRow(int node) { return getChildRow(node, getChildCount(node)-1); } /** * Get the last child node of the given parent node. * @param node the parent Node * @return the last child Node */ public Node getLastChild(Node node) { return getChild(node, node.getChildCount()-1); } /** * Get the node id of the previous sibling of the given node id. * @param node a node id (node table row number) * @return the node id of the previous sibling, or -1 if there * is no previous sibling. */ public int getPreviousSiblingRow(int node) { int p = getParent(node); if ( p < 0 ) return -1; int[] links = (int[])m_links.get(p, OUTLINKS); int idx = m_links.getInt(node, CHILDINDEX); return ( idx<=0 ? -1 : getTargetNode(links[idx-1])); } /** * Get the previous sibling of the given node. * @param node a node * @return the previous sibling, or null if there is no previous sibling */ public Node getPreviousSibling(Node node) { int n = getPreviousSiblingRow(node.getRow()); return ( n<0 ? null : getNode(n) ); } /** * Get the node id of the next sibling of the given node id. * @param node a node id (node table row number) * @return the node id of the next sibling, or -1 if there * is no next sibling. */ public int getNextSiblingRow(int node) { int p = getParent(node); if ( p < 0 ) return -1; int[] links = (int[])m_links.get(p, OUTLINKS); int idx = m_links.getInt(node, CHILDINDEX); int max = getChildCount(p)-1; return ( idx<0 || idx>=max ? -1 : getTargetNode(links[idx+1])); } /** * Get the next sibling of the given node. * @param node a node * @return the next sibling, or null if there is no next sibling */ public Node getNextSibling(Node node) { int n = getNextSiblingRow(node.getRow()); return ( n<0 ? null : getNode(n) ); } /** * Get the depth of the given node id in the tree. * @param node a node id (node table row number) * @return the depth of the node in tree. The root node * is at a depth level of 0, with each child at a greater * depth level. -1 is returned if the input node id is not * in the tree. */ public int getDepth(int node) { if ( !getNodeTable().isValidRow(node) ) return -1; int depth = 0; if ( node!=m_root && getParent(node) < 0 ) return -1; for ( int i=node; i!=m_root && i>=0; ++depth, i=getParent(i) ); return depth; } /** * Get the number of children of the given node id. * @param node a node id (node table row number) * @return the number of child nodes for the given node */ public int getChildCount(int node) { return getOutDegree(node); } /** * Get the edge id of the edge to the given node's parent. * @param node the node id (node table row number) * @return the edge id (edge table row number) of the parent edge */ public int getParentEdge(int node) { if ( getInDegree(node) > 0 ) { int[] inlinks = (int[])m_links.get(node, INLINKS); return inlinks[0]; } else { return -1; } } /** * Get the edge to the given node's parent. * @param n a Node instance * @return the parent Edge connecting the given node to its parent */ public Edge getParentEdge(Node n) { nodeCheck(n, true); int pe = getParentEdge(n.getRow()); return ( pe < 0 ? null : getEdge(pe) ); } /** * Get a node's parent node id * @param node the child node id (node table row number) * @return the parent node id, or -1 if there is no parent */ public int getParent(int node) { int pe = getParentEdge(node); return ( pe < 0 ? -1 : getSourceNode(pe) ); } /** * Get a node's parent node * @param n the child node * @return the parent node, or null if there is no parent */ public Node getParent(Node n) { int p = getParent(n.getRow()); return ( p < 0 ? null : getNode(p) ); } // ------------------------------------------------------------------------ // Iterators /** * Get an iterator over the edge ids for edges connecting child nodes to * a given parent * @param node the parent node id (node table row number) * @return an iterator over the edge ids for edges conencting child nodes * to a given parent */ public IntIterator childEdgeRows(int node) { return super.outEdgeRows(node); } /** * Get an iterator over the edges connecting child nodes to a given parent * @param n the parent node * @return an iterator over the edges connecting child nodes to a given * parent */ public Iterator childEdges(Node n) { return super.outEdges(n); } /** * Get an iterator over the child nodes of a parent node. * @param n the parent node * @return an iterator over the child nodes of a parent node */ public Iterator children(Node n) { return super.outNeighbors(n); } // ------------------------------------------------------------------------ // Sanity Test /** * Check that the underlying graph structure forms a valid tree. * @return true if this is a valid tree, false otherwise */ public boolean isValidTree() { // TODO: write a visitor interface and use that instead? int nnodes = getNodeCount(); int nedges = getEdgeCount(); // first make sure there are n nodes and n-1 edges if ( nnodes != nedges+1 ) { s_logger.warning("Node/edge counts incorrect."); return false; } // iterate through nodes, make sure each one has the right // number of parents int root = getRootRow(); IntIterator nodes = getNodeTable().rows(); while ( nodes.hasNext() ) { int n = nodes.nextInt(); int id = getInDegree(n); if ( n==root && id > 0 ) { s_logger.warning("Root node has a parent."); return false; } else if ( id > 1 ) { s_logger.warning("Node "+n+" has multiple parents."); return false; } } // now do a traversal and make sure we visit everything int[] counts = new int[] { 0, nedges }; isValidHelper(getRootRow(), counts); if ( counts[0] > nedges ) { s_logger.warning("The tree has non-tree edges in it."); return false; } if ( counts[0] < nedges ) { s_logger.warning("Not all of the tree was visited. " + "Only "+counts[0]+"/"+nedges+" edges encountered"); return false; } return true; } /** * isValidTree's recursive helper method. */ private void isValidHelper(int node, int[] counts) { IntIterator edges = childEdgeRows(node); int ncount = 0; while ( edges.hasNext() ) { // get next edge, increment count int edge = edges.nextInt(); ++ncount; ++counts[0]; // visit the next edge int c = getAdjacentNode(edge, node); isValidHelper(c, counts); // check the counts if ( counts[0] > counts[1] ) return; } } // ------------------------------------------------------------------------ // Spanning Tree Methods /** * Returns a spanning tree over this tree. If no spanning tree * has been constructed at an alternative root, this method simply returns * a pointer to this Tree instance. If a spanning tree rooted at an * alternative node has been created, that tree is returned. * * @return a spanning tree over this tree * @see #getSpanningTree(Node) * @see Graph#clearSpanningTree() */ public Tree getSpanningTree() { return m_spanning==null ? this : m_spanning; } /** * Returns a spanning tree over this tree, rooted at the given root. If * the given root is not the same as that of this Tree, a new spanning * tree instance will be constructed, made the current spanning tree * for this Tree instance, and returned. * * To clear out any generated spanning trees use the clearSpanningTree() * method of the Graph class. After calling clearSpanningTree(), the * getSpanningTree() method (with no arguments) will return a pointer * to this Tree instance instead of any generated spanning trees. * * @param root the node at which to root the spanning tree. * @return a spanning tree over this tree, rooted at the given root * @see #getSpanningTree() * @see Graph#clearSpanningTree() */ public Tree getSpanningTree(Node root) { nodeCheck(root, true); if ( m_spanning == null ) { if ( m_root == root.getRow() ) { return this; } else { m_spanning = new SpanningTree(this, root); } } else if ( m_spanning.getRoot() != root ) { m_spanning.buildSpanningTree(root); } return m_spanning; } // ------------------------------------------------------------------------ // Tree Linkage Schema (appended to the Graph Linkage Schema) /** Links table data field storing the index number of a child node */ protected static final String CHILDINDEX = "_childIndex"; /** Schema addition to be added onto {@link Graph#LINKS_SCHEMA}. */ protected static final Schema TREE_LINKS_SCHEMA = new Schema(); static { TREE_LINKS_SCHEMA.addColumn(CHILDINDEX, int.class, new Integer(-1)); TREE_LINKS_SCHEMA.lockSchema(); } } // end of class Tree