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