package statalign.postprocess.plugins; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import statalign.base.Vertex; public class TreeNode { // Variables /** The name/identifier of this node. */ public String name; /** The branch length of this node. */ public double edgeLength; /** The children of this node. */ public List<TreeNode> children; /** The parent of this node. */ public TreeNode parent; /** Properties associated with this node. Lazy initialized: Not instantiated unless needed. */ public HashMap<String, Object> properties; // TODO: switch out for a property. public boolean ignoreNode = false; // Functions /** * A constructor which does not specify a name nor a edge length. */ public TreeNode() { this(null, 0.0d); } // /** // * Copy constructor // */ // public TreeNode(TreeNode other) { // this(other.name, other.edgeLength); // System.out.print(this+"="+name); // if (other.isLeaf()) { // children.clear(); // } // else { // for (int i=0; i<children.size(); i++) { // System.out.print("("); // children.set(i,new TreeNode(other.children.get(i))); // System.out.print("):"+edgeLength); // } // } // } public TreeNode(Vertex v) { this(v.name, v.edgeLength); // System.out.print(System.identityHashCode(this)+"="+name+"["+v.leafCount+"]"); // System.out.print("("); if (v.left != null) children.add(new TreeNode(v.left)); if (v.right != null) children.add(new TreeNode(v.right)); for (int i=0; i<children.size(); i++) { children.get(i).parent = this; } // System.out.print("):"+edgeLength); } /** * A constructor specifying the name of the node. * @param name the name of the node. */ public TreeNode(String name) { this(name, 0.0d); } /** * A constructor specifying the name and the edge length of the node. * @param name the name of the node. * @param edgeLength the edge length of the node. */ public TreeNode(String name, double edgeLength) { this.name = name; this.edgeLength = edgeLength; this.children = new ArrayList<TreeNode>(2); } /** * Determines whether this node is a leaf or not. * @return whether this node is a leaf or not. */ public boolean isLeaf() { return children.size() == 0; } /** * Adds a child to this tree's list of children. * @param node the child to be added. */ public void addChild(TreeNode node) { children.add(node); } /** * Finds all of the leaves of the subtree of this node. * @return a list of the leaves of this node's subtree. */ public List<TreeNode> getLeaves() { return getLeaves(this); } /** * Same as {@link #getLeaves()}. * @param node the node that we want to determine the leaves of. * @return a list of the leaves of this nodes subtree. */ public List<TreeNode> getLeaves(TreeNode node) { List<TreeNode> leaves = new ArrayList<TreeNode>(); if (node.isLeaf()) { leaves.add(node); } for (TreeNode n : node.children) { leaves.addAll(getLeaves(n)); } return leaves; } /** * Constructs a Newick string from this subtree. * @return a string representation of this subtree in a Newick format. */ @Override public String toString() { StringBuilder sb = new StringBuilder(); if (!children.isEmpty()) { sb.append('(').append(children.get(0)); for (int i = 1; i < children.size(); i++) sb.append(',').append(children.get(i)); sb.append(')'); } else { sb.append(name); } if (parent != null) { sb.append(":").append(String.format(Locale.US, "%.5f", edgeLength)); } else { sb.append(";"); } return sb.toString(); } /** * Constructs a Newick string from this subtree. * @return a string representation of this subtree in a Newick format. */ public String toStringWithProbs(int noOfSamples) { StringBuilder sb = new StringBuilder(); if (!children.isEmpty()) { sb.append('(').append(children.get(0).toStringWithProbs(noOfSamples)); for (int i = 1; i < children.size(); i++) sb.append(',').append(children.get(i).toStringWithProbs(noOfSamples)); sb.append(')'); String s = ""; if (hasProperty("noOfOccurrences")) { Integer noOfOccurrences = (Integer) getProperty("noOfOccurrences"); s = String.format("%.0f", noOfOccurrences * 100 / (double) noOfSamples); } sb.append(s); } else { sb.append(name); } if (parent != null) { sb.append(":").append(String.format(Locale.US, "%.5f", edgeLength)); } else { sb.append(";"); } return sb.toString(); } // Properties functions /** * Adds a key=value property to be associated with this node. * @param key the key portion of the property. * @param value the value portion of the property. */ public void addProperty(String key, Object value) { // Lazy initialization if (properties == null) { properties = new HashMap<String, Object>(); } properties.put(key, value); } /** * Return the value for a certain key property associated with this node. * @param key the key portion of the property. * @return the value associated with this key (or null if not existing). */ public Object getProperty(String key) { if (properties == null) { return null; } return properties.get(key); } /** * Same as {@link #getProperty(String)} but this method returns a value casted to an {@link Integer}. * @param key the key portion of the property. * @return the value associated with this key (casted to an {@link Integer}). */ public int getIntProperty(String key) { return (Integer) properties.get(key); } /** * Determines whether a certain key exists in the properties associated with this node. * @param key the key of the property. * @return a boolean whether this key actually exists or not. */ public boolean hasProperty(String key) { return properties != null && properties.containsKey(key); } // Misc variables/functions /** * The sum of the length of the names below this vertex. Used in the tree visualisation to decide * how much space this subtree needs to be able to put leaves' names nicely onto the graphical * interface. */ public int nameLengthSum; /** The number of leaves in this subtree. */ public int leafCount; /** * Calculate the nodes needed to cross to get to the most distant leaf * Used in tree visualisation. * @return steps needed to reach the most distant leaf. */ public int maxSteps() { /*if(left == null){ return 0; } else return 1 + Math.max(left.maxSteps(),right.maxSteps());*/ if (isLeaf()) { return 0; } int max = 0; for (TreeNode node : children) { max = Math.max(max, node.maxSteps()); } return 1 + max; } /** * This function calculates the sum of the length of the strings representing the * names of the leaves below this subtree. Used in tree visualisation. * @return the sum of the lengths of the names below this vertex. */ public int countNameLengthSum() { return nameLengthSum = isLeaf() ? Math.min(10, name.indexOf(' ') == -1 ? name.length() : name.indexOf(' ')) + 2 : children.get(0).countNameLengthSum() + children.get(1).countNameLengthSum(); } /** * Calculated the number of leads that exists below this node * @return the number of leaves below the node. */ public int countLeaves() { if (this.isLeaf()) { return leafCount = 1; } int sum = 0; for (TreeNode child : children) { sum += child.countLeaves(); } return leafCount = sum; } /** * Calculate the maximum depth below on this vertex. * Used in tree visualisation. * @return maximum depth below this vertex. */ public double maxDepth() { if (this.isLeaf()) { return edgeLength; } else { double max = 0; for (int i = 0; i < children.size(); i++) { if (max < children.get(i).maxDepth()) { max = children.get(i).maxDepth(); } } return edgeLength + max; } } /** TODO: WHAT? */ public TreeNode getLeft() { return children.get(0); } public TreeNode getRight() { return children.get(1); } }