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