Tree Node
//package org.j4me.collections; /** * <code>TreeNode</code> objects can be combined into a tree. The tree is not * a special tree such as a balanced tree. It can have any number of levels * and each node can have any number of children. * <p> * Each tree node may have at most one parent and 0 or more children. * <code>TreeNode</code> provides operations for examining and modifying a node's * parent and children. A node's tree is the set of all nodes that can be * reached by starting at the node and following all the possible links to * parents and children. A node with no parent is the root of its tree; a * node with no children is a leaf. A tree may consist of many subtrees, * each node acting as the root for its own subtree. * <p> * Every <code>TreeNode</code> can hold a reference to one user object. It is up * to the developer to decide what the object is and how to use it. For * example in maintaining the golf course tree the user object is an * <code>ICourseElement</code>. * <p> * <i>This is not a thread safe class.</i> If used from multiple threads it * must be manually sychronized by your code. */ public class TreeNode { /** * This node's parent node. If this is the root of the tree then * the parent will be <code>null</code>. */ private TreeNode parent; /** * An array of all this node's child nodes. The array will always * exist (i.e. never <code>null</code>) and be of length zero if this is * a leaf node. * <p> * This is an array instead of a <code>Vector</code> to favor speed of * accessing the children. The array takes longer on adds because * of array copying and management. However to get the array it * can just be returned instead of creating a new array from the * <code>Vector</code> each time. The tree is used frequently enough for * reading course elements that this difference makes a small impact * on rendering. */ private TreeNode[] children = new TreeNode[0]; /** * Constructs a tree node object. It can become the root of a tree. * Or it can become a child of another node by calling the other node's * <code>add</code> method. * <p> * There is no user object attached to the node. Call <code>setUserObject</code> * to attach one. */ public TreeNode () { // Nothing needed. } /** * Constructs a tree node object. It can become the root of a tree. * Or it can become a child of another node by calling the other node's * <code>add</code> method. * * @param userObject is an object this node encapsulates. It is up to * the developer to maintain its type. To get the object back out * call <code>getUserObject</code>. */ public TreeNode (Object userObject) { m_userData = userObject; } /** * Adds the <code>child</code> node to this container making this its parent. * * @param child is the node to add to the tree as a child of <code>this</code> * * @param index is the position within the children list to add the * child. It must be between 0 (the first child) and the * total number of current children (the last child). If it is * negative the child will become the last child. */ public void add (TreeNode child, int index) { // Add the child to the list of children. if ( index < 0 || index == children.length ) // then append { TreeNode[] newChildren = new TreeNode[ children.length + 1 ]; System.arraycopy( children, 0, newChildren, 0, children.length ); newChildren[children.length] = child; children = newChildren; } else if ( index > children.length ) { throw new IllegalArgumentException("Cannot add child to index " + index + ". There are only " + children.length + " children."); } else // insert { TreeNode[] newChildren = new TreeNode[ children.length + 1 ]; if ( index > 0 ) { System.arraycopy( children, 0, newChildren, 0, index ); } newChildren[index] = child; System.arraycopy( children, index, newChildren, index + 1, children.length - index ); children = newChildren; } // Set the parent of the child. child.parent = this; } /** * Adds the <code>child</code> node to this container making this its parent. * The child is appended to the list of children as the last child. */ public void add (TreeNode child) { add( child, -1 ); } /** * Removes the child at position <code>index</code> from the tree. * * @param index is the position of the child. It should be between * 0 (the first child) and the total number of children minus 1 * (the last child). * @return The removed child node. This will be <code>null</code> if * no child exists at the specified <code>index</code>. */ public TreeNode remove (int index) { if ( index < 0 || index >= children.length ) throw new IllegalArgumentException("Cannot remove element with index " + index + " when there are " + children.length + " elements."); // Get a handle to the node being removed. TreeNode node = children[index]; node.parent = null; // Remove the child from this node. TreeNode[] newChildren = new TreeNode[ children.length - 1 ]; if ( index > 0 ) { System.arraycopy( children, 0, newChildren, 0, index ); } if ( index != children.length - 1 ) { System.arraycopy( children, index + 1, newChildren, index, children.length - index - 1 ); } children = newChildren; return node; } /** * Removes this node from its parent. This node becomes the root * of a subtree where all of its children become first level * nodes. * <p> * Calling this on the root node has no effect. */ public void removeFromParent () { if ( parent != null ) { int position = this.index(); parent.remove( position ); parent = null; } } /** * Gets the parent node of this one. * * @return The parent of this node. This will return <code>null</code> * if this node is the root node in the tree. */ public TreeNode getParent () { return parent; } /** * Returns if this node is the root node in the tree or not. * * @return <code>true</code> if this node is the root of the tree; * <code>false</code> if it has a parent. */ public boolean isRoot () { if ( parent == null ) { return true; } else { return false; } } /** * Gets a list of all the child nodes of this node. * * @return An array of all the child nodes. The array will * be the size of the number of children. A leaf node * will return an empty array, not <code>null</code>. */ public TreeNode[] children () { return children; } /** * Returns if this node has children or if it is a leaf * node. * * @return <code>true</code> if this node has children; <code>false</code> * if it does not have any children. */ public boolean hasChildren () { if ( children.length == 0 ) { return false; } else { return true; } } /** * Gets the position of this node in the list of siblings * managed by the parent node. This node can be obtained * by <code>this = parent.children[this.index()]</code>. * * @return The index of the child array of this node's * parent. If this is the root node it will return -1. */ public int index () { if ( parent != null ) { for ( int i = 0; ; i++ ) { Object node = parent.children[i]; if ( this == node ) { return i; } } } // Only ever make it here if this is the root node. return -1; } /** * Gets this node's depth in the tree. The root node will * have a depth of 0, first-level nodes will have a depth * of 1, and so on. * * @return The depth of this node in the tree. */ public int depth () { int depth = recurseDepth( parent, 0 ); return depth; } /** * Recursive helper method to calculate the depth of a node. * The caller should pass its parent and an initial depth of 0. * <p> * A recursive approach is used so that when a node that is * part of a tree is removed from that tree, we do not need * to recalculate the depth of every node in that subtree. * * @param node is the node to recursively check for its depth. * This should be set to <code>parent</code> by the caller. * @param depth is the depth of the current node (i.e. the * child of <code>node</code>). This should be set to 0 by the * caller. */ private int recurseDepth (TreeNode node, int depth) { if ( node == null ) // reached top of tree { return depth; } else { return recurseDepth( node.parent, depth + 1 ); } } /** * A handle to the programmer assigned object encapsulated by this * node. This will be <code>null</code> when the user has not assigned * any data to this node. */ private Object m_userData; /** * Attaches a user defined object to this node. Only one * object can be attached to a node. * * @param userObject is the programmer defined object to * attach to this node in the tree. Set it to <code>null</code> * to clear any objects. */ public void setUserObject (Object userObject) { m_userData = userObject; } /** * Gets the user defined object attached to this node. It * must be cast back to what it was inserted as. It is up * to the developer to make this cast. * * @return The programmer defined object attached to this * node in the tree. Returns <code>null</code> if no object is * attached. */ public Object getUserObject () { return m_userData; } } -------- package org.j4me.collections; import j2meunit.framework.*; /** * Tests the <code>TreeNode</code> object. It is a generic tree (integer.e. not a balanced tree * or some other specialized tree). * * @see org.j4me.collections.TreeNode */ public class TreeNodeTest extends TestCase { public TreeNodeTest () { super(); } public TreeNodeTest (String name, TestMethod method) { super( name, method ); } public Test suite () { TestSuite suite = new TestSuite(); suite.addTest(new TreeNodeTest("testIllegalOperations", new TestMethod() { public void run(TestCase tc) {((TreeNodeTest) tc).testIllegalOperations(); } })); suite.addTest(new TreeNodeTest("testUserObjects", new TestMethod() { public void run(TestCase tc) {((TreeNodeTest) tc).testUserObjects(); } })); suite.addTest(new TreeNodeTest("testRoot", new TestMethod() { public void run(TestCase tc) {((TreeNodeTest) tc).testRoot(); } })); suite.addTest(new TreeNodeTest("testOneChild", new TestMethod() { public void run(TestCase tc) {((TreeNodeTest) tc).testOneChild(); } })); suite.addTest(new TreeNodeTest("testMultipleChildren", new TestMethod() { public void run(TestCase tc) {((TreeNodeTest) tc).testMultipleChildren(); } })); suite.addTest(new TreeNodeTest("testMultipleLevels", new TestMethod() { public void run(TestCase tc) {((TreeNodeTest) tc).testMultipleLevels(); } })); suite.addTest(new TreeNodeTest("testBigTree", new TestMethod() { public void run(TestCase tc) {((TreeNodeTest) tc).testBigTree(); } })); return suite; } /** * Tests the tree nodes defend themselves again invalid parameters that would * be the result of programming errors. */ public void testIllegalOperations () { // Test cannot add a child node to position greater than available number of children. boolean caughtException = false; try { TreeNode root = new TreeNode(); root.add( new TreeNode(), 1 ); } catch (IllegalArgumentException e) { caughtException = true; } catch (Throwable t) { String actualExceptionName = t.getClass().getName(); fail( "Expected exception 'IllegalArgumentException' and got '" + actualExceptionName + "'." ); } if ( caughtException == false ) { fail( "Expected exception 'IllegalArgumentException' but no exceptions caught." ); } // Test remove a child node from position greater than available number of children. caughtException = false; try { TreeNode root = new TreeNode(); root.remove( 1 ); } catch (IllegalArgumentException e) { caughtException = true; } catch (Throwable t) { String actualExceptionName = t.getClass().getName(); fail( "Expected exception 'IllegalArgumentException' and got '" + actualExceptionName + "'." ); } if ( caughtException == false ) { fail( "Expected exception 'IllegalArgumentException' but no exceptions caught." ); } } /** * Tests that user objects can be attached and extracted from * a tree node. */ public void testUserObjects () { String user = "This is a test string.\n It should go in and come out the same."; Integer user2 = new Integer(13); // Create a node with the user defined object. TreeNode node = new TreeNode( user ); // Get the object back out. Object obj = node.getUserObject(); String result = (String)obj; assertEquals("The attached string should be the same one as put in.", user, result); // Try erasing the string. node.setUserObject( null ); obj = node.getUserObject(); assertNull("No user object should be attached to the node.", obj); // Put a different object back in and pull it out. node.setUserObject( user2 ); obj = node.getUserObject(); Integer intResult = (Integer)obj; assertEquals("The attached Integer should be the same one as put in.", user2, intResult); } /** * Tests assertions about a root node. This is a very simple test * and should be right before working with children. */ public void testRoot () { TreeNode root = new TreeNode(); // Verify properties of the root node. boolean isRoot = root.isRoot(); assertTrue("The node should be the root node.", isRoot); int index = root.index(); assertEquals("The root node should have an index of -1 since it has no parent.", -1, index); int depth = root.depth(); assertEquals("The root node should have depth of 0 since it has no parent.", 0, depth); TreeNode parent = root.getParent(); assertNull("The root node should not have a parent node.", parent); boolean hasChildren = root.hasChildren(); assertTrue("The root node should not have any children because none have been added.", hasChildren == false); TreeNode[] children = root.children(); int childLength = children.length; assertEquals("The root node should not have any children because none have been added.", 0, childLength); // Verify the following method doesn't throw an exception. root.removeFromParent(); } /** * Test a tree that has a root and just one child. This is a simple * test to verify the child has properties appropriately set before * testing more complex trees. */ public void testOneChild () { // Create the tree. TreeNode root = new TreeNode(); TreeNode child = new TreeNode(); root.add( child ); // Verify properties of the child node. boolean isRoot = child.isRoot(); assertTrue("The node should not be the root node.", isRoot == false); int index = child.index(); assertEquals("The child node should have an index of 0 since it is the only child.", 0, index); int depth = child.depth(); assertEquals("The child node should have depth of 1 since it is a first level node.", 1, depth); TreeNode parent = child.getParent(); assertEquals("The child's parent should be the root node.", root, parent); boolean hasChildren = child.hasChildren(); assertTrue("The child node should not have any children because none have been added.", hasChildren == false); hasChildren = root.hasChildren(); assertTrue("The root node should now have children.", hasChildren == true); TreeNode[] children = child.children(); int childLength = children.length; assertEquals("The child node should not have any children because none have been added.", 0, childLength); children = root.children(); childLength = children.length; assertEquals("The root node should have 1 child.", 1, childLength); TreeNode theChild = children[0]; assertEquals("The root's child should be child.", child, theChild); // Now remove the child from the root. child.removeFromParent(); isRoot = child.isRoot(); assertTrue("The child node should now be the root of its own subtree.", isRoot == true); depth = child.depth(); assertEquals("The child node should have depth of 0 since it is now the root.", 0, depth); parent = child.getParent(); assertEquals("The child should not have a parent since it is now the root.", null, parent); hasChildren = root.hasChildren(); assertTrue("The root node should no longer have any children.", hasChildren == false); } /** * This tests a tree only 1 level deep, but there are several first * level children. This is useful to test that siblings are kept * correctly before testing multiple level trees. */ public void testMultipleChildren () { TreeNode root = new TreeNode(); TreeNode child1 = new TreeNode(); TreeNode child2 = new TreeNode(); TreeNode child3 = new TreeNode(); // Add the children. root.add( child1, 0 ); root.add( child3, 1 ); // Add with index, but really appending child root.add( child2, 1 ); // Add out of order to test insertion in the middle of the children TreeNode[] children = root.children(); assertEquals("child1 should be the first child.", child1, children[0]); assertEquals("child2 should be the second child.", child2, children[1]); assertEquals("child3 should be the third child.", child3, children[2]); // Verify the properties of the children. for ( int i = 0; i < children.length; i++ ) { TreeNode child = children[i]; int index = child.index(); assertEquals("child" + (i+1) + " should have an index of " + i, i, index); boolean isRoot = child.isRoot(); assertTrue("child" + (i+1) + " should not be the root node.", isRoot == false); int depth = child.depth(); assertEquals("child" + (i+1) + " should have depth of 1 since it is a first level node.", 1, depth); TreeNode parent = child.getParent(); assertEquals("child" + (i+1) + "'s parent should be the root node.", root, parent); boolean hasChildren = child.hasChildren(); assertTrue("child" + (i+1) + " should not have any children because none have been added.", hasChildren == false); } // Remove the middle child. TreeNode removed = root.remove( 1 ); assertEquals("The removed node should be child2.", child2, removed); assertEquals("child2 should now have a depth of 0.", 0, removed.depth()); children = root.children(); assertEquals("The root should now have 2 children.", 2, children.length); assertEquals("child3 should be the second child.", child3, children[1]); // Add the middle child back in. root.add( removed, 1 ); children = root.children(); TreeNode node = children[1]; assertEquals("The second node should be child2 again.", child2, node); assertEquals("child2's parent should be the root again.", root, child2.getParent()); assertEquals("child2's depth should be 1 again.", 1, child2.depth()); // Remove all the children. root.remove( 1 ); root.remove( 1 ); child1.removeFromParent(); assertTrue("The root should not have any children now.", root.hasChildren() == false); } /** * This tests that multiple levels of the tree work correctly. Each * level has only 1 child. This is a simple test to verify depth * works before moving onto more complex trees. */ public void testMultipleLevels () { TreeNode root = new TreeNode(); TreeNode depth1 = new TreeNode(); TreeNode depth2 = new TreeNode(); TreeNode depth3 = new TreeNode(); // Add the children. root.add( depth1 ); depth2.add( depth3 ); depth1.add( depth2 ); // Verify the properties of the nodes. assertTrue("The root should have 1 child.", root.hasChildren() == true); assertEquals("The root should have a depth of 0.", 0, root.depth()); assertEquals("The root should not have a parent.", null, root.getParent()); assertTrue("depth1 should have 1 child.", depth1.hasChildren() == true); assertEquals("depth1 should have a depth of 1.", 1, depth1.depth()); assertEquals("depth1 should have root as its parent.", root, depth1.getParent()); assertTrue("depth2 should have 1 child.", depth2.hasChildren() == true); assertEquals("depth2 should have a depth of 2.", 2, depth2.depth()); assertEquals("depth2 should have depth1 as its parent.", depth1, depth2.getParent()); assertTrue("depth3 should have not have any children.", depth3.hasChildren() == false); assertEquals("depth3 should have a depth of 3.", 3, depth3.depth()); assertEquals("depth3 should have depth2 as its parent.", depth2, depth3.getParent()); // Cut the tree in 1/2. depth2.removeFromParent(); assertTrue("depth2 should now be a root.", depth2.isRoot() == true); assertEquals("depth2 should now have a depth of 0.", 0, depth2.depth()); assertEquals("depth3 should now have a depth of 1.", 1, depth3.depth()); assertTrue("depth1 should not have any children now.", depth1.hasChildren() == false); } /** * This tests a tree with multiple varying depths and multiple * varying amounts of children at each depth. */ public void testBigTree () { String testData = ""; TreeNode root = new TreeNode(); TreeNode d1c0 = new TreeNode( testData ); TreeNode d1c1 = new TreeNode( testData ); TreeNode d1c2 = new TreeNode( testData ); TreeNode d2c1c0 = new TreeNode( testData ); TreeNode d2c1c1 = new TreeNode( testData ); TreeNode d2c1c2 = new TreeNode( testData ); TreeNode d2c2c0 = new TreeNode( testData ); TreeNode d2c2c1 = new TreeNode( testData ); TreeNode d3c2c0c0 = new TreeNode( testData ); TreeNode d3c2c0c1 = new TreeNode( testData ); root.add( d1c0 ); root.add( d1c1 ); root.add( d1c2 ); d1c1.add( d2c1c1 ); // Second child d1c1.add( d2c1c0, 0 ); // First child d1c1.add( d2c1c2 ); // Third child d1c2.add( d2c2c0 ); d2c2c0.add( d3c2c0c0 ); d2c2c0.add( d3c2c0c1 ); d1c2.add( d2c2c1 ); // Verify the tree structure. assertEquals("root should be the parent of d1c0", root, d1c0.getParent()); assertEquals("root should be the parent of d1c1", root, d1c1.getParent()); assertEquals("root should be the parent of d1c2", root, d1c2.getParent()); assertEquals("d1c1 should be the parent of d2c1c0", d1c1, d2c1c0.getParent()); assertEquals("d1c1 should be the parent of d2c1c1", d1c1, d2c1c1.getParent()); assertEquals("d1c1 should be the parent of d2c1c2", d1c1, d2c1c2.getParent()); assertEquals("d1c2 should be the parent of d2c2c0", d1c2, d2c2c0.getParent()); assertEquals("d1c2 should be the parent of d2c2c1", d1c2, d2c2c1.getParent()); assertEquals("d2c2c0 should be the parent of d3c2c0c0", d2c2c0, d3c2c0c0.getParent()); assertEquals("d2c2c0 should be the parent of d3c2c0c1", d2c2c0, d3c2c0c1.getParent()); // Verify the depths of some of the nodes. assertEquals("root should have a depth of 0", 0, root.depth()); assertEquals("d1c1 should have a depth of 1", 1, d1c1.depth()); assertEquals("d2c2c0 should have a depth of 2", 2, d2c2c0.depth()); assertEquals("d3c2c0c1 should have a depth of 3", 3, d3c2c0c1.depth()); // Verify we can traverse the tree from root to d3c2c0c0. TreeNode node = root.children()[2]; // d1c2 node = node.children()[0]; // d2c2c0 node = node.children()[0]; // d3c2c0c0 assertEquals("We should have tranversed the tree to d3c2c0c0.", d3c2c0c0, node); assertEquals("Should have test data as user object from node d3c2c0c0.", testData, node.getUserObject()); } }