package newickTreeParsing;
import java.util.*;
import java.io.Serializable;
import java.text.Collator;
/**
* A public class representing a (phylognenetic) tree.
* Nodes of the tree are of type TreeNode.
* Nodes are traversed in pre- and post-orders.
*
* @author Yunhong Zhou, Li Zhang
* @version 2.1
* @see TreeNode
*/
public class Tree implements Serializable{
/** The list of nodes of the tree indexed by their keys, indexed by key */
public ArrayList nodes;
/**
* Most internal nodes don't have names. Do we assign a unique
* name to each of them? No! each node has a key and the key is unique
* for nodes.
*/
private HashMap nodesByName;
/** key should be unique for each tree, set by object that creates trees */
private int key;
/** Leaf counter, for determining grid size, making arrays for tree comparisons */
private int numLeaves = 0;
// reference for array of leaves in SC.cullingObject
/** Split axis reference for leaf recovery (leaves are attached to split line culling objects) */
//private StaticSplitAxis leafSplitAxis;
/**
* Default tree constructor. Nodes are created by parser and added in later.
*
*/
public Tree() {
root = new TreeNode();
nodes = new ArrayList();
nodesByName = new HashMap();
}
/**
* Copy constructor used to create versions of trees that are identical to the supplied input tree.
* The copying should also make copies of the nodes in the given tree with the node copy constructor.
* (This function isn't working properly)
* @param treeToCopy Tree used to make a copy.
*/
public Tree(Tree treeToCopy)
{
// TODO: make this work with copy constructors (this constructor is only used in matrix mode)
fileName = treeToCopy.fileName;
height = treeToCopy.height;
key = treeToCopy.key;
// leafSplitAxis = new SplitAxis(treeToCopy.leafSplitAxis); // not implemented
nexusIndex = treeToCopy.nexusIndex;
nodes = new ArrayList(treeToCopy.nodes);
nodesByName = new HashMap(treeToCopy.nodesByName);
numLeaves = treeToCopy.numLeaves;
root = treeToCopy.root;
}
/**
* Clean up method, called when the tree is deleted.
* @see TreeNode#close()
*
*/
public void close(){
TreeNode pren = root.leftmostLeaf;
for(TreeNode n = pren.preorderNext; n!=null; n=n.preorderNext) {
n.close();
}
}
/**
* Calls to #close() when tree is deleted.
*/
protected void finalize() throws Throwable {
try {
close();
}
finally {
super.finalize();
}
}
/**
* Returns the number of interior nodes in this tree. For debugging.
* @return Total number of nodes minus the number of leaves.
*/
private int getInteriorCount() { return nodes.size() - numLeaves;}
/**
* Returns the node count, for internal and leaf nodes.
* @return Size of the {@link #nodes} array, which contains all nodes.
*/
protected int getTotalNodeCount() { return nodes.size();}
/**
* Returns the node indexed by the given key.
* @param key Key of the node to retrieve.
* @return Treenode referenced by the given key.
*/
public TreeNode getNodeByKey(int key){ if (key >= nodes.size()) return null; return (TreeNode) nodes.get(key);}
/**
* Returns the node given by the string.
* @param s Name/label of node to retrieve.
* @return Treenode referenced by the given name.
*/
public TreeNode getNodeByName(String s){
return (TreeNode) nodesByName.get(s);
}
/**
* Height of tree, which is also the longest path from the root to some leaf node.
*/
private int height = 0;
/**
* Accessor for height of tree. This is also the longest path from the root to some leaf node.
* @return value of {@link #height}.
*/
public int getHeight() { return height; }
/** Mutator for key
* @param i New value for {@link #key}.
*/
public void setKey(int i) {key = i;}
/** Accessor for key.
* @return Value of {@link #key}.
*/
public int getKey() {return key;}
/**
* File name accessor.
* @return value of {@link #fileName}.
*/
public String getName() {return fileName;}
/** Left most leaf accessor. This is the "min leaf"
* @return root's left most leaf, which is the smallest indexed leaf node in the tree.
*/
public TreeNode getLeftmostLeaf() { return root.leftmostLeaf; }
/** Root accessor.
* @return Value of {@link #root}*/
public TreeNode getRoot() { return root;}
public void setRootNode(TreeNode newRoot) { this.root = newRoot; }
/**
* File name for this tree.
*/
private String fileName = null; // the filename
/**
* Index of tree in nexus file, if this tree is from a nexus file.
*/
private int nexusIndex = 0; // the number (>0 for nexus, appended to nexus filename)
/**
* Root node of this tree
*/
protected TreeNode root=null;
/**
* Sets the file name. Copies the value for some reason.
* @param tn New value for file name.
*/
public void setFileName(String tn) {
fileName = new String(tn);
}
/**
* Returns the number of leaves in this tree.
* @return value of {@link #numLeaves}.
*/
public int getLeafCount() {
return numLeaves;
}
/**
* Post processing includes computing size of each node,
* linking nodes in different order, etc.
* Sets left and right-most leaves of the tree.
* Computes and stores pre- and post-orders for leaves.
* Can't do minmax until after linkNodesInPreorder is called
* to set index values!
*
* @see TreeNode
*/
public void postProcess() {
preorderPostProcess();
linkLeaves();
// System.out.println("progress bar updated: min:" + jpb.getMinimum() + " max:" + jpb.getMaximum() + " value:" + jpb.getValue());
}
/**
*
* Traverses the tree in pre-order, stores the ordering in the preorderNext field of TreeNodes
* Sets node count for the tree.
*
* @see TreeNode
*/
private void preorderPostProcess()
{
// munge names here, names become fully qualified, labels are what names were
final char separator = '/'; // separator between name fields
// arbitrary seen by users in search, no parsing on this is required later
int index = 0;
height = 1;
for(TreeNode n = root; n != null; n = n.preorderNext)
{
//debugging
//System.out.println("support: " + n.getSupport());
n.label = n.name;
n.key = index++;
nodes.add(n);
if(n.name != null && n.name.length() > 0) {
// don't put an empty string in the
// hash table
nodesByName.put(n.name, n);
}
n.height = (null != n.parent) ? n.parent.height+1 : 1;
height = (n.height > height) ? n.height : height;
}
}
/**
* Traverse the tree and initialize the {@link #nodesByName} and {@link #nodes} data structures.
* Used when modifying the names of nodes as well as initialization.
*
*/
public void setUpNameLists()
{
nodes = new ArrayList();
nodesByName = new HashMap();
final char separator = '/'; // separator between name fields
for(TreeNode n = root; n != null; n = n.preorderNext)
{
n.label = n.name;
nodes.add(n);
if(n.name != null && n.name.length() > 0) {
// don't put an empty string in the
// hash table
nodesByName.put(n.name, n);
}
n.height = (null != n.parent) ? n.parent.height+1 : 1;
height = (n.height > height) ? n.height : height;
}
}
/**
* Wrapper for initiating {@link #linkSubtreeNodesInPreorder(TreeNode)} with the root node.
*/
private void linkNodesInPreorder() {
linkSubtreeNodesInPreorder(root);
}
/**
* Traverses the subtree rooted at TreeNode n in pre-order, stores the
* ordering in the preorderNext field of TreeNodes.
* @param n the root of the subtree
*
* @see TreeNode
*/
private void linkSubtreeNodesInPreorder(TreeNode n) {
if(n.isLeaf()) return;
for(int i=0; i<n.numberChildren(); i++) {
linkSubtreeNodesInPreorder(n.getChild(i));
}
n.preorderNext = n.firstChild();
for(int i=0; i<n.numberChildren()-1; i++) {
n.getChild(i).rightmostLeaf.preorderNext = n.getChild(i+1);
}
n.rightmostLeaf.preorderNext = null;
}
/**
*
* Links leaves of the tree in pre-order,
* check to see whether leaves have distinct names.
* If leaves have the same name, add a suffix index separated by " "
*
* @see #linkNodesInPreorder()
* @see TreeNode
* @see NameComparator
* @param jpb Progress bar.
*/
private void linkLeaves() {
int counter = 0;
int percentage = 0;
TreeNode pren = root.leftmostLeaf;
Vector leaves = new Vector();
leaves.add(pren);
// pren.lindex = 0;
for(TreeNode n = pren.preorderNext; n!=null; n=n.preorderNext)
{
counter++;
if(n.isLeaf())
{
leaves.add(n);
}
}
numLeaves = leaves.size();
NameComparator myNameComparator = new NameComparator();
TreeNode[] sortedLeafArray = (TreeNode[])leaves.toArray(new TreeNode[leaves.size()]);
Arrays.sort(sortedLeafArray, myNameComparator);
int index = 0;
TreeNode curr = sortedLeafArray[0];
TreeNode next;
for(int i=0; i<leaves.size()-1; i++){
next = sortedLeafArray[i+1]; // only 1 index lookup per iteration
boolean compare = myNameComparator.compare(curr, next) == 0;
if (compare || index > 0)
{
String name = curr.getName();
nodesByName.remove(curr); // before all nodes with
// same name were being ignored in search and comparing two identically named
// leaves was broken, much fewer differences in trees with many leaves that
// have the same name (imagine: all index.html occurrences being marked as
// different since numbering convention doesn't string match the original node name)
curr.setName(name+ " " + index); //sb.toString());
nodesByName.put(name+ " " + index, curr);//sortedLeafArray[i].getName(), sortedLeafArray[i]); // add the node back with number convention
if (!compare)
index = 0;
else
index++;
}
curr = next;
}
}
/** Get the leaf associated with the given leaf index.
* @param index A leaf index of interest.
* @return The leaf node at the index, or null on error.
* */
public TreeNode getLeaf(int index)
{
// System.out.println("getting leaf: " + index );
return null;
}
/** Stub function */
public float getMinObjectValue() {
// TODO Auto-generated method stub
return 0;
}
/** Stub function */
public float getMaxObjectValue() {
// TODO Auto-generated method stub
return 0;
}
/**
* @return Returns the index number of this tree in the nexus file it was found in.
* Indexing starts at 0, and 0 for non-nexus.
*/
public int getNexusIndex()
{
return nexusIndex;
}
/**
* @param leafSplitAxis The leafSplitLine to set.
*/
//public void setLeafSplitAxis(StaticSplitAxis leafSplitAxis) {
// this.leafSplitAxis = leafSplitAxis;
//}
/**
* Get the leaves under this node. Used for tree to tree comparison, removing leaf nodes from difference calculations when they only appear in one side of the tree.
* This operation is constant time per leaf, since it relies on pre-ordered node links and pointers to extreme leaves.
* Time complexity of this function is linear in the number of leaves in the subtree under the node.
* @param node Node to get leaves under. The root node will return all leaves in the tree, leaves return a list of just themselves.
* @return List of leaves under this node.
*/
public LinkedList getLeaves(TreeNode node)
{
LinkedList leaves = new LinkedList();
TreeNode currNode = node.leftmostLeaf;
while (currNode != node.rightmostLeaf)
{
if (!currNode.isLeaf()) // internal node?
currNode = currNode.leftmostLeaf; // descend to minimal leaf
leaves.add(currNode);
currNode = currNode.preorderNext;
}
leaves.add(node.rightmostLeaf);
return leaves;
}
}
/** Comparator class for Strings */
class NameComparator implements Comparator{
/** collator object used for string comparison. */
Collator myCollator = Collator.getInstance(Locale.US);
/** String comparator, uses {@link Collator} comparator. */
public int compare(Object o1, Object o2){
String s1 = ((TreeNode)o1).getName();
String s2 = ((TreeNode)o2).getName();
return myCollator.compare(s1, s2);
}
}