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