772c86034d2cc4ed56943d7a3b2abd0c1c6e04de
[jalview.git] / forester / java / src / org / forester / phylogeny / Phylogeny.java
1 // $Id:
2 // FORESTER -- software libraries and applications
3 // for evolutionary biology research and applications.
4 //
5 // Copyright (C) 2008-2009 Christian M. Zmasek
6 // Copyright (C) 2008-2009 Burnham Institute for Medical Research
7 // Copyright (C) 2000-2001 Washington University School of Medicine
8 // and Howard Hughes Medical Institute
9 // All rights reserved
10 //
11 // This library is free software; you can redistribute it and/or
12 // modify it under the terms of the GNU Lesser General Public
13 // License as published by the Free Software Foundation; either
14 // version 2.1 of the License, or (at your option) any later version.
15 //
16 // This library is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 // Lesser General Public License for more details.
20 //
21 // You should have received a copy of the GNU Lesser General Public
22 // License along with this library; if not, write to the Free Software
23 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 //
25 // Contact: phylosoft @ gmail . com
26 // WWW: www.phylosoft.org/forester
27
28 package org.forester.phylogeny;
29
30 import java.io.IOException;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Collection;
34 import java.util.HashMap;
35 import java.util.Iterator;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.NoSuchElementException;
39 import java.util.Vector;
40
41 import org.forester.io.writers.PhylogenyWriter;
42 import org.forester.phylogeny.data.BranchData;
43 import org.forester.phylogeny.data.Confidence;
44 import org.forester.phylogeny.data.Identifier;
45 import org.forester.phylogeny.data.PhylogenyDataUtil;
46 import org.forester.phylogeny.data.Sequence;
47 import org.forester.phylogeny.data.SequenceRelation;
48 import org.forester.phylogeny.data.SequenceRelation.SEQUENCE_RELATION_TYPE;
49 import org.forester.phylogeny.iterators.ExternalForwardIterator;
50 import org.forester.phylogeny.iterators.LevelOrderTreeIterator;
51 import org.forester.phylogeny.iterators.PhylogenyNodeIterator;
52 import org.forester.phylogeny.iterators.PostorderTreeIterator;
53 import org.forester.phylogeny.iterators.PreorderTreeIterator;
54 import org.forester.util.FailedConditionCheckException;
55 import org.forester.util.ForesterUtil;
56
57 public class Phylogeny {
58
59     public final static boolean                                 ALLOW_MULTIPLE_PARENTS_DEFAULT = false;
60     private PhylogenyNode                                       _root;
61     private boolean                                             _rooted;
62     private boolean                                             _allow_multiple_parents;
63     private String                                              _name;
64     private String                                              _type;
65     private String                                              _description;
66     private String                                              _distance_unit;
67     private Confidence                                          _confidence;
68     private Identifier                                          _identifier;
69     private boolean                                             _rerootable;
70     private HashMap<Integer, PhylogenyNode>                     _idhash;
71     private List<PhylogenyNode>                                 _external_nodes_set;
72     private Collection<Sequence>                                _sequenceRelationQueries;
73     private Collection<SequenceRelation.SEQUENCE_RELATION_TYPE> _relevant_sequence_relation_types;
74
75     /**
76      * Default Phylogeny constructor. Constructs an empty Phylogeny.
77      */
78     public Phylogeny() {
79         init();
80     }
81
82     /**
83      * Adds this Phylogeny to the list of child nodes of PhylogenyNode parent
84      * and sets the parent of this to parent.
85      * 
86      * @param n
87      *            the PhylogenyNode to add
88      */
89     public void addAsChild( final PhylogenyNode parent ) {
90         if ( isEmpty() ) {
91             throw new IllegalArgumentException( "Attempt to add an empty tree." );
92         }
93         if ( !isRooted() ) {
94             throw new IllegalArgumentException( "Attempt to add an unrooted tree." );
95         }
96         parent.addAsChild( getRoot() );
97         externalNodesHaveChanged();
98     }
99
100     public void addAsSibling( final PhylogenyNode sibling ) {
101         if ( isEmpty() ) {
102             throw new IllegalArgumentException( "Attempt to add an empty tree." );
103         }
104         if ( !isRooted() ) {
105             throw new IllegalArgumentException( "Attempt to add an unrooted tree." );
106         }
107         final int sibling_index = sibling.getChildNodeIndex();
108         final PhylogenyNode new_node = new PhylogenyNode();
109         final PhylogenyNode sibling_parent = sibling.getParent();
110         new_node.setChild1( sibling );
111         new_node.setChild2( getRoot() );
112         new_node.setParent( sibling_parent );
113         sibling.setParent( new_node );
114         sibling_parent.setChildNode( sibling_index, new_node );
115         final double new_dist = sibling.getDistanceToParent() == PhylogenyDataUtil.BRANCH_LENGTH_DEFAULT ? PhylogenyDataUtil.BRANCH_LENGTH_DEFAULT
116                 : sibling.getDistanceToParent() / 2;
117         new_node.setDistanceToParent( new_dist );
118         sibling.setDistanceToParent( new_dist );
119         externalNodesHaveChanged();
120     }
121
122     /**
123      * This calculates the height of the subtree emanating at n for rooted,
124      * tree-shaped phylogenies
125      * 
126      * @param n
127      *            the root-node of a subtree
128      * @return the height of the subtree emanating at n
129      */
130     public double calculateSubtreeHeight( final PhylogenyNode n ) {
131         if ( n.isExternal() || n.isCollapse() ) {
132             return ForesterUtil.isLargerOrEqualToZero( n.getDistanceToParent() );
133         }
134         else {
135             double max = -Double.MAX_VALUE;
136             for( int i = 0; i < n.getNumberOfDescendants(); ++i ) {
137                 final double l = calculateSubtreeHeight( n.getChildNode( i ) );
138                 if ( l > max ) {
139                     max = l;
140                 }
141             }
142             return max + ForesterUtil.isLargerOrEqualToZero( n.getDistanceToParent() );
143         }
144     }
145
146     /**
147      * Returns a deep copy of this Phylogeny.
148      * <p>
149      * (The resulting Phylogeny has its references in the external nodes
150      * corrected, if they are lacking/obsolete in this.)
151      */
152     public Phylogeny copy() {
153         return copy( _root );
154     }
155
156     /**
157      * Returns a shallow copy of this Phylogeny.
158      * <p>
159      * (The resulting Phylogeny has its references in the external nodes
160      * corrected, if they are lacking/obsolete in this.)
161      */
162     public Phylogeny copyShallow() {
163         return copyShallow( _root );
164     }
165
166     public Phylogeny copyShallow( final PhylogenyNode source ) {
167         final Phylogeny tree = new Phylogeny();
168         if ( isEmpty() ) {
169             tree.init();
170             return tree;
171         }
172         tree._rooted = _rooted;
173         tree._name = _name;
174         tree._description = _description;
175         tree._type = _type;
176         tree._rerootable = _rerootable;
177         tree._distance_unit = _distance_unit;
178         tree._confidence = _confidence;
179         tree._identifier = _identifier;
180         tree.setAllowMultipleParents( isAllowMultipleParents() );
181         tree._root = PhylogenyMethods.copySubTreeShallow( source );
182         return tree;
183     }
184
185     /**
186      * Returns a deep copy of this Phylogeny.
187      * <p>
188      * (The resulting Phylogeny has its references in the external nodes
189      * corrected, if they are lacking/obsolete in this.)
190      */
191     public Phylogeny copy( final PhylogenyNode source ) {
192         final Phylogeny tree = new Phylogeny();
193         if ( isEmpty() ) {
194             tree.init();
195             return tree;
196         }
197         tree._rooted = _rooted;
198         tree._name = new String( _name );
199         tree._description = new String( _description );
200         tree._type = new String( _type );
201         tree._rerootable = _rerootable;
202         tree._distance_unit = new String( _distance_unit );
203         if ( _confidence != null ) {
204             tree._confidence = ( Confidence ) _confidence.copy();
205         }
206         if ( _identifier != null ) {
207             tree._identifier = ( Identifier ) _identifier.copy();
208         }
209         tree.setAllowMultipleParents( isAllowMultipleParents() );
210         tree._root = PhylogenyMethods.copySubTree( source );
211         return tree;
212     }
213
214     /**
215      * Need the delete and/or rehash _idhash (not done automatically
216      * to allow client multiple deletions in linear time).
217      * Need to call 'recalculateNumberOfExternalDescendants(boolean)' after this 
218      * if tree is to be displayed.
219      * 
220      * @param remove_us the parent node of the subtree to be deleted
221      */
222     public void deleteSubtree( final PhylogenyNode remove_us, final boolean collapse_resulting_node_with_one_desc ) {
223         if ( isEmpty() ) {
224             return;
225         }
226         if ( remove_us.isRoot() ) {
227             init();
228             return;
229         }
230         if ( !collapse_resulting_node_with_one_desc ) {
231             remove_us.getParent().removeChildNode( remove_us );
232         }
233         else {
234             final PhylogenyNode removed_node = remove_us;
235             final PhylogenyNode p = remove_us.getParent();
236             if ( p.isRoot() ) {
237                 if ( p.getNumberOfDescendants() == 2 ) {
238                     if ( removed_node.isFirstChildNode() ) {
239                         setRoot( getRoot().getChildNode( 1 ) );
240                         getRoot().setParent( null );
241                     }
242                     else {
243                         setRoot( getRoot().getChildNode( 0 ) );
244                         getRoot().setParent( null );
245                     }
246                 }
247                 else {
248                     p.removeChildNode( removed_node.getChildNodeIndex() );
249                 }
250             }
251             else {
252                 final PhylogenyNode pp = removed_node.getParent().getParent();
253                 if ( p.getNumberOfDescendants() == 2 ) {
254                     final int pi = p.getChildNodeIndex();
255                     if ( removed_node.isFirstChildNode() ) {
256                         p.getChildNode( 1 ).setDistanceToParent( PhylogenyMethods.addPhylogenyDistances( p
257                                 .getDistanceToParent(), p.getChildNode( 1 ).getDistanceToParent() ) );
258                         pp.setChildNode( pi, p.getChildNode( 1 ) );
259                     }
260                     else {
261                         p.getChildNode( 0 ).setDistanceToParent( PhylogenyMethods.addPhylogenyDistances( p
262                                 .getDistanceToParent(), p.getChildNode( 0 ).getDistanceToParent() ) );
263                         pp.setChildNode( pi, p.getChildNode( 0 ) );
264                     }
265                 }
266                 else {
267                     p.removeChildNode( removed_node.getChildNodeIndex() );
268                 }
269             }
270         }
271         remove_us.setParent( null );
272         setIdHash( null );
273         externalNodesHaveChanged();
274     }
275
276     public void externalNodesHaveChanged() {
277         _external_nodes_set = null;
278     }
279
280     public String[] getAllExternalNodeNames() {
281         int i = 0;
282         if ( isEmpty() ) {
283             return null;
284         }
285         final String[] names = new String[ getNumberOfExternalNodes() ];
286         for( final PhylogenyNodeIterator iter = iteratorExternalForward(); iter.hasNext(); ) {
287             names[ i++ ] = new String( iter.next().getName() );
288         }
289         return names;
290     }
291
292     public Confidence getConfidence() {
293         return _confidence;
294     }
295
296     public String getDescription() {
297         return _description;
298     }
299
300     public String getDistanceUnit() {
301         return _distance_unit;
302     }
303
304     /**
305      * 
306      * Warning. The order of the returned nodes is random
307      * -- and hence cannot be relied on.
308      * 
309      * @return Unordered set of PhylogenyNode
310      */
311     public List<PhylogenyNode> getExternalNodes() {
312         if ( _external_nodes_set == null ) {
313             _external_nodes_set = new ArrayList<PhylogenyNode>();
314             for( final PhylogenyNodeIterator it = iteratorPostorder(); it.hasNext(); ) {
315                 final PhylogenyNode n = it.next();
316                 if ( n.isExternal() ) {
317                     _external_nodes_set.add( n );
318                 }
319             }
320         }
321         return _external_nodes_set;
322     }
323
324     /**
325      * Returns the number of duplications of this Phylogeny (int). A return
326      * value of -1 indicates that the number of duplications is unknown.
327      */
328     // public int getNumberOfDuplications() {
329     // return _number_of_duplications;
330     // } // getNumberOfDuplications()
331     /**
332      * Sets the number of duplications of this Phylogeny (int). A value of -1
333      * indicates that the number of duplications is unknown.
334      * 
335      * @param clean_nh
336      *            set to true for clean NH format
337      */
338     // public void setNumberOfDuplications( int i ) {
339     // if ( i < 0 ) {
340     // _number_of_duplications = -1;
341     // }
342     // else {
343     // _number_of_duplications = i;
344     // }
345     // } // setNumberOfDuplications( int )
346     /**
347      * Returns the first external PhylogenyNode.
348      */
349     public PhylogenyNode getFirstExternalNode() {
350         if ( isEmpty() ) {
351             throw new FailedConditionCheckException( "attempt to obtain first external node of empty phylogeney" );
352         }
353         PhylogenyNode node = getRoot();
354         while ( node.isInternal() ) {
355             node = node.getFirstChildNode();
356         }
357         return node;
358     }
359
360     /**
361      * This calculates the height for rooted, tree-shaped phylogenies. The
362      * height is the longest distance from the root to an external node. Please
363      * note. Child nodes of collapsed nodes are ignored -- which is useful for
364      * display purposes but might be misleading for other applications.
365      * 
366      * @return the height for rooted, tree-shaped phylogenies
367      */
368     public double getHeight() {
369         if ( isEmpty() ) {
370             return 0.0;
371         }
372         return calculateSubtreeHeight( getRoot() );
373     }
374
375     public Identifier getIdentifier() {
376         return _identifier;
377     }
378
379     // ---------------------------------------------------------
380     // Modification of Phylogeny topology and Phylogeny appearance
381     // ---------------------------------------------------------
382     private HashMap<Integer, PhylogenyNode> getIdHash() {
383         return _idhash;
384     }
385
386     /**
387      * Returns the name of this Phylogeny.
388      */
389     public String getName() {
390         return _name;
391     }
392
393     /**
394      * Finds the PhylogenyNode of this Phylogeny which has a matching ID number.
395      * Takes O(n) time. After method hashIDs() has been called it runs in
396      * constant time.
397      * 
398      * @param id
399      *            ID number (int) of the PhylogenyNode to find
400      * @return PhylogenyNode with matching ID, null if not found
401      */
402     public PhylogenyNode getNode( final int id ) throws NoSuchElementException {
403         if ( isEmpty() ) {
404             throw new NoSuchElementException( "attempt to get node in an empty phylogeny" );
405         }
406         if ( _idhash != null ) {
407             return _idhash.get( id );
408         }
409         else {
410             for( final PhylogenyNodeIterator iter = iteratorPreorder(); iter.hasNext(); ) {
411                 final PhylogenyNode node = iter.next();
412                 if ( node.getId() == id ) {
413                     return node;
414                 }
415             }
416         }
417         return null;
418     }
419
420     /**
421      * Returns a PhylogenyNode of this Phylogeny which has a matching name.
422      * Throws an Exception if seqname is not present in this or not unique.
423      * 
424      * @param name
425      *            name (String) of PhylogenyNode to find
426      * @return PhylogenyNode with matchin name
427      */
428     public PhylogenyNode getNode( final String name ) {
429         if ( isEmpty() ) {
430             return null;
431         }
432         final List<PhylogenyNode> nodes = getNodes( name );
433         if ( ( nodes == null ) || ( nodes.size() < 1 ) ) {
434             throw new IllegalArgumentException( "node named [" + name + "] not found" );
435         }
436         if ( nodes.size() > 1 ) {
437             throw new IllegalArgumentException( "node named [" + name + "] not unique" );
438         }
439         return nodes.get( 0 );
440     }
441
442     /**
443      * Return Node by TaxonomyId Olivier CHABROL :
444      * olivier.chabrol@univ-provence.fr
445      * 
446      * @param taxonomyID
447      *            search taxonomy identifier
448      * @param nodes
449      *            sublist node to search
450      * @return List node with the same taxonomy identifier
451      */
452     private List<PhylogenyNode> getNodeByTaxonomyID( final String taxonomyID, final List<PhylogenyNode> nodes ) {
453         final List<PhylogenyNode> retour = new ArrayList<PhylogenyNode>();
454         for( final PhylogenyNode node : nodes ) {
455             if ( taxonomyID.equals( PhylogenyMethods.getTaxonomyIdentifier( node ) ) ) {
456                 retour.add( node );
457             }
458         }
459         return retour;
460     }
461
462     /**
463      * Returns a List with references to all Nodes of this Phylogeny which have
464      * a matching name.
465      * 
466      * @param name
467      *            name (String) of Nodes to find
468      * @return Vector of references to Nodes of this Phylogeny with matching
469      *         names
470      * @see #getNodesWithMatchingSpecies(String)
471      */
472     public List<PhylogenyNode> getNodes( final String name ) {
473         if ( isEmpty() ) {
474             return null;
475         }
476         final List<PhylogenyNode> nodes = new ArrayList<PhylogenyNode>();
477         for( final PhylogenyNodeIterator iter = iteratorPreorder(); iter.hasNext(); ) {
478             final PhylogenyNode n = iter.next();
479             if ( n.getName().equals( name ) ) {
480                 nodes.add( n );
481             }
482         }
483         return nodes;
484     }
485
486     public List<PhylogenyNode> getNodesViaSequenceName( final String seq_name ) {
487         if ( isEmpty() ) {
488             return null;
489         }
490         final List<PhylogenyNode> nodes = new ArrayList<PhylogenyNode>();
491         for( final PhylogenyNodeIterator iter = iteratorPreorder(); iter.hasNext(); ) {
492             final PhylogenyNode n = iter.next();
493             if ( n.getNodeData().isHasSequence() && n.getNodeData().getSequence().getName().equals( seq_name ) ) {
494                 nodes.add( n );
495             }
496         }
497         return nodes;
498     }
499
500     public List<PhylogenyNode> getNodesViaTaxonomyCode( final String taxonomy_code ) {
501         if ( isEmpty() ) {
502             return null;
503         }
504         final List<PhylogenyNode> nodes = new ArrayList<PhylogenyNode>();
505         for( final PhylogenyNodeIterator iter = iteratorPreorder(); iter.hasNext(); ) {
506             final PhylogenyNode n = iter.next();
507             if ( n.getNodeData().isHasTaxonomy()
508                     && n.getNodeData().getTaxonomy().getTaxonomyCode().equals( taxonomy_code ) ) {
509                 nodes.add( n );
510             }
511         }
512         return nodes;
513     }
514
515     /**
516      * Returns a Vector with references to all Nodes of this Phylogeny which
517      * have a matching species name.
518      * 
519      * @param specname
520      *            species name (String) of Nodes to find
521      * @return Vector of references to Nodes of this Phylogeny with matching
522      *         species names.
523      * @see #getNodes(String)
524      */
525     public List<PhylogenyNode> getNodesWithMatchingSpecies( final String specname ) {
526         if ( isEmpty() ) {
527             return null;
528         }
529         final List<PhylogenyNode> nodes = new ArrayList<PhylogenyNode>();
530         for( final PhylogenyNodeIterator iter = iteratorPreorder(); iter.hasNext(); ) {
531             final PhylogenyNode n = iter.next();
532             if ( PhylogenyMethods.getSpecies( n ).equals( specname ) ) {
533                 nodes.add( n );
534             }
535         }
536         return nodes;
537     }
538
539     public PhylogenyNode getNodeViaSequenceName( final String seq_name ) {
540         if ( isEmpty() ) {
541             return null;
542         }
543         final List<PhylogenyNode> nodes = getNodesViaSequenceName( seq_name );
544         if ( ( nodes == null ) || ( nodes.size() < 1 ) ) {
545             throw new IllegalArgumentException( "node with sequence named [" + seq_name + "] not found" );
546         }
547         if ( nodes.size() > 1 ) {
548             throw new IllegalArgumentException( "node with sequence named [" + seq_name + "] not unique" );
549         }
550         return nodes.get( 0 );
551     }
552
553     public PhylogenyNode getNodeViaTaxonomyCode( final String taxonomy_code ) {
554         if ( isEmpty() ) {
555             return null;
556         }
557         final List<PhylogenyNode> nodes = getNodesViaTaxonomyCode( taxonomy_code );
558         if ( ( nodes == null ) || ( nodes.size() < 1 ) ) {
559             throw new IllegalArgumentException( "node with taxonomy code \"" + taxonomy_code + "\" not found" );
560         }
561         if ( nodes.size() > 1 ) {
562             throw new IllegalArgumentException( "node with taxonomy code \"" + taxonomy_code + "\" not unique" );
563         }
564         return nodes.get( 0 );
565     }
566
567     /**
568      * This is time-inefficient since it runs a iterator each time it is called.
569      * 
570      */
571     public int getNodeCount() {
572         if ( isEmpty() ) {
573             return 0;
574         }
575         int c = 0;
576         for( final PhylogenyNodeIterator it = iteratorPreorder(); it.hasNext(); it.next() ) {
577             ++c;
578         }
579         return c;
580     }
581
582     public int getNumberOfBranches() {
583         if ( isEmpty() ) {
584             return 0;
585         }
586         int c = 0;
587         for( final PhylogenyNodeIterator iter = iteratorPreorder(); iter.hasNext(); iter.next() ) {
588             ++c;
589         }
590         if ( !isRooted() ) {
591             --c;
592         }
593         return c;
594     }
595
596     /**
597      * Returns the sum of external Nodes of this Phylogeny (int).
598      */
599     public int getNumberOfExternalNodes() {
600         if ( isEmpty() ) {
601             return 0;
602         }
603         return getExternalNodes().size();
604     }
605
606     /**
607      * Returns all paralogs of the external PhylogenyNode n of this Phylogeny.
608      * paralog are returned as List of node references.
609      * <p>
610      * PRECONDITION: This tree must be binary and rooted, and speciation -
611      * duplication need to be assigned for each of its internal Nodes.
612      * <p>
613      * Returns null if this Phylogeny is empty or if n is internal.
614      * <p>
615      * (Last modified: 11/22/00) Olivier CHABROL :
616      * olivier.chabrol@univ-provence.fr
617      * 
618      * @param n
619      *            external PhylogenyNode whose orthologs are to be returned
620      * @return Vector of references to all orthologous Nodes of PhylogenyNode n
621      *         of this Phylogeny, null if this Phylogeny is empty or if n is
622      *         internal
623      */
624     public List<PhylogenyNode> getParalogousNodes( final PhylogenyNode n, final String[] taxonomyCodeRange ) {
625         PhylogenyNode node = n;
626         PhylogenyNode prev = null;
627         final List<PhylogenyNode> v = new ArrayList<PhylogenyNode>();
628         final Map<PhylogenyNode, List<String>> map = new HashMap<PhylogenyNode, List<String>>();
629         getTaxonomyMap( getRoot(), map );
630         if ( !node.isExternal() || isEmpty() ) {
631             return null;
632         }
633         final String searchNodeSpeciesId = PhylogenyMethods.getTaxonomyIdentifier( n );
634         if ( !node.isExternal() || isEmpty() ) {
635             return null;
636         }
637         List<String> taxIdList = null;
638         final List<String> taxonomyCodeRangeList = Arrays.asList( taxonomyCodeRange );
639         while ( !node.isRoot() ) {
640             prev = node;
641             node = node.getParent();
642             taxIdList = map.get( node );
643             if ( node.isDuplication() && isContains( taxIdList, taxonomyCodeRangeList ) ) {
644                 if ( node.getChildNode1() == prev ) {
645                     v.addAll( getNodeByTaxonomyID( searchNodeSpeciesId, node.getChildNode2()
646                             .getAllExternalDescendants() ) );
647                 }
648                 else {
649                     v.addAll( getNodeByTaxonomyID( searchNodeSpeciesId, node.getChildNode1()
650                             .getAllExternalDescendants() ) );
651                 }
652             }
653         }
654         return v;
655     }
656
657     public Collection<SequenceRelation.SEQUENCE_RELATION_TYPE> getRelevantSequenceRelationTypes() {
658         if ( _relevant_sequence_relation_types == null ) {
659             _relevant_sequence_relation_types = new Vector<SEQUENCE_RELATION_TYPE>();
660         }
661         return _relevant_sequence_relation_types;
662     }
663
664     /**
665      * Returns the root PhylogenyNode of this Phylogeny.
666      */
667     public PhylogenyNode getRoot() {
668         return _root;
669     }
670
671     public Collection<Sequence> getSequenceRelationQueries() {
672         return _sequenceRelationQueries;
673     }
674
675     /**
676      * List all species contains in all leaf under a node Olivier CHABROL :
677      * olivier.chabrol@univ-provence.fr
678      * 
679      * @param node
680      *            PhylogenyNode whose sub node species are returned
681      * @return species contains in all leaf under the param node
682      */
683     private List<String> getSubNodeTaxonomy( final PhylogenyNode node ) {
684         final List<String> taxonomyList = new ArrayList<String>();
685         final List<PhylogenyNode> childs = node.getAllExternalDescendants();
686         String speciesId = null;
687         for( final PhylogenyNode phylogenyNode : childs ) {
688             // taxId = new Long(phylogenyNode.getTaxonomyID());
689             speciesId = PhylogenyMethods.getTaxonomyIdentifier( phylogenyNode );
690             if ( !taxonomyList.contains( speciesId ) ) {
691                 taxonomyList.add( speciesId );
692             }
693         }
694         return taxonomyList;
695     }
696
697     /**
698      * Create a map [<PhylogenyNode, List<String>], the list contains the
699      * species contains in all leaf under phylogeny node Olivier CHABROL :
700      * olivier.chabrol@univ-provence.fr
701      * 
702      * @param node
703      *            the tree root node
704      * @param map
705      *            map to fill
706      */
707     private void getTaxonomyMap( final PhylogenyNode node, final Map<PhylogenyNode, List<String>> map ) {
708         // node is leaf
709         if ( node.isExternal() ) {
710             return;
711         }
712         map.put( node, getSubNodeTaxonomy( node ) );
713         getTaxonomyMap( node.getChildNode1(), map );
714         getTaxonomyMap( node.getChildNode2(), map );
715     }
716
717     public String getType() {
718         return _type;
719     }
720
721     /**
722      * Hashes the ID number of each PhylogenyNode of this Phylogeny to its
723      * corresponding PhylogenyNode, in order to make method getNode( id ) run in
724      * constant time. Important: The user is responsible for calling this method
725      * (again) after this Phylogeny has been changed/created/renumbered.
726      */
727     public void hashIDs() {
728         if ( isEmpty() ) {
729             return;
730         }
731         setIdHash( new HashMap<Integer, PhylogenyNode>() );
732         for( final PhylogenyNodeIterator iter = iteratorPreorder(); iter.hasNext(); ) {
733             final PhylogenyNode node = iter.next();
734             getIdHash().put( node.getId(), node );
735         }
736     }
737
738     /**
739      * Deletes this Phylogeny.
740      */
741     public void init() {
742         _root = null;
743         _rooted = false;
744         _name = "";
745         _description = "";
746         _type = "";
747         _distance_unit = "";
748         _idhash = null;
749         _confidence = null;
750         _identifier = null;
751         _rerootable = true;
752         setAllowMultipleParents( Phylogeny.ALLOW_MULTIPLE_PARENTS_DEFAULT );
753     }
754
755     private boolean isAllowMultipleParents() {
756         return _allow_multiple_parents;
757     }
758
759     /**
760      * Returns whether this is a completely binary tree (i.e. all internal nodes
761      * are bifurcations).
762      * 
763      */
764     public boolean isCompletelyBinary() {
765         if ( isEmpty() ) {
766             return false;
767         }
768         for( final PhylogenyNodeIterator iter = iteratorPreorder(); iter.hasNext(); ) {
769             final PhylogenyNode node = iter.next();
770             if ( node.isInternal() && ( node.getNumberOfDescendants() != 2 ) ) {
771                 return false;
772             }
773         }
774         return true;
775     }
776
777     /**
778      * Util method to check if all element of a list is contains in the
779      * rangeList. Olivier CHABROL : olivier.chabrol@univ-provence.fr
780      * 
781      * @param list
782      *            list to be check
783      * @param rangeList
784      *            the range list to compare
785      * @return <code>true</code> if all param list element are contains in param
786      *         rangeList, <code>false</code> otherwise.
787      */
788     private boolean isContains( final List<String> list, final List<String> rangeList ) {
789         if ( list.size() > rangeList.size() ) {
790             return false;
791         }
792         String l = null;
793         for( final Iterator<String> iterator = list.iterator(); iterator.hasNext(); ) {
794             l = iterator.next();
795             if ( !rangeList.contains( l ) ) {
796                 return false;
797             }
798         }
799         return true;
800     }
801
802     /**
803      * Checks whether a Phylogeny object is deleted (or empty).
804      * 
805      * @return true if the tree is deleted (or empty), false otherwise
806      */
807     public boolean isEmpty() {
808         return ( getRoot() == null );
809     }
810
811     public boolean isRerootable() {
812         return _rerootable;
813     }
814
815     /**
816      * Returns true is this Phylogeny is rooted.
817      */
818     public boolean isRooted() {
819         return _rooted;
820     } // isRooted()
821
822     public boolean isTree() {
823         return true;
824     }
825
826     public PhylogenyNodeIterator iteratorExternalForward() {
827         return new ExternalForwardIterator( this );
828     }
829
830     public PhylogenyNodeIterator iteratorLevelOrder() {
831         return new LevelOrderTreeIterator( this );
832     }
833
834     public PhylogenyNodeIterator iteratorPostorder() {
835         return new PostorderTreeIterator( this );
836     }
837
838     public PhylogenyNodeIterator iteratorPreorder() {
839         return new PreorderTreeIterator( this );
840     }
841
842     /**
843      * Resets the ID numbers of the nodes of this Phylogeny in level order,
844      * starting with start_label (for the root). <br>
845      * WARNING. After this method has been called, node IDs are no longer
846      * unique. 
847      */
848     public void levelOrderReID() {
849         if ( isEmpty() ) {
850             return;
851         }
852         _idhash = null;
853         int max = 0;
854         for( final PhylogenyNodeIterator it = iteratorPreorder(); it.hasNext(); ) {
855             final PhylogenyNode node = it.next();
856             if ( node.isRoot() ) {
857                 node.setId( PhylogenyNode.getNodeCount() );
858             }
859             else {
860                 node.setId( node.getParent().getId() + 1 );
861                 if ( node.getId() > max ) {
862                     max = node.getId();
863                 }
864             }
865         }
866         PhylogenyNode.setNodeCount( max + 1 );
867     }
868
869     /**
870      * Arranges the order of childern for each node of this Phylogeny in such a
871      * way that either the branch with more children is on top (right) or on
872      * bottom (left), dependent on the value of boolean order.
873      * 
874      * @param order
875      *            decides in which direction to order
876      */
877     public void orderAppearance( final boolean order ) throws RuntimeException {
878         if ( !isTree() ) {
879             throw new FailedConditionCheckException( "Attempt to order appearance on phylogeny which is not tree-like." );
880         }
881         if ( isEmpty() ) {
882             return;
883         }
884         orderAppearanceHelper( getRoot(), order );
885     }
886
887     // Helper method for "orderAppearance(boolean)".
888     // Traverses this Phylogeny recusively.
889     private void orderAppearanceHelper( final PhylogenyNode n, final boolean order ) {
890         if ( n.isExternal() ) {
891             return;
892         }
893         else {
894             PhylogenyNode temp = null;
895             // FIXME
896             if ( ( n.getNumberOfDescendants() == 2 )
897                     && ( n.getChildNode1().getNumberOfExternalNodes() != n.getChildNode2().getNumberOfExternalNodes() )
898                     && ( ( n.getChildNode1().getNumberOfExternalNodes() < n.getChildNode2().getNumberOfExternalNodes() ) == order ) ) {
899                 temp = n.getChildNode1();
900                 n.setChild1( n.getChildNode2() );
901                 n.setChild2( temp );
902             }
903             for( int i = 0; i < n.getNumberOfDescendants(); ++i ) {
904                 orderAppearanceHelper( n.getChildNode( i ), order );
905             }
906         }
907     }
908
909     public void preOrderReId() {
910         if ( isEmpty() ) {
911             return;
912         }
913         setIdHash( null );
914         int i = PhylogenyNode.getNodeCount();
915         for( final PhylogenyNodeIterator it = iteratorPreorder(); it.hasNext(); ) {
916             it.next().setId( i++ );
917         }
918         PhylogenyNode.setNodeCount( i );
919     }
920
921     /**
922      * Prints descriptions of all external Nodes of this Phylogeny to
923      * System.out.
924      */
925     public void printExtNodes() {
926         if ( isEmpty() ) {
927             return;
928         }
929         for( final PhylogenyNodeIterator iter = iteratorExternalForward(); iter.hasNext(); ) {
930             System.out.println( iter.next() + "\n" );
931         }
932     }
933
934     /**
935      * (Re)counts the number of children for each PhylogenyNode of this
936      * Phylogeny. As an example, this method needs to be called after a
937      * Phylogeny has been reRooted and it is to be displayed.
938      * 
939      * @param consider_collapsed_nodes
940      *            set to true to take into account collapsed nodes (collapsed
941      *            nodes have 1 child).
942      */
943     public void recalculateNumberOfExternalDescendants( final boolean consider_collapsed_nodes ) {
944         if ( isEmpty() ) {
945             return;
946         }
947         for( final PhylogenyNodeIterator iter = iteratorPostorder(); iter.hasNext(); ) {
948             final PhylogenyNode node = iter.next();
949             if ( node.isExternal() || ( consider_collapsed_nodes && node.isCollapse() ) ) {
950                 node.setSumExtNodes( 1 );
951             }
952             else {
953                 int sum = 0;
954                 for( int i = 0; i < node.getNumberOfDescendants(); ++i ) {
955                     sum += node.getChildNode( i ).getNumberOfExternalNodes();
956                 }
957                 node.setSumExtNodes( sum );
958             }
959         }
960     }
961
962     /**
963      * Places the root of this Phylogeny on the parent branch of the
964      * PhylogenyNode with a corresponding ID. The new root is always placed on
965      * the middle of the branch. If the resulting reRooted Phylogeny is to be
966      * used any further, in most cases the following methods have to be called
967      * on the resulting Phylogeny:
968      * <p>
969      * <li>recalculateNumberOfExternalDescendants(boolean)
970      * <li>recalculateAndReset()
971      * 
972      * @param id
973      *            ID (int) of PhylogenyNode of this Phylogeny
974      */
975     public void reRoot( final int id ) {
976         reRoot( getNode( id ) );
977     }
978
979     /**
980      * Places the root of this Phylogeny on Branch b. The new root is always
981      * placed on the middle of the branch b.
982      * 
983      */
984     public void reRoot( final PhylogenyBranch b ) {
985         final PhylogenyNode n1 = b.getFirstNode();
986         final PhylogenyNode n2 = b.getSecondNode();
987         if ( n1.isExternal() ) {
988             reRoot( n1 );
989         }
990         else if ( n2.isExternal() ) {
991             reRoot( n2 );
992         }
993         else if ( ( n2 == n1.getChildNode1() ) || ( n2 == n1.getChildNode2() ) ) {
994             reRoot( n2 );
995         }
996         else if ( ( n1 == n2.getChildNode1() ) || ( n1 == n2.getChildNode2() ) ) {
997             reRoot( n1 );
998         }
999         else if ( ( n1.getParent() != null ) && n1.getParent().isRoot()
1000                 && ( ( n1.getParent().getChildNode1() == n2 ) || ( n1.getParent().getChildNode2() == n2 ) ) ) {
1001             reRoot( n1 );
1002         }
1003         else {
1004             throw new IllegalArgumentException( "reRoot( Branch b ): b is not a branch." );
1005         }
1006     }
1007
1008     /**
1009      * Places the root of this Phylogeny on the parent branch PhylogenyNode n.
1010      * The new root is always placed on the middle of the branch.
1011      * <p>
1012      * If the resulting reRooted Phylogeny is to be used any further, in most
1013      * cases the following three methods have to be called on the resulting
1014      * Phylogeny:
1015      * <ul>
1016      * <li>recalculateNumberOfExternalDescendants(boolean) <li>recalculateAndReset()
1017      * </ul>
1018      * <p>
1019      * (Last modified: 10/01/01)
1020      * 
1021      * @param n
1022      *            PhylogenyNode of this Phylogeny\
1023      */
1024     public void reRoot( final PhylogenyNode n ) {
1025         reRoot( n, -1 );
1026     }
1027
1028     public void reRoot( final PhylogenyNode n, final double distance_n_to_parent ) {
1029         if ( isEmpty() || ( getNumberOfExternalNodes() < 2 ) ) {
1030             return;
1031         }
1032         setRooted( true );
1033         if ( n.isRoot() ) {
1034             return;
1035         }
1036         else if ( n.getParent().isRoot() ) {
1037             if ( ( n.getParent().getNumberOfDescendants() == 2 ) && ( distance_n_to_parent >= 0 ) ) {
1038                 final double d = n.getParent().getChildNode1().getDistanceToParent()
1039                         + n.getParent().getChildNode2().getDistanceToParent();
1040                 PhylogenyNode other;
1041                 if ( n.getChildNodeIndex() == 0 ) {
1042                     other = n.getParent().getChildNode2();
1043                 }
1044                 else {
1045                     other = n.getParent().getChildNode1();
1046                 }
1047                 n.setDistanceToParent( distance_n_to_parent );
1048                 final double dm = d - distance_n_to_parent;
1049                 if ( dm >= 0 ) {
1050                     other.setDistanceToParent( dm );
1051                 }
1052                 else {
1053                     other.setDistanceToParent( 0 );
1054                 }
1055             }
1056             if ( n.getParent().getNumberOfDescendants() > 2 ) {
1057                 final int index = n.getChildNodeIndex();
1058                 final double dn = n.getDistanceToParent();
1059                 final PhylogenyNode prev_root = getRoot();
1060                 prev_root.getDescendants().remove( index );
1061                 final PhylogenyNode new_root = new PhylogenyNode();
1062                 new_root.setChildNode( 0, n );
1063                 new_root.setChildNode( 1, prev_root );
1064                 if ( n.getBranchDataDirectly() != null ) {
1065                     prev_root.setBranchData( ( BranchData ) n.getBranchDataDirectly().copy() );
1066                 }
1067                 setRoot( new_root );
1068                 if ( distance_n_to_parent >= 0 ) {
1069                     n.setDistanceToParent( distance_n_to_parent );
1070                     final double d = dn - distance_n_to_parent;
1071                     if ( d >= 0 ) {
1072                         prev_root.setDistanceToParent( d );
1073                     }
1074                     else {
1075                         prev_root.setDistanceToParent( 0 );
1076                     }
1077                 }
1078                 else {
1079                     if ( dn >= 0 ) {
1080                         final double d = dn / 2.0;
1081                         n.setDistanceToParent( d );
1082                         prev_root.setDistanceToParent( d );
1083                     }
1084                 }
1085             }
1086         }
1087         else {
1088             PhylogenyNode a = n;
1089             PhylogenyNode b = null;
1090             PhylogenyNode c = null;
1091             final PhylogenyNode new_root = new PhylogenyNode();
1092             double distance1 = 0.0;
1093             double distance2 = 0.0;
1094             BranchData branch_data_1 = null;
1095             BranchData branch_data_2 = null;
1096             b = a.getParent();
1097             c = b.getParent();
1098             new_root.setChildNode( 0, a );
1099             new_root.setChildNode( 1, b );
1100             distance1 = c.getDistanceToParent();
1101             if ( c.getBranchDataDirectly() != null ) {
1102                 branch_data_1 = ( BranchData ) c.getBranchDataDirectly().copy();
1103             }
1104             c.setDistanceToParent( b.getDistanceToParent() );
1105             if ( b.getBranchDataDirectly() != null ) {
1106                 c.setBranchData( ( BranchData ) b.getBranchDataDirectly().copy() );
1107             }
1108             if ( a.getBranchDataDirectly() != null ) {
1109                 b.setBranchData( ( BranchData ) a.getBranchDataDirectly().copy() );
1110             }
1111             // New root is always placed in the middle of the branch:
1112             if ( a.getDistanceToParent() == PhylogenyDataUtil.BRANCH_LENGTH_DEFAULT ) {
1113                 b.setDistanceToParent( PhylogenyDataUtil.BRANCH_LENGTH_DEFAULT );
1114             }
1115             else {
1116                 if ( distance_n_to_parent >= 0.0 ) {
1117                     final double diff = a.getDistanceToParent() - distance_n_to_parent;
1118                     a.setDistanceToParent( distance_n_to_parent );
1119                     b.setDistanceToParent( diff >= 0.0 ? diff : 0.0 );
1120                 }
1121                 else {
1122                     final double d = a.getDistanceToParent() / 2.0;
1123                     a.setDistanceToParent( d );
1124                     b.setDistanceToParent( d );
1125                 }
1126             }
1127             b.setChildNodeOnly( a.getChildNodeIndex( b ), c );
1128             // moving to the old root, swapping references:
1129             while ( !c.isRoot() ) {
1130                 a = b;
1131                 b = c;
1132                 c = c.getParent();
1133                 b.setChildNodeOnly( a.getChildNodeIndex( b ), c );
1134                 b.setParent( a );
1135                 distance2 = c.getDistanceToParent();
1136                 branch_data_2 = c.getBranchDataDirectly();
1137                 c.setDistanceToParent( distance1 );
1138                 c.setBranchData( branch_data_1 );
1139                 distance1 = distance2;
1140                 branch_data_1 = branch_data_2;
1141             }
1142             // removing the old root:
1143             if ( c.getNumberOfDescendants() == 2 ) {
1144                 final PhylogenyNode node = c.getChildNode( 1 - b.getChildNodeIndex( c ) );
1145                 node.setParent( b );
1146                 if ( ( c.getDistanceToParent() == PhylogenyDataUtil.BRANCH_LENGTH_DEFAULT )
1147                         && ( node.getDistanceToParent() == PhylogenyDataUtil.BRANCH_LENGTH_DEFAULT ) ) {
1148                     node.setDistanceToParent( PhylogenyDataUtil.BRANCH_LENGTH_DEFAULT );
1149                 }
1150                 else {
1151                     node.setDistanceToParent( ( c.getDistanceToParent() >= 0.0 ? c.getDistanceToParent() : 0.0 )
1152                             + ( node.getDistanceToParent() >= 0.0 ? node.getDistanceToParent() : 0.0 ) );
1153                 }
1154                 if ( c.getBranchDataDirectly() != null ) {
1155                     node.setBranchData( ( BranchData ) c.getBranchDataDirectly().copy() );
1156                 }
1157                 for( int i = 0; i < b.getNumberOfDescendants(); ++i ) {
1158                     if ( b.getChildNode( i ) == c ) {
1159                         b.setChildNodeOnly( i, node );
1160                         break;
1161                     }
1162                 }
1163             }
1164             else {
1165                 c.setParent( b );
1166                 c.removeChildNode( b.getChildNodeIndex( c ) );
1167             }
1168             setRoot( new_root );
1169         }
1170     }
1171
1172     /**
1173      * Sets all Nodes of this Phylogeny to not-collapsed.
1174      * <p>
1175      * In most cases methods adjustNodeCount(false) and recalculateAndReset()
1176      * need to be called after this method has been called.
1177      */
1178     public void setAllNodesToNotCollapse() {
1179         if ( isEmpty() ) {
1180             return;
1181         }
1182         for( final PhylogenyNodeIterator iter = iteratorPreorder(); iter.hasNext(); ) {
1183             final PhylogenyNode node = iter.next();
1184             node.setCollapse( false );
1185         }
1186     }
1187
1188     private void setAllowMultipleParents( final boolean allow_multiple_parents ) {
1189         _allow_multiple_parents = allow_multiple_parents;
1190     }
1191
1192     public void setConfidence( final Confidence confidence ) {
1193         _confidence = confidence;
1194     }
1195
1196     public void setDescription( final String description ) {
1197         _description = description;
1198     }
1199
1200     public void setDistanceUnit( final String _distance_unit ) {
1201         this._distance_unit = _distance_unit;
1202     }
1203
1204     public void setIdentifier( final Identifier identifier ) {
1205         _identifier = identifier;
1206     }
1207
1208     void setIdHash( final HashMap<Integer, PhylogenyNode> idhash ) {
1209         _idhash = idhash;
1210     }
1211
1212     /**
1213      * Sets the indicators of all Nodes of this Phylogeny to 0.
1214      */
1215     public void setIndicatorsToZero() {
1216         if ( isEmpty() ) {
1217             return;
1218         }
1219         for( final PhylogenyNodeIterator iter = iteratorPreorder(); iter.hasNext(); ) {
1220             iter.next().setIndicator( ( byte ) 0 );
1221         }
1222     } // setIndicatorsToZero()
1223
1224     /**
1225      * Sets the name of this Phylogeny to s.
1226      */
1227     public void setName( final String s ) {
1228         _name = s;
1229     }
1230
1231     public void setRelevantSequenceRelationTypes( final Collection<SequenceRelation.SEQUENCE_RELATION_TYPE> types ) {
1232         _relevant_sequence_relation_types = types;
1233     }
1234
1235     public void setRerootable( final boolean rerootable ) {
1236         _rerootable = rerootable;
1237     }
1238
1239     public void setRoot( final PhylogenyNode n ) {
1240         _root = n;
1241     } // setRoot( PhylogenyNode )
1242
1243     /**
1244      * Sets whether this Phylogeny is rooted or not.
1245      */
1246     public void setRooted( final boolean b ) {
1247         _rooted = b;
1248     } // setRooted( boolean )
1249
1250     public void setSequenceRelationQueries( final Collection<Sequence> sequencesByName ) {
1251         _sequenceRelationQueries = sequencesByName;
1252     }
1253
1254     public void setType( final String type ) {
1255         _type = type;
1256     }
1257
1258     /**
1259      * Swaps the the two childern of a PhylogenyNode node of this Phylogeny.
1260      * <p>
1261      * (Last modified: 06/13/01)
1262      * 
1263      * @param node
1264      *            a PhylogenyNode of this Phylogeny
1265      */
1266     public void swapChildren( final PhylogenyNode node ) throws RuntimeException {
1267         if ( !isTree() ) {
1268             throw new FailedConditionCheckException( "Attempt to swap children on phylogeny which is not tree-like." );
1269         }
1270         if ( isEmpty() || node.isExternal() || ( node.getNumberOfDescendants() < 2 ) ) {
1271             return;
1272         }
1273         final PhylogenyNode first = node.getFirstChildNode();
1274         for( int i = 1; i < node.getNumberOfDescendants(); ++i ) {
1275             node.setChildNode( i - 1, node.getChildNode( i ) );
1276         }
1277         node.setChildNode( node.getNumberOfDescendants() - 1, first );
1278     } // swapChildren( PhylogenyNode )
1279
1280     public String toNewHampshire() {
1281         return toNewHampshire( false, false );
1282     }
1283
1284     public String toNewHampshire( final boolean simple_nh, final boolean write_conf_values_in_branckets_in_nh ) {
1285         try {
1286             return new PhylogenyWriter().toNewHampshire( this, simple_nh, true, write_conf_values_in_branckets_in_nh )
1287                     .toString();
1288         }
1289         catch ( final IOException e ) {
1290             throw new Error( "this should not have happend: " + e.getMessage() );
1291         }
1292     }
1293
1294     public String toNewHampshireX() {
1295         try {
1296             return new PhylogenyWriter().toNewHampshireX( this ).toString();
1297         }
1298         catch ( final IOException e ) {
1299             throw new Error( "this should not have happend: " + e.getMessage() );
1300         }
1301     }
1302
1303     public String toNexus( final boolean write_conf_values_in_branckets_in_nh ) {
1304         try {
1305             return new PhylogenyWriter().toNexus( this, write_conf_values_in_branckets_in_nh ).toString();
1306         }
1307         catch ( final IOException e ) {
1308             throw new Error( "this should not have happend: " + e.getMessage() );
1309         }
1310     }
1311
1312     public String toPhyloXML( final int phyloxml_level ) {
1313         try {
1314             return new PhylogenyWriter().toPhyloXML( this, phyloxml_level ).toString();
1315         }
1316         catch ( final IOException e ) {
1317             throw new Error( "this should not have happend: " + e.getMessage() );
1318         }
1319     }
1320
1321     // ---------------------------------------------------------
1322     // Writing of Phylogeny to Strings
1323     // ---------------------------------------------------------
1324     /**
1325      * Converts this Phylogeny to a New Hampshire X (String) representation.
1326      * 
1327      * @return New Hampshire X (String) representation of this
1328      * @see #toNewHampshireX()
1329      */
1330     @Override
1331     public String toString() {
1332         return toNewHampshireX();
1333     }
1334
1335     /**
1336      * Removes the root PhylogenyNode this Phylogeny.
1337      */
1338     public void unRoot() throws RuntimeException {
1339         if ( !isTree() ) {
1340             throw new FailedConditionCheckException( "Attempt to unroot a phylogeny which is not tree-like." );
1341         }
1342         if ( isEmpty() ) {
1343             return;
1344         }
1345         setIndicatorsToZero();
1346         if ( !isRooted() || ( getNumberOfExternalNodes() <= 1 ) ) {
1347             return;
1348         }
1349         setRooted( false );
1350         return;
1351     } // unRoot()
1352 }