/*
* SimpleTree.java
*
* Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard
*
* This file is part of BEAST.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership and licensing.
*
* BEAST 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
* of the License, or (at your option) any later version.
*
* BEAST 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 BEAST; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
package dr.evolution.tree;
import dr.evolution.util.MutableTaxonListListener;
import dr.evolution.util.Taxon;
import dr.util.Attributable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* A data structure for binary rooted trees.
*
* @author Andrew Rambaut
* @author Alexei Drummond
*
* @version $Id: SimpleTree.java,v 1.65 2006/08/23 10:46:33 rambaut Exp $
*/
public class SimpleTree implements MutableTree {
/** Constructor tree with no nodes. Use adoptNodes to add some nodes. */
public SimpleTree() {
root = null;
}
/** clone constructor */
public SimpleTree(Tree tree) {
setUnits(tree.getUnits());
root = new SimpleNode(tree, tree.getRoot());
nodeCount = tree.getNodeCount();
internalNodeCount = tree.getInternalNodeCount();
externalNodeCount = tree.getExternalNodeCount();
nodes = new SimpleNode[nodeCount];
SimpleNode node = root;
do {
node = (SimpleNode) TreeUtils.postorderSuccessor(this, node);
if ((node.getNumber() >= externalNodeCount && node.isExternal()) ||
(node.getNumber() < externalNodeCount && !node.isExternal())) {
throw new RuntimeException("Error cloning tree: node numbers are incompatible");
}
nodes[node.getNumber()] = node;
} while (node != root);
}
/** clone constructor */
public SimpleTree(SimpleNode root) {
adoptNodes(root);
}
/**
* @return a copy of this tree
*/
public Tree getCopy() {
return new SimpleTree(this);
}
/**
* Adopt a node hierarchy as its own. Only called by the SimpleTree(SimpleNode, TaxonList).
* This creates the node list and stores the nodes in post-traversal order.
*/
protected void adoptNodes(SimpleNode node) {
if (inEdit) throw new RuntimeException("Mustn't be in an edit transaction to call this method!");
internalNodeCount = 0;
externalNodeCount = 0;
root = node;
do {
node = (SimpleNode) TreeUtils.postorderSuccessor(this, node);
if (node.isExternal()) {
externalNodeCount++;
} else
internalNodeCount++;
} while(node != root);
nodeCount = internalNodeCount + externalNodeCount;
nodes = new SimpleNode[nodeCount];
node = root;
int i = 0;
int j = externalNodeCount;
do {
node = (SimpleNode) TreeUtils.postorderSuccessor(this, node);
if (node.isExternal()) {
node.setNumber(i);
nodes[i] = node;
i++;
} else {
node.setNumber(j);
nodes[j] = node;
j++;
}
} while(node != root);
}
/**
* Return the units that this tree is expressed in.
*/
public final Type getUnits() {
return units;
}
/**
* Sets the units that this tree is expressed in.
*/
public final void setUnits(Type units) {
this.units = units;
}
/**
* @return a count of the number of nodes (internal + external) in this
* tree.
*/
public int getNodeCount() {
return nodeCount;
}
public boolean hasNodeHeights() { return true; }
public double getNodeHeight(NodeRef node) { return ((SimpleNode)node).getHeight(); }
public double getNodeRate(NodeRef node) { return ((SimpleNode)node).getRate(); }
public Taxon getNodeTaxon(NodeRef node) { return ((SimpleNode)node).getTaxon(); }
public int getChildCount(NodeRef node) { return ((SimpleNode)node).getChildCount(); }
public boolean isExternal(NodeRef node) { return ((SimpleNode)node).getChildCount() == 0; }
public boolean isRoot(NodeRef node) { return (node == root); }
public NodeRef getChild(NodeRef node, int i) { return ((SimpleNode)node).getChild(i); }
public NodeRef getParent(NodeRef node) { return ((SimpleNode)node).getParent(); }
public boolean hasBranchLengths() { return true; }
public double getBranchLength(NodeRef node) {
NodeRef parent = getParent(node);
if (parent == null) {
return 0.0;
}
return getNodeHeight(parent) - getNodeHeight(node);
}
public void setBranchLength(NodeRef node, double length) {
throw new UnsupportedOperationException("SimpleTree cannot have branch lengths set... use FlexibleTree");
}
public final SimpleNode getExternalNode(int i) {
return nodes[i];
}
public final SimpleNode getInternalNode(int i) {
return nodes[i+externalNodeCount];
}
public final NodeRef getNode(int i) {
return nodes[i];
}
/**
* Returns the number of external nodes.
*/
public final int getExternalNodeCount() {
return externalNodeCount;
}
/**
* Returns the ith internal node.
*/
public final int getInternalNodeCount() {
return internalNodeCount;
}
/**
* Returns the root node of this tree.
*/
public final NodeRef getRoot() {
return root;
}
/**
* Set a new node as root node.
*/
public final void setRoot(NodeRef r) {
if (!inEdit) throw new RuntimeException("Must be in edit transaction to call this method!");
if (!(r instanceof SimpleNode)) { throw new IllegalArgumentException(); }
root = (SimpleNode)r;
root.setParent(null);
}
/**
* @return the height of the root node.
*/
public final double getRootHeight() {
return root.getHeight();
}
/**
* Set the height of the root node.
*/
public final void setRootHeight(double height) {
root.setHeight(height);
fireTreeChanged();
}
public void addChild(NodeRef p, NodeRef c) {
if (!inEdit) throw new RuntimeException("Must be in edit transaction to call this method!");
SimpleNode parent = (SimpleNode)p;
SimpleNode child = (SimpleNode)c;
if (parent.hasChild(child)) throw new IllegalArgumentException("Child already existists in parent");
parent.addChild(child);
}
public void removeChild(NodeRef p, NodeRef c) {
if (!inEdit) throw new RuntimeException("Must be in edit transaction to call this method!");
SimpleNode parent = (SimpleNode)p;
SimpleNode child = (SimpleNode)c;
parent.removeChild(child);
}
public void replaceChild(NodeRef node, NodeRef child, NodeRef newChild) {
if (!inEdit) throw new RuntimeException("Must be in edit transaction to call this method!");
SimpleNode parent = (SimpleNode)node;
parent.replaceChild((SimpleNode)child, (SimpleNode)newChild);
}
public boolean beginTreeEdit() {
boolean r = inEdit;
inEdit = true;
return r;
}
public void endTreeEdit() {
inEdit = false;
fireTreeChanged();
}
public void setNodeHeight(NodeRef n, double height) {
SimpleNode node = (SimpleNode)n;
node.setHeight(height);
fireTreeChanged();
}
public void setNodeRate(NodeRef n, double rate) {
SimpleNode node = (SimpleNode)n;
node.setRate(rate);
fireTreeChanged();
}
/**
* Sets an named attribute for a given node.
* @param node the node whose attribute is being set.
* @param name the name of the attribute.
* @param value the new value of the attribute.
*/
public void setNodeAttribute(NodeRef node, String name, Object value) {
((SimpleNode)node).setAttribute(name, value);
fireTreeChanged();
}
/**
* @return an object representing the named attributed for the given node.
* @param node the node whose attribute is being fetched.
* @param name the name of the attribute of interest.
*/
public Object getNodeAttribute(NodeRef node, String name) {
return ((SimpleNode)node).getAttribute(name);
}
/**
* @return an interator of attribute names available for this node.
* @return a key set of attribute names available for this node.
*/
public Iterator getNodeAttributeNames(NodeRef node) {
return ((SimpleNode)node).getAttributeNames();
}
// **************************************************************
// TaxonList IMPLEMENTATION
// **************************************************************
/**
* @return a count of the number of taxa in the list.
*/
public int getTaxonCount() {
return getExternalNodeCount();
}
/**
* @return the ith taxon in the list.
*/
public Taxon getTaxon(int taxonIndex) {
return getExternalNode(taxonIndex).getTaxon();
}
/**
* @return the ID of the taxon of the ith external node. If it doesn't have
* a taxon, returns the ID of the node itself.
*/
public String getTaxonId(int taxonIndex) {
Taxon taxon = getTaxon(taxonIndex);
if (taxon != null)
return taxon.getId();
else
return getExternalNode(taxonIndex).getId();
}
/**
* returns the index of the taxon with the given id.
*/
public int getTaxonIndex(String id) {
for (int i = 0, n = getTaxonCount(); i < n; i++) {
if (getTaxonId(i).equals(id)) return i;
}
return -1;
}
/**
* returns the index of the given taxon.
*/
public int getTaxonIndex(Taxon taxon) {
for (int i = 0, n = getTaxonCount(); i < n; i++) {
if (getTaxon(i) == taxon) return i;
}
return -1;
}
public List<Taxon> asList() {
List<Taxon> taxa = new ArrayList<Taxon>();
for (int i = 0, n = getTaxonCount(); i < n; i++) {
taxa.add(getTaxon(i));
}
return taxa;
}
public Iterator<Taxon> iterator() {
return new Iterator<Taxon>() {
private int index = -1;
public boolean hasNext() {
return index < getTaxonCount() - 1;
}
public Taxon next() {
index ++;
return getTaxon(index);
}
public void remove() { /* do nothing */ }
};
}
/**
* @return an object representing the named attributed for the taxon of the given
* external node. If the node doesn't have a taxon then the nodes own attribute
* is returned.
* @param taxonIndex the index of the taxon whose attribute is being fetched.
* @param name the name of the attribute of interest.
*/
public Object getTaxonAttribute(int taxonIndex, String name) {
Taxon taxon = getTaxon(taxonIndex);
if (taxon != null)
return taxon.getAttribute(name);
else
return getExternalNode(taxonIndex).getAttribute(name);
}
// **************************************************************
// MutableTaxonList IMPLEMENTATION
// **************************************************************
public int addTaxon(Taxon taxon) { throw new IllegalArgumentException("Cannot add taxon to a MutableTree"); }
public boolean removeTaxon(Taxon taxon) { throw new IllegalArgumentException("Cannot add taxon to a MutableTree"); }
/**
* Sets the ID of the taxon of the ith external node. If it doesn't have
* a taxon, sets the ID of the node itself.
*/
public void setTaxonId(int taxonIndex, String id) {
Taxon taxon = getTaxon(taxonIndex);
if (taxon != null)
taxon.setId(id);
else
getExternalNode(taxonIndex).setId(id);
fireTreeChanged();
fireTaxaChanged();
}
/**
* Sets an named attribute for the taxon of a given external node. If the node
* doesn't have a taxon then the attribute is added to the node itself.
* @param taxonIndex the index of the taxon whose attribute is being set.
* @param name the name of the attribute.
* @param value the new value of the attribute.
*/
public void setTaxonAttribute(int taxonIndex, String name, Object value) {
Taxon taxon = getTaxon(taxonIndex);
if (taxon != null)
taxon.setAttribute(name, value);
else
getExternalNode(taxonIndex).setAttribute(name, value);
fireTreeChanged();
fireTaxaChanged();
}
// **************************************************************
// Identifiable IMPLEMENTATION
// **************************************************************
protected String id = null;
/**
* @return the id.
*/
public String getId() {
return id;
}
/**
* Sets the id.
*/
public void setId(String id) {
this.id = id;
}
// **************************************************************
// Attributable IMPLEMENTATION
// **************************************************************
private Attributable.AttributeHelper attributes = null;
/**
* Sets an named attribute for this object.
* @param name the name of the attribute.
* @param value the new value of the attribute.
*/
public void setAttribute(String name, Object value) {
if (attributes == null)
attributes = new Attributable.AttributeHelper();
attributes.setAttribute(name, value);
}
/**
* @return an object representing the named attributed for this object.
* @param name the name of the attribute of interest.
*/
public Object getAttribute(String name) {
if (attributes == null)
return null;
else
return attributes.getAttribute(name);
}
/**
* @return an iterator of the attributes that this object has.
*/
public Iterator<String> getAttributeNames() {
if (attributes == null)
return null;
else
return attributes.getAttributeNames();
}
public void addMutableTreeListener(MutableTreeListener listener) {
mutableTreeListeners.add(listener);
}
private void fireTreeChanged() {
for (MutableTreeListener mutableTreeListener : mutableTreeListeners) {
mutableTreeListener.treeChanged(this);
}
}
private final ArrayList<MutableTreeListener> mutableTreeListeners = new ArrayList<MutableTreeListener>();
public void addMutableTaxonListListener(MutableTaxonListListener listener) {
mutableTaxonListListeners.add(listener);
}
private void fireTaxaChanged() {
for (MutableTaxonListListener mutableTaxonListListener : mutableTaxonListListeners) {
mutableTaxonListListener.taxaChanged(this);
}
}
private final ArrayList<MutableTaxonListListener> mutableTaxonListListeners = new ArrayList<MutableTaxonListListener>();
/**
* @return a string containing a newick representation of the tree
*/
public String toString() {
return TreeUtils.newick(this);
}
/**
* @return whether two trees have the same topology
*/
public boolean equals(Object obj) {
if (!(obj instanceof Tree)) {
throw new IllegalArgumentException("SimpleTree.equals can only compare instances of Tree");
}
return TreeUtils.equal(this, (Tree)obj);
}
// **************************************************************
// Private members
// **************************************************************
/** root node */
SimpleNode root;
/** list of internal nodes (including root) */
SimpleNode[] nodes = null;
/** number of nodes (including root and tips) */
int nodeCount;
/** number of external nodes */
int externalNodeCount;
/** number of internal nodes (including root) */
int internalNodeCount;
/** holds the units of the trees branches. */
private Type units = Type.SUBSTITUTIONS;
boolean inEdit = false;
}