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