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