90f7faf15c4e9970bef623e863741e2800ac1502
[jalview.git] / src / jalview / io / NewickFile.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 // NewickFile.java
22 // Tree I/O
23 // http://evolution.genetics.washington.edu/phylip/newick_doc.html
24 // TODO: Implement Basic NHX tag parsing and preservation
25 // TODO: http://evolution.genetics.wustl.edu/eddy/forester/NHX.html
26 // TODO: Extended SequenceNodeI to hold parsed NHX strings
27 package jalview.io;
28
29 import jalview.datamodel.SequenceNode;
30 import jalview.util.MessageManager;
31
32 import java.io.BufferedReader;
33 import java.io.File;
34 import java.io.FileReader;
35 import java.io.IOException;
36 import java.util.StringTokenizer;
37
38 import com.stevesoft.pat.Regex;
39
40 /**
41  * Parse a new hanpshire style tree Caveats: NHX files are NOT supported and the
42  * tree distances and topology are unreliable when they are parsed. TODO: on
43  * this: NHX codes are appended in comments beginning with &&NHX. The codes are
44  * given below (from http://www.phylosoft.org/forester/NHX.html): Element Type
45  * Description Corresponding phyloXML element (parent element in parentheses) no
46  * tag string name of this node/clade (MUST BE FIRST, IF ASSIGNED)
47  * <name>(<clade>) : decimal branch length to parent node (MUST BE SECOND, IF
48  * ASSIGNED) <branch_length>(<clade>) :GN= string gene name <name>(<sequence>)
49  * :AC= string sequence accession <accession>(<sequence>) :ND= string node
50  * identifier - if this is being used, it has to be unique within each phylogeny
51  * <node_id>(<clade>) :B= decimal confidence value for parent branch
52  * <confidence>(<clade>) :D= 'T', 'F', or '?' 'T' if this node represents a
53  * duplication event - 'F' if this node represents a speciation event, '?' if
54  * this node represents an unknown event (D= tag should be replaced by Ev= tag)
55  * n/a :Ev=duplications>speciations>gene losses>event type>duplication type int
56  * int int string string event (replaces the =D tag), number of duplication,
57  * speciation, and gene loss events, type of event (transfer, fusion, root,
58  * unknown, other, speciation_duplication_loss, unassigned) <events>(<clade>)
59  * :E= string EC number at this node <annotation>(<sequence>) :Fu= string
60  * function at this node <annotation>(<sequence>)
61  * :DS=protein-length>from>to>support>name>from>... int int int double string
62  * int ... domain structure at this node <domain_architecture>(<sequence>) :S=
63  * string species name of the species/phylum at this node <taxonomy>(<clade>)
64  * :T= integer taxonomy ID of the species/phylum at this node <id>(<taxonomy>)
65  * :W= integer width of parent branch <width>(<clade>) :C=rrr.ggg.bbb
66  * integer.integer.integer color of parent branch <color>(<clade>) :Co= 'Y' or
67  * 'N' collapse this node when drawing the tree (default is not to collapse) n/a
68  * :XB= string custom data associated with a branch <property>(<clade>) :XN=
69  * string custom data associated with a node <property>(<clade>) :O= integer
70  * orthologous to this external node n/a :SN= integer subtree neighbors n/a :SO=
71  * integer super orthologous (no duplications on paths) to this external node
72  * n/a
73  * 
74  * @author Jim Procter
75  * @version $Revision$
76  */
77 public class NewickFile extends FileParse
78 {
79   SequenceNode root;
80
81   private boolean HasBootstrap = false;
82
83   private boolean HasDistances = false;
84
85   private boolean RootHasDistance = false;
86
87   // File IO Flags
88   boolean ReplaceUnderscores = false;
89
90   boolean printRootInfo = true;
91
92   private Regex[] NodeSafeName = new Regex[]
93   { new Regex().perlCode("m/[\\[,:'()]/"), // test for
94       // requiring
95       // quotes
96       new Regex().perlCode("s/'/''/"), // escaping quote
97       // characters
98       new Regex().perlCode("s/\\/w/_/") // unqoted whitespace
99   // transformation
100   };
101
102   char QuoteChar = '\'';
103
104   /**
105    * Creates a new NewickFile object.
106    * 
107    * @param inStr
108    *          DOCUMENT ME!
109    * 
110    * @throws IOException
111    *           DOCUMENT ME!
112    */
113   public NewickFile(String inStr) throws IOException
114   {
115     super(inStr, "Paste");
116   }
117
118   /**
119    * Creates a new NewickFile object.
120    * 
121    * @param inFile
122    *          DOCUMENT ME!
123    * @param type
124    *          DOCUMENT ME!
125    * 
126    * @throws IOException
127    *           DOCUMENT ME!
128    */
129   public NewickFile(String inFile, String type) throws IOException
130   {
131     super(inFile, type);
132   }
133
134   public NewickFile(FileParse source) throws IOException
135   {
136     super(source);
137   }
138
139   /**
140    * Creates a new NewickFile object.
141    * 
142    * @param newtree
143    *          DOCUMENT ME!
144    */
145   public NewickFile(SequenceNode newtree)
146   {
147     root = newtree;
148   }
149
150   /**
151    * Creates a new NewickFile object.
152    * 
153    * @param newtree
154    *          DOCUMENT ME!
155    * @param bootstrap
156    *          DOCUMENT ME!
157    */
158   public NewickFile(SequenceNode newtree, boolean bootstrap)
159   {
160     HasBootstrap = bootstrap;
161     root = newtree;
162   }
163
164   /**
165    * Creates a new NewickFile object.
166    * 
167    * @param newtree
168    *          DOCUMENT ME!
169    * @param bootstrap
170    *          DOCUMENT ME!
171    * @param distances
172    *          DOCUMENT ME!
173    */
174   public NewickFile(SequenceNode newtree, boolean bootstrap,
175           boolean distances)
176   {
177     root = newtree;
178     HasBootstrap = bootstrap;
179     HasDistances = distances;
180   }
181
182   /**
183    * Creates a new NewickFile object.
184    * 
185    * @param newtree
186    *          DOCUMENT ME!
187    * @param bootstrap
188    *          DOCUMENT ME!
189    * @param distances
190    *          DOCUMENT ME!
191    * @param rootdistance
192    *          DOCUMENT ME!
193    */
194   public NewickFile(SequenceNode newtree, boolean bootstrap,
195           boolean distances, boolean rootdistance)
196   {
197     root = newtree;
198     HasBootstrap = bootstrap;
199     HasDistances = distances;
200     RootHasDistance = rootdistance;
201   }
202
203   /**
204    * DOCUMENT ME!
205    * 
206    * @param Error
207    *          DOCUMENT ME!
208    * @param Er
209    *          DOCUMENT ME!
210    * @param r
211    *          DOCUMENT ME!
212    * @param p
213    *          DOCUMENT ME!
214    * @param s
215    *          DOCUMENT ME!
216    * 
217    * @return DOCUMENT ME!
218    */
219   private String ErrorStringrange(String Error, String Er, int r, int p,
220           String s)
221   {
222     return ((Error == null) ? "" : Error)
223             + Er
224             + " at position "
225             + p
226             + " ( "
227             + s.substring(((p - r) < 0) ? 0 : (p - r),
228                     ((p + r) > s.length()) ? s.length() : (p + r)) + " )\n";
229   }
230
231   // @tree annotations
232   // These are set automatically by the reader
233   public boolean HasBootstrap()
234   {
235     return HasBootstrap;
236   }
237
238   /**
239    * DOCUMENT ME!
240    * 
241    * @return DOCUMENT ME!
242    */
243   public boolean HasDistances()
244   {
245     return HasDistances;
246   }
247
248   public boolean HasRootDistance()
249   {
250     return RootHasDistance;
251   }
252
253   /**
254    * parse the filesource as a newick file (new hampshire and/or extended)
255    * 
256    * @throws IOException
257    *           with a line number and character position for badly formatted NH
258    *           strings
259    */
260   public void parse() throws IOException
261   {
262     String nf;
263
264     { // fill nf with complete tree file
265
266       StringBuffer file = new StringBuffer();
267
268       while ((nf = nextLine()) != null)
269       {
270         file.append(nf);
271       }
272
273       nf = file.toString();
274     }
275
276     root = new SequenceNode();
277
278     SequenceNode realroot = null;
279     SequenceNode c = root;
280
281     int d = -1;
282     int cp = 0;
283     // int flen = nf.length();
284
285     String Error = null;
286     String nodename = null;
287     String commentString2 = null; // comments after simple node props
288
289     float DefDistance = (float) 0.001; // @param Default distance for a node -
290     // very very small
291     int DefBootstrap = -1; // @param Default bootstrap for a node
292
293     float distance = DefDistance;
294     int bootstrap = DefBootstrap;
295
296     boolean ascending = false; // flag indicating that we are leaving the
297     // current node
298
299     Regex majorsyms = new Regex(
300             "[(\\['),;]");
301
302     int nextcp = 0;
303     int ncp = cp;
304     boolean parsednodename = false;
305     while (majorsyms.searchFrom(nf, cp) && (Error == null))
306     {
307       int fcp = majorsyms.matchedFrom();
308       char schar;
309       switch (schar = nf.charAt(fcp))
310       {
311       case '(':
312
313         // ascending should not be set
314         // New Internal node
315         if (ascending)
316         {
317           Error = ErrorStringrange(Error, "Unexpected '('", 7, fcp, nf);
318
319           continue;
320         }
321
322         ;
323         d++;
324
325         if (c.right() == null)
326         {
327           c.setRight(new SequenceNode(null, c, null, DefDistance,
328                   DefBootstrap, false));
329           c = (SequenceNode) c.right();
330         }
331         else
332         {
333           if (c.left() != null)
334           {
335             // Dummy node for polytomy - keeps c.left free for new node
336             SequenceNode tmpn = new SequenceNode(null, c, null, 0, 0, true);
337             tmpn.SetChildren(c.left(), c.right());
338             c.setRight(tmpn);
339           }
340
341           c.setLeft(new SequenceNode(null, c, null, DefDistance,
342                   DefBootstrap, false));
343           c = (SequenceNode) c.left();
344         }
345
346         if (realroot == null)
347         {
348           realroot = c;
349         }
350
351         nodename = null;
352         distance = DefDistance;
353         bootstrap = DefBootstrap;
354         cp = fcp + 1;
355
356         break;
357
358       // Deal with quoted fields
359       case '\'':
360
361         Regex qnodename = new Regex(
362                 "'([^']|'')+'");
363
364         if (qnodename.searchFrom(nf, fcp))
365         {
366           int nl = qnodename.stringMatched().length();
367           nodename = new String(qnodename.stringMatched().substring(1,
368                   nl - 1));
369           // unpack any escaped colons
370           Regex xpandquotes = Regex
371                   .perlCode("s/''/'/");
372           String widernodename = xpandquotes.replaceAll(nodename);
373           nodename = widernodename;
374           // jump to after end of quoted nodename
375           nextcp = fcp + nl + 1;
376           parsednodename = true;
377         }
378         else
379         {
380           Error = ErrorStringrange(Error,
381                   "Unterminated quotes for nodename", 7, fcp, nf);
382         }
383
384         break;
385
386       default:
387         if (schar == ';')
388         {
389           if (d != -1)
390           {
391             Error = ErrorStringrange(Error, "Wayward semicolon (depth=" + d
392                     + ")", 7, fcp, nf);
393           }
394           // cp advanced at the end of default
395         }
396         if (schar == '[')
397         {
398           // node string contains Comment or structured/extended NH format info
399           /*
400            * if ((fcp-cp>1 && nf.substring(cp,fcp).trim().length()>1)) { // will
401            * process in remains System.err.println("skipped text:
402            * '"+nf.substring(cp,fcp)+"'"); }
403            */
404           // verify termination.
405           Regex comment = new Regex("]");
406           if (comment.searchFrom(nf, fcp))
407           {
408             // Skip the comment field
409             nextcp = comment.matchedFrom() + 1;
410             warningMessage = "Tree file contained comments which may confuse input algorithm.";
411             break;
412
413             // cp advanced at the end of default to nextcp, ncp is unchanged so
414             // any node info can be read.
415           }
416           else
417           {
418             Error = ErrorStringrange(Error, "Unterminated comment", 3, fcp,
419                     nf);
420           }
421
422           ;
423         }
424         // Parse simpler field strings
425         String fstring = nf.substring(ncp, fcp);
426         // remove any comments before we parse the node info
427         // TODO: test newick file with quoted square brackets in node name (is
428         // this allowed?)
429         while (fstring.indexOf(']') > -1)
430         {
431           int cstart = fstring.indexOf('[');
432           int cend = fstring.indexOf(']');
433           commentString2 = fstring.substring(cstart + 1, cend);
434           fstring = fstring.substring(0, cstart)
435                   + fstring.substring(cend + 1);
436
437         }
438         Regex uqnodename = new Regex(
439                 "\\b([^' :;\\](),]+)");
440         Regex nbootstrap = new Regex(
441                 "\\s*([0-9+]+)\\s*:");
442         Regex ndist = new Regex(
443                 ":([-0-9Ee.+]+)");
444
445         if (!parsednodename
446                 && uqnodename.search(fstring)
447                 && ((uqnodename.matchedFrom(1) == 0) || (fstring
448                         .charAt(uqnodename.matchedFrom(1) - 1) != ':'))) // JBPNote
449         // HACK!
450         {
451           if (nodename == null)
452           {
453             if (ReplaceUnderscores)
454             {
455               nodename = uqnodename.stringMatched(1).replace('_', ' ');
456             }
457             else
458             {
459               nodename = uqnodename.stringMatched(1);
460             }
461           }
462           else
463           {
464             Error = ErrorStringrange(Error,
465                     "File has broken algorithm - overwritten nodename", 10,
466                     fcp, nf);
467           }
468         }
469         // get comment bootstraps
470
471         if (nbootstrap.search(fstring))
472         {
473           if (nbootstrap.stringMatched(1).equals(
474                   uqnodename.stringMatched(1)))
475           {
476             nodename = null; // no nodename here.
477           }
478           if (nodename == null
479                   || nodename.length() == 0
480                   || nbootstrap.matchedFrom(1) > (uqnodename.matchedFrom(1) + uqnodename
481                           .stringMatched().length()))
482           {
483             try
484             {
485               bootstrap = (new Integer(nbootstrap.stringMatched(1)))
486                       .intValue();
487               HasBootstrap = true;
488             } catch (Exception e)
489             {
490               Error = ErrorStringrange(Error,
491                       "Can't parse bootstrap value", 4,
492                       ncp + nbootstrap.matchedFrom(), nf);
493             }
494           }
495         }
496
497         boolean nodehasdistance = false;
498
499         if (ndist.search(fstring))
500         {
501           try
502           {
503             distance = (new Float(ndist.stringMatched(1))).floatValue();
504             HasDistances = true;
505             nodehasdistance = true;
506           } catch (Exception e)
507           {
508             Error = ErrorStringrange(Error,
509                     "Can't parse node distance value", 7,
510                     ncp + ndist.matchedFrom(), nf);
511           }
512         }
513
514         if (ascending)
515         {
516           // Write node info here
517           c.setName(nodename);
518           // Trees without distances still need a render distance
519           c.dist = (HasDistances) ? distance : DefDistance;
520           // be consistent for internal bootstrap defaults too
521           c.setBootstrap((HasBootstrap) ? bootstrap : DefBootstrap);
522           if (c == realroot)
523           {
524             RootHasDistance = nodehasdistance; // JBPNote This is really
525             // UGLY!!! Ensure root node gets
526             // its given distance
527           }
528           parseNHXNodeProps(c, commentString2);
529           commentString2 = null;
530         }
531         else
532         {
533           // Find a place to put the leaf
534           SequenceNode newnode = new SequenceNode(null, c, nodename,
535                   (HasDistances) ? distance : DefDistance,
536                   (HasBootstrap) ? bootstrap : DefBootstrap, false);
537           parseNHXNodeProps(c, commentString2);
538           commentString2 = null;
539
540           if (c.right() == null)
541           {
542             c.setRight(newnode);
543           }
544           else
545           {
546             if (c.left() == null)
547             {
548               c.setLeft(newnode);
549             }
550             else
551             {
552               // Insert a dummy node for polytomy
553               // dummy nodes have distances
554               SequenceNode newdummy = new SequenceNode(null, c, null,
555                       (HasDistances ? 0 : DefDistance), 0, true);
556               newdummy.SetChildren(c.left(), newnode);
557               c.setLeft(newdummy);
558             }
559           }
560         }
561
562         if (ascending)
563         {
564           // move back up the tree from preceding closure
565           c = c.AscendTree();
566
567           if ((d > -1) && (c == null))
568           {
569             Error = ErrorStringrange(
570                     Error,
571                     "File broke algorithm: Lost place in tree (is there an extra ')' ?)",
572                     7, fcp, nf);
573           }
574         }
575
576         if (nf.charAt(fcp) == ')')
577         {
578           d--;
579           ascending = true;
580         }
581         else
582         {
583           if (nf.charAt(fcp) == ',')
584           {
585             if (ascending)
586             {
587               ascending = false;
588             }
589             else
590             {
591               // Just advance focus, if we need to
592               if ((c.left() != null) && (!c.left().isLeaf()))
593               {
594                 c = (SequenceNode) c.left();
595               }
596             }
597           }
598         }
599
600         // Reset new node properties to obvious fakes
601         nodename = null;
602         distance = DefDistance;
603         bootstrap = DefBootstrap;
604         commentString2 = null;
605         parsednodename = false;
606       }
607       if (nextcp == 0)
608       {
609         ncp = cp = fcp + 1;
610       }
611       else
612       {
613         cp = nextcp;
614         nextcp = 0;
615       }
616     }
617
618     if (Error != null)
619     {
620       throw (new IOException(MessageManager.formatMessage("exception.newfile", new String[]{Error.toString()})));
621     }
622     if (root == null)
623     {
624         throw (new IOException(MessageManager.formatMessage("exception.newfile", new String[]{MessageManager.getString("label.no_tree_read_in")})));
625     }
626     // THe next line is failing for topali trees - not sure why yet. if
627     // (root.right()!=null && root.isDummy())
628     root = (SequenceNode) root.right().detach(); // remove the imaginary root.
629
630     if (!RootHasDistance)
631     {
632       root.dist = (HasDistances) ? 0 : DefDistance;
633     }
634   }
635
636   /**
637    * parse NHX codes in comment strings and update NewickFile state flags for
638    * distances and bootstraps, and add any additional properties onto the node.
639    * 
640    * @param c
641    * @param commentString
642    * @param commentString2
643    */
644   private void parseNHXNodeProps(SequenceNode c, String commentString)
645   {
646     // TODO: store raw comment on the sequenceNode so it can be recovered when
647     // tree is output
648     if (commentString != null && commentString.startsWith("&&NHX"))
649     {
650       StringTokenizer st = new StringTokenizer(commentString.substring(5),
651               ":");
652       while (st.hasMoreTokens())
653       {
654         String tok = st.nextToken();
655         int colpos = tok.indexOf("=");
656
657         if (colpos > -1)
658         {
659           String code = tok.substring(0, colpos);
660           String value = tok.substring(colpos + 1);
661           try
662           {
663             // parse out code/value pairs
664             if (code.toLowerCase().equals("b"))
665             {
666               int v = -1;
667               Float iv = new Float(value);
668               v = iv.intValue(); // jalview only does integer bootstraps
669               // currently
670               c.setBootstrap(v);
671               HasBootstrap = true;
672             }
673             // more codes here.
674           } catch (Exception e)
675           {
676             System.err.println("Couldn't parse code '" + code + "' = '"
677                     + value + "'");
678             e.printStackTrace(System.err);
679           }
680         }
681       }
682     }
683
684   }
685
686   /**
687    * DOCUMENT ME!
688    * 
689    * @return DOCUMENT ME!
690    */
691   public SequenceNode getTree()
692   {
693     return root;
694   }
695
696   /**
697    * Generate a newick format tree according to internal flags for bootstraps,
698    * distances and root distances.
699    * 
700    * @return new hampshire tree in a single line
701    */
702   public String print()
703   {
704     synchronized (this)
705     {
706       StringBuffer tf = new StringBuffer();
707       print(tf, root);
708
709       return (tf.append(";").toString());
710     }
711   }
712
713   /**
714    * 
715    * 
716    * Generate a newick format tree according to internal flags for distances and
717    * root distances and user specificied writing of bootstraps.
718    * 
719    * @param withbootstraps
720    *          controls if bootstrap values are explicitly written.
721    * 
722    * @return new hampshire tree in a single line
723    */
724   public String print(boolean withbootstraps)
725   {
726     synchronized (this)
727     {
728       boolean boots = this.HasBootstrap;
729       this.HasBootstrap = withbootstraps;
730
731       String rv = print();
732       this.HasBootstrap = boots;
733
734       return rv;
735     }
736   }
737
738   /**
739    * 
740    * Generate newick format tree according to internal flags for writing root
741    * node distances.
742    * 
743    * @param withbootstraps
744    *          explicitly write bootstrap values
745    * @param withdists
746    *          explicitly write distances
747    * 
748    * @return new hampshire tree in a single line
749    */
750   public String print(boolean withbootstraps, boolean withdists)
751   {
752     synchronized (this)
753     {
754       boolean dists = this.HasDistances;
755       this.HasDistances = withdists;
756
757       String rv = print(withbootstraps);
758       this.HasDistances = dists;
759
760       return rv;
761     }
762   }
763
764   /**
765    * Generate newick format tree according to user specified flags
766    * 
767    * @param withbootstraps
768    *          explicitly write bootstrap values
769    * @param withdists
770    *          explicitly write distances
771    * @param printRootInfo
772    *          explicitly write root distance
773    * 
774    * @return new hampshire tree in a single line
775    */
776   public String print(boolean withbootstraps, boolean withdists,
777           boolean printRootInfo)
778   {
779     synchronized (this)
780     {
781       boolean rootinfo = printRootInfo;
782       this.printRootInfo = printRootInfo;
783
784       String rv = print(withbootstraps, withdists);
785       this.printRootInfo = rootinfo;
786
787       return rv;
788     }
789   }
790
791   /**
792    * DOCUMENT ME!
793    * 
794    * @return DOCUMENT ME!
795    */
796   char getQuoteChar()
797   {
798     return QuoteChar;
799   }
800
801   /**
802    * DOCUMENT ME!
803    * 
804    * @param c
805    *          DOCUMENT ME!
806    * 
807    * @return DOCUMENT ME!
808    */
809   char setQuoteChar(char c)
810   {
811     char old = QuoteChar;
812     QuoteChar = c;
813
814     return old;
815   }
816
817   /**
818    * DOCUMENT ME!
819    * 
820    * @param name
821    *          DOCUMENT ME!
822    * 
823    * @return DOCUMENT ME!
824    */
825   private String nodeName(String name)
826   {
827     if (NodeSafeName[0].search(name))
828     {
829       return QuoteChar + NodeSafeName[1].replaceAll(name) + QuoteChar;
830     }
831     else
832     {
833       return NodeSafeName[2].replaceAll(name);
834     }
835   }
836
837   /**
838    * DOCUMENT ME!
839    * 
840    * @param c
841    *          DOCUMENT ME!
842    * 
843    * @return DOCUMENT ME!
844    */
845   private String printNodeField(SequenceNode c)
846   {
847     return ((c.getName() == null) ? "" : nodeName(c.getName()))
848             + ((HasBootstrap) ? ((c.getBootstrap() > -1) ? ((c.getName() != null ? " "
849                     : "") + c.getBootstrap())
850                     : "")
851                     : "") + ((HasDistances) ? (":" + c.dist) : "");
852   }
853
854   /**
855    * DOCUMENT ME!
856    * 
857    * @param root
858    *          DOCUMENT ME!
859    * 
860    * @return DOCUMENT ME!
861    */
862   private String printRootField(SequenceNode root)
863   {
864     return (printRootInfo) ? (((root.getName() == null) ? ""
865             : nodeName(root.getName()))
866             + ((HasBootstrap) ? ((root.getBootstrap() > -1) ? ((root
867                     .getName() != null ? " " : "") + +root.getBootstrap())
868                     : "") : "") + ((RootHasDistance) ? (":" + root.dist)
869             : "")) : "";
870   }
871
872   // Non recursive call deals with root node properties
873   public void print(StringBuffer tf, SequenceNode root)
874   {
875     if (root != null)
876     {
877       if (root.isLeaf() && printRootInfo)
878       {
879         tf.append(printRootField(root));
880       }
881       else
882       {
883         if (root.isDummy())
884         {
885           _print(tf, (SequenceNode) root.right());
886           _print(tf, (SequenceNode) root.left());
887         }
888         else
889         {
890           tf.append("(");
891           _print(tf, (SequenceNode) root.right());
892
893           if (root.left() != null)
894           {
895             tf.append(",");
896           }
897
898           _print(tf, (SequenceNode) root.left());
899           tf.append(")" + printRootField(root));
900         }
901       }
902     }
903   }
904
905   // Recursive call for non-root nodes
906   public void _print(StringBuffer tf, SequenceNode c)
907   {
908     if (c != null)
909     {
910       if (c.isLeaf())
911       {
912         tf.append(printNodeField(c));
913       }
914       else
915       {
916         if (c.isDummy())
917         {
918           _print(tf, (SequenceNode) c.left());
919           if (c.left() != null)
920           {
921             tf.append(",");
922           }
923           _print(tf, (SequenceNode) c.right());
924         }
925         else
926         {
927           tf.append("(");
928           _print(tf, (SequenceNode) c.right());
929
930           if (c.left() != null)
931           {
932             tf.append(",");
933           }
934
935           _print(tf, (SequenceNode) c.left());
936           tf.append(")" + printNodeField(c));
937         }
938       }
939     }
940   }
941
942   // Test
943   public static void main(String[] args)
944   {
945     try
946     {
947       if (args == null || args.length != 1)
948       {
949         System.err
950                 .println("Takes one argument - file name of a newick tree file.");
951         System.exit(0);
952       }
953
954       File fn = new File(args[0]);
955
956       StringBuffer newickfile = new StringBuffer();
957       BufferedReader treefile = new BufferedReader(new FileReader(fn));
958       String l;
959
960       while ((l = treefile.readLine()) != null)
961       {
962         newickfile.append(l);
963       }
964
965       treefile.close();
966       System.out.println("Read file :\n");
967
968       NewickFile trf = new NewickFile(args[0], "File");
969       trf.parse();
970       System.out.println("Original file :\n");
971
972       Regex nonl = new Regex("\n+", "");
973       System.out.println(nonl.replaceAll(newickfile.toString()) + "\n");
974
975       System.out.println("Parsed file.\n");
976       System.out.println("Default output type for original input.\n");
977       System.out.println(trf.print());
978       System.out.println("Without bootstraps.\n");
979       System.out.println(trf.print(false));
980       System.out.println("Without distances.\n");
981       System.out.println(trf.print(true, false));
982       System.out.println("Without bootstraps but with distanecs.\n");
983       System.out.println(trf.print(false, true));
984       System.out.println("Without bootstraps or distanecs.\n");
985       System.out.println(trf.print(false, false));
986       System.out.println("With bootstraps and with distances.\n");
987       System.out.println(trf.print(true, true));
988     } catch (java.io.IOException e)
989     {
990       System.err.println("Exception\n" + e);
991       e.printStackTrace();
992     }
993   }
994 }