import java.util.NoSuchElementException;
/**
* Implementation of homework 9.
*
* @author Cheng Jade
* @assignment ICS211 Homework TA9
* @date Apr 10, 2010
* @bugs None
*/
public class ChengJade9 {
/**
* The main entry point of the program.
*
* @param args
* The command line arguments (not used)
*/
public static void main(final String[] args) {
// Define two family trees, dad's side and mom's side family trees.
final FamilyTree grandpaTree = new FamilyTree();
final FamilyTree grandmaTree = new FamilyTree();
// Add elements to dad's side family tree and print out the contents.
tryAdd(grandpaTree, null, "grandpa");
tryAdd(grandpaTree, "grandpa", "aunt");
tryAdd(grandpaTree, "grandpa", "dad");
tryAdd(grandpaTree, "grandpa", "uncle");
tryAdd(grandpaTree, "aunt", "cousin1");
tryAdd(grandpaTree, "aunt", "cousin2");
tryAdd(grandpaTree, "dad", "jade");
tryAdd(grandpaTree, "uncle", "cousin3");
tryAdd(grandpaTree, "cousin1", "nephew1");
tryAdd(grandpaTree, "cousin1", "niece");
tryAdd(grandpaTree, "cousin1", "nephew2");
System.out.println("Printing grandpa's family tree...");
System.out.println(grandpaTree);
// Add elements to mom's side family tree and print out the contents.
tryAdd(grandmaTree, null, "grandma");
tryAdd(grandmaTree, "grandma", "aunt");
tryAdd(grandmaTree, "grandma", "mom");
tryAdd(grandmaTree, "grandma", "uncle1");
tryAdd(grandmaTree, "grandma", "uncle2");
tryAdd(grandmaTree, "aunt", "cousin1");
tryAdd(grandmaTree, "mom", "jade");
tryAdd(grandmaTree, "uncle1", "cousin2");
tryAdd(grandmaTree, "uncle1", "cousin3");
tryAdd(grandmaTree, "cousin3", "second niece");
tryAdd(grandmaTree, "cousin3", "second nephew");
tryAdd(grandmaTree, "uncle1", "cousin4");
tryAdd(grandmaTree, "second niece", "second niece's son");
System.out.println("Printing grandma's family tree...");
System.out.println(grandmaTree);
// Test some bad situations.
try {
System.out.println("Adding \"orphan1\" to \"nobogy\" in grandpa's family tree...");
tryAdd(grandpaTree, "nobody", "orphan1");
Thread.sleep(500);
System.out.println("\nAdding \"orphan2\" to \"null\" in grandpa's family tree...");
tryAdd(grandpaTree, null, "orphan2");
Thread.sleep(500);
final FamilyTree emptyTree = new FamilyTree();
System.out.println("\nPrinting an empty family tree...");
System.out.println(emptyTree);
} catch (final InterruptedException e) {
System.err.println(e.getMessage());
System.exit(1);
}
// Allow the Eclipse debugger to exit happily.
System.exit(0);
}
/**
* Attempts to add an item to the tree and displays an error if the add
* method is unsuccessful.
*
* @param tree
* The family tree.
* @param parent
* The name of the parent or null if this is the root node.
* @param child
* The name of the child to add.
*/
static void tryAdd(final FamilyTree tree, final String parent,
final String child) {
assert null != tree;
try {
tree.add(parent, child);
} catch (final NoSuchElementException e) {
System.err.println(e.getMessage());
System.err.flush();
}
}
}
/**
* a family tree class.
*/
class FamilyTree {
/**
* The root node of the family tree.
*/
private FamilyTreeNode myRoot = null;
/**
* Adds a node to the tree. This method searches for the parent and adds the
* child to that node if it is found. If this is the root node, then the
* parent parameter should be null. Only one root node is allowed per tree.
*
* @param parent
* The name of the parent to the child or null if the child
* should be the root of the tree.
* @param name
* The name of the child to add.
* @throws NoSuchElementException
* if the parent is not found or if the parent parameter is null
* and the root node is already assigned.
* @throws NullPointerException
* if the name parameter is null.
*/
public void add(final String parent, final String name) {
if (name == null)
throw new NullPointerException("name");
// Check if assigning this child as the root of the tree.
if (parent == null) {
// Assign the root item if there is not already one set.
if (this.myRoot == null) {
this.myRoot = new FamilyTreeNode(null, name);
return;
}
// Do not reassign the root item. Throw an exception instead.
throw new NoSuchElementException("Error: Cannot assign '" + name
+ "' as root because the root has already been assigned.");
}
// Try to add the child by searching through the tree, and throw an
// exception if the parent is not found.
if (!this.add(parent, name, this.myRoot))
throw new NoSuchElementException("Error: There is no matching parent '" + parent
+ "' to add child '" + name + "'.");
}
/**
* Adds a node to the tree recursively.
*
* @param parent
* The name of the parent to the child.
* @param name
* The name of the child to add.
* @param node
* The parent node to examine.
* @return True if the parent was found and the child was added or false if
* otherwise.
*/
private boolean add(final String parent, final String name,
final FamilyTreeNode node) {
assert null != parent;
assert null != node;
// Add the child if the parent name is a match.
if (parent.equalsIgnoreCase(node.name())) {
node.addChild(name);
return true;
}
// Loop over each child, and search for the parent recursively. Return
// true if the child is added.
for (int i = 0; i < node.numChildren(); i++)
if (this.add(parent, name, node.child(i)))
return true;
// If the parent was not found, return false.
return false;
}
/**
* Returns the string representation of the class data. The nodes of the
* tree are returned in pre-order.
*
* @return The string representation of the class data.
*/
@Override
public String toString() {
if (this.myRoot == null)
return "Nobody's in this family tree";
final StringBuilder builder = new StringBuilder();
toString(builder, this.myRoot, "", "");
return builder.toString();
}
/**
* Returns the string representation of the class data. The nodes of the
* tree are returned in pre-order.
*
* @param builder
* The string builder.
* @param node
* The current node.
* @param indent1
* The indent with bullet that is printed before all nodes except
* the root node
* @param indent2
* The indent with spaces that is printed before all nodes
* @return The string representation of the class data.
*/
private static void toString(
final StringBuilder builder,
final FamilyTreeNode node,
final String indent1,
final String indent2) {
assert null != node;
builder.append(indent1 + node + "\n");
final int size = node.numChildren();
for (int i = 0; i < size; i++) {
if (i == size - 1)
toString(builder, node.child(i), indent2 + " +--- ", indent2 + " ");
else
toString(builder, node.child(i), indent2 + " +--- ", indent2 + " | ");
}
}
}
/**
* a family tree node class.
*/
class FamilyTreeNode {
/**
* An array of children. When the node has no children, this array is null.
* When there are N children, the array has a length of N.
*/
private FamilyTreeNode[] myChildren;
/**
* The person's name.
*/
private final String myName;
/**
* A pointer to the parent node.
*/
private final FamilyTreeNode myParent;
/**
* Initializes a new instance of the class. Initially, the node has no
* children.
*
* @param parent
* A pointer to the parent node of this new node or null if this
* is the root node.
* @param name
* The name of person.
* @throws NullPointerException
* if the name parameter is null.
*/
public FamilyTreeNode(final FamilyTreeNode parent, final String name) {
if (name == null)
throw new NullPointerException("name");
this.myParent = parent;
this.myName = name;
this.myChildren = null;
}
/**
* Adds a child node to this node.
*
* @param name
* The name of the child to add.
* @throws NullPointerException
* if the name parameter is null.
*/
public void addChild(final String name) {
if (name == null)
throw new NullPointerException("name");
// Create the new node.
final FamilyTreeNode newChild = new FamilyTreeNode(this, name);
// Create the array if this is the first node.
if (this.myChildren == null) {
this.myChildren = new FamilyTreeNode[]{newChild};
return;
}
// Otherwise, duplicate the array, copy the old values, and add the new
// node
// to the end of the array.
final int size = this.myChildren.length;
final FamilyTreeNode[] newChildren = new FamilyTreeNode[size + 1];
System.arraycopy(this.myChildren, 0, newChildren, 0, size);
newChildren[size] = newChild;
this.myChildren = newChildren;
}
/**
* Returns the child at the specified index.
*
* @param index
* The index of the node, which must range from 0 to the value
* returned by numChildren.
* @return The node at the specified index.
* @throws IndexOutOfBoundsException
* if the index is invalid.
*/
public FamilyTreeNode child(final int index) {
if (index < 0 || index >= this.myChildren.length)
throw new IndexOutOfBoundsException("index");
return this.myChildren[index];
}
/**
* Returns the person's name.
*
* @return The person's name.
*/
public String name() {
return this.myName;
}
/**
* Returns the number of children for the node.
*
* @return The number of children for the node.
*/
public int numChildren() {
return this.myChildren == null ? 0 : this.myChildren.length;
}
/**
* Returns the parent node or null if this is the root node.
*
* @return The parent node.
*/
public FamilyTreeNode parent() {
return this.myParent;
}
/**
* Returns the string representation of the class data. This method returns
* the person's name.
*
* @return A string representation of the class data.
*/
@Override
public String toString() {
return this.myName;
}
}