package nl.tudelft.lifetiles.tree.model;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import nl.tudelft.lifetiles.core.util.SetUtils;
import nl.tudelft.lifetiles.sequence.model.Sequence;
/**
* A tree to store the relation between samples.
*
* @author Albert Smit
* @author Rutger van den Berg
*
*/
public class PhylogeneticTreeItem {
/**
* The list of children of this node.
*/
private final Set<PhylogeneticTreeItem> children;
/**
* The distance between samples. This is an optional field.
*/
private double distance;
/**
* The name of the sample. This is an optional field.
*/
private String name;
/**
* The parent node, null when this node is the root node.
*/
private PhylogeneticTreeItem parent;
/**
* The sequence this node is associated with.
*/
private Sequence sequence;
/**
* The sequences this nodes descendants are associated with.
*/
private Set<Sequence> childSequences;
/**
* Creates a new PhylogeneticTreeItem. Will initialize the ArrayList storing
* the children and assign a new and unique id to the node.
*/
public PhylogeneticTreeItem() {
children = new CopyOnWriteArraySet<PhylogeneticTreeItem>();
}
/**
* Method to determine the amount of descendant nodes each node has.
*
* @return the amount of descendant nodes
*/
public int numberDescendants() {
int result = 0;
for (PhylogeneticTreeItem child : children) {
result += child.numberDescendants() + 1;
}
return result;
}
/**
* Method to determine the maximum amount of layers.
*
* @return the amount of layers
*/
public int maxDepth() {
int result = 0;
for (PhylogeneticTreeItem child : children) {
result = Math.max(result, child.maxDepth() + 1);
}
return result;
}
/**
* creates a Set containing this nodes sequence, and the sequences of its
* children.
*
* @return a set with all sequences that descend from this node.
*/
public Set<Sequence> getSequences() {
Set<Sequence> result = new HashSet<Sequence>();
if (sequence != null) {
result.add(sequence);
}
for (PhylogeneticTreeItem child : children) {
result.addAll(child.getSequences());
}
return result;
}
/**
* Adds a child to the PhylogeneticTreeItem. This method will add the
* PhylogeneticTreeItem child to the ArrayList storing the children of this
* node.
*
* @param child
* the PhylogeneticTreeItem that needs to be added to the tree
*/
public void addChild(final PhylogeneticTreeItem child) {
children.add(child);
}
/**
* Creates a new tree that only contains the visible nodes. When a node has
* only one child, it is removed from the tree and its child is returned
* instead. When a node has no children, and is not visible, null is
* returned.
*
* @param visibleSequences
* the sequences that need to be in this tree.
* @return the new root of a subtree.
*/
public PhylogeneticTreeItem subTree(
final Set<Sequence> visibleSequences) {
// copy the node
PhylogeneticTreeItem result = new PhylogeneticTreeItem();
result.setDistance(distance);
if (visibleSequences.contains(sequence)) {
result.setName(name);
} else if (children.isEmpty()) {
return null;
}
// copy the children when they are needed
for (PhylogeneticTreeItem child : children) {
// check if this child is needed
if (SetUtils.intersectionSize(childSequences, visibleSequences) > 0) {
PhylogeneticTreeItem subtree = child.subTree(visibleSequences);
if (subtree != null) {
subtree.setParent(result);
}
}
}
// remove useless nodes(nodes with a single child can be removed from
// the subtree)
if (result.getChildren().isEmpty() && result.getName() == null) {
return null;
}
if (result.getChildren().size() == 1) {
result = result.getChildren().get(0);
}
return result;
}
/**
* Fills the set containing the sequences that descend from this node. the
* sequences should already have been added to the tree.
*/
public void populateChildSequences() {
for (PhylogeneticTreeItem child : children) {
child.populateChildSequences();
}
setChildSequences();
}
/**
* Returns the ArrayList of children.
*
* @return the ArrayList containing all children of this node
*/
public List<PhylogeneticTreeItem> getChildren() {
return new ArrayList<PhylogeneticTreeItem>(children);
}
/**
* Returns the distance stored in this node. distance is an optional
* property, so the distance can often be 0.0.
*
* @return the distance of this node
*/
public double getDistance() {
return distance;
}
/**
* Returns the name stored in this node. name is an optional property, so
* this method can return null.
*
* @return the name of this node
*/
public String getName() {
return name;
}
/**
* Returns this nodes parent node.
*
* @return the PhylogeneticTreeItem that is this nodes parent
*/
public PhylogeneticTreeItem getParent() {
return parent;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + children.hashCode();
long temp;
temp = Double.doubleToLongBits(distance);
result = prime * result + (int) (temp ^ (temp >>> prime + 1));
result = prime * result;
if (name != null) {
result += name.hashCode();
}
return result;
}
/**
* Sets this nodes distance to the passed double.
*
* @param distance
* the distance between the nodes
*/
public void setDistance(final double distance) {
this.distance = distance;
}
/**
* Set this nodes name to the passed String.
*
* @param name
* the name of this node
*/
public void setName(final String name) {
this.name = name;
}
/**
* Compares this with another Object. returns true when both are the same.
* two PhylogeneticTreeItems are considered the same when both have the
* same:
*
* <ol>
* <li>name or both have no name</li>
* <li>distance</li>
* <li>children, order does not matter</li>
* </ol>
*
* @param other
* the object to compare with
*
* @return true if both are the same, otherwise false
*/
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof PhylogeneticTreeItem)) {
return false;
}
PhylogeneticTreeItem other = (PhylogeneticTreeItem) obj;
if (!Double.valueOf(this.distance).equals(other.distance)) {
return false;
}
if (name == null) {
if (other.name != null) {
return false;
}
} else if (!name.equals(other.name)) {
return false;
}
return children.equals(other.children);
}
/**
* Sets the parent to be the node passed to the method This method will set
* the parent and also add itself to the list of children in the parent
* node.
*
* @param parentNode
* the node that will be this nodes parent
*/
public void setParent(final PhylogeneticTreeItem parentNode) {
this.parent = parentNode;
this.parent.addChild(this);
}
/**
* @return the sequence
*/
public Sequence getSequence() {
return sequence;
}
/**
* @param seq
* the sequence to set
*/
public void setSequence(final Sequence seq) {
this.sequence = seq;
}
/**
* Returns a String representation of the PhylogeneticTreeItem. The String
* will have the following format:
* <code><Node: id, name: name, Distance:distance, parent: parentID>
* </code> or when the PhylogeneticTreeItem has no parent:
* <code><Node: id, Name: name, Distance: distance, ROOT></code>
*
* @return the String version of the object.
*/
@Override
public String toString() {
return toStringWithDepth(0);
}
/**
* @param depth
* The depth of this treeitem as compared to the depth of the
* treeitem on which toString was called.
* @return Recursive string representation.
*/
private String toStringWithDepth(final int depth) {
StringBuffer output = new StringBuffer("<Node: Name: "
+ name + ", Distance: " + distance + ">");
for (PhylogeneticTreeItem child : children) {
output.append('\n');
for (int i = 0; i <= depth; i++) {
output.append('\t');
}
output.append(child.toStringWithDepth(depth + 1));
}
return output.toString();
}
/**
* @return returns the list of child sequences stored in this node
*/
public Set<Sequence> getChildSequences() {
return childSequences;
}
/**
* Sets this nodes childSequences field to the set returned by getSequences.
*/
public void setChildSequences() {
this.childSequences = this.getSequences();
}
}