Merge branch 'develop' into features/r2_11_2_alphafold/JAL-629
[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.datamodel.SequenceNode;
40 import jalview.util.MessageManager;
41
42 /**
43  * Parse a new hanpshire style tree Caveats: NHX files are NOT supported and the
44  * tree distances and topology are unreliable when they are parsed. TODO: on
45  * this: NHX codes are appended in comments beginning with &&NHX. The codes are
46  * given below (from http://www.phylosoft.org/forester/NHX.html): Element Type
47  * Description Corresponding phyloXML element (parent element in parentheses) no
48  * tag string name of this node/clade (MUST BE FIRST, IF ASSIGNED)
49  * <name>(<clade>) : decimal branch length to parent node (MUST BE SECOND, IF
50  * ASSIGNED) <branch_length>(<clade>) :GN= string gene name <name>(<sequence>)
51  * :AC= string sequence accession <accession>(<sequence>) :ND= string node
52  * identifier - if this is being used, it has to be unique within each phylogeny
53  * <node_id>(<clade>) :B= decimal confidence value for parent branch
54  * <confidence>(<clade>) :D= 'T', 'F', or '?' 'T' if this node represents a
55  * duplication event - 'F' if this node represents a speciation event, '?' if
56  * this node represents an unknown event (D= tag should be replaced by Ev= tag)
57  * n/a :Ev=duplications>speciations>gene losses>event type>duplication type int
58  * int int string string event (replaces the =D tag), number of duplication,
59  * speciation, and gene loss events, type of event (transfer, fusion, root,
60  * unknown, other, speciation_duplication_loss, unassigned) <events>(<clade>)
61  * :E= string EC number at this node <annotation>(<sequence>) :Fu= string
62  * function at this node <annotation>(<sequence>)
63  * :DS=protein-length>from>to>support>name>from>... int int int double string
64  * int ... domain structure at this node <domain_architecture>(<sequence>) :S=
65  * string species name of the species/phylum at this node <taxonomy>(<clade>)
66  * :T= integer taxonomy ID of the species/phylum at this node <id>(<taxonomy>)
67  * :W= integer width of parent branch <width>(<clade>) :C=rrr.ggg.bbb
68  * integer.integer.integer color of parent branch <color>(<clade>) :Co= 'Y' or
69  * 'N' collapse this node when drawing the tree (default is not to collapse) n/a
70  * :XB= string custom data associated with a branch <property>(<clade>) :XN=
71  * string custom data associated with a node <property>(<clade>) :O= integer
72  * orthologous to this external node n/a :SN= integer subtree neighbors n/a :SO=
73  * integer super orthologous (no duplications on paths) to this external node
74  * n/a
75  * 
76  * @author Jim Procter
77  * @version $Revision$
78  */
79 public class NewickFile extends FileParse
80 {
81   SequenceNode root;
82
83   private boolean HasBootstrap = false;
84
85   private boolean HasDistances = false;
86
87   private boolean RootHasDistance = false;
88
89   // File IO Flags
90   boolean ReplaceUnderscores = false;
91
92   boolean printRootInfo = true;
93
94   private Regex[] NodeSafeName = new Regex[] {
95       new Regex().perlCode("m/[\\[,:'()]/"), // test for
96       // requiring
97       // quotes
98       new Regex().perlCode("s/'/''/"), // escaping quote
99       // characters
100       new Regex().perlCode("s/\\/w/_/") // unqoted whitespace
101       // transformation
102   };
103
104   char QuoteChar = '\'';
105
106   /**
107    * Creates a new NewickFile object.
108    * 
109    * @param inStr
110    *          DOCUMENT ME!
111    * 
112    * @throws IOException
113    *           DOCUMENT ME!
114    */
115   public NewickFile(String inStr) throws IOException
116   {
117     super(inStr, DataSourceType.PASTE);
118   }
119
120   /**
121    * Creates a new NewickFile object.
122    * 
123    * @param inFile
124    *          DOCUMENT ME!
125    * @param protocol
126    *          DOCUMENT ME!
127    * 
128    * @throws IOException
129    *           DOCUMENT ME!
130    */
131   public NewickFile(String inFile, DataSourceType protocol)
132           throws IOException
133   {
134     super(inFile, protocol);
135   }
136
137   public NewickFile(FileParse source) throws IOException
138   {
139     super(source);
140   }
141
142   /**
143    * Creates a new NewickFile object.
144    * 
145    * @param newtree
146    *          DOCUMENT ME!
147    */
148   public NewickFile(SequenceNode newtree)
149   {
150     root = newtree;
151   }
152
153   /**
154    * Creates a new NewickFile object.
155    * 
156    * @param newtree
157    *          DOCUMENT ME!
158    * @param bootstrap
159    *          DOCUMENT ME!
160    */
161   public NewickFile(SequenceNode newtree, boolean bootstrap)
162   {
163     HasBootstrap = bootstrap;
164     root = newtree;
165   }
166
167   /**
168    * Creates a new NewickFile object.
169    * 
170    * @param newtree
171    *          DOCUMENT ME!
172    * @param bootstrap
173    *          DOCUMENT ME!
174    * @param distances
175    *          DOCUMENT ME!
176    */
177   public NewickFile(SequenceNode newtree, boolean bootstrap,
178           boolean distances)
179   {
180     root = newtree;
181     HasBootstrap = bootstrap;
182     HasDistances = distances;
183   }
184
185   /**
186    * Creates a new NewickFile object.
187    * 
188    * @param newtree
189    *          DOCUMENT ME!
190    * @param bootstrap
191    *          DOCUMENT ME!
192    * @param distances
193    *          DOCUMENT ME!
194    * @param rootdistance
195    *          DOCUMENT ME!
196    */
197   public NewickFile(SequenceNode newtree, boolean bootstrap,
198           boolean distances, boolean rootdistance)
199   {
200     root = newtree;
201     HasBootstrap = bootstrap;
202     HasDistances = distances;
203     RootHasDistance = rootdistance;
204   }
205
206   /**
207    * DOCUMENT ME!
208    * 
209    * @param Error
210    *          DOCUMENT ME!
211    * @param Er
212    *          DOCUMENT ME!
213    * @param r
214    *          DOCUMENT ME!
215    * @param p
216    *          DOCUMENT ME!
217    * @param s
218    *          DOCUMENT ME!
219    * 
220    * @return DOCUMENT ME!
221    */
222   private String ErrorStringrange(String Error, String Er, int r, int p,
223           String s)
224   {
225     return ((Error == null) ? "" : Error) + Er + " at position " + p + " ( "
226             + s.substring(((p - r) < 0) ? 0 : (p - r),
227                     ((p + r) > s.length()) ? s.length() : (p + r))
228             + " )\n";
229   }
230
231   // @tree annotations
232   // These are set automatically by the reader
233   public boolean HasBootstrap()
234   {
235     return HasBootstrap;
236   }
237
238   /**
239    * DOCUMENT ME!
240    * 
241    * @return DOCUMENT ME!
242    */
243   public boolean HasDistances()
244   {
245     return HasDistances;
246   }
247
248   public boolean HasRootDistance()
249   {
250     return RootHasDistance;
251   }
252
253   /**
254    * parse the filesource as a newick file (new hampshire and/or extended)
255    * 
256    * @throws IOException
257    *           with a line number and character position for badly formatted NH
258    *           strings
259    */
260   public void parse() throws IOException
261   {
262     String nf;
263
264     { // fill nf with complete tree file
265
266       StringBuffer file = new StringBuffer();
267
268       while ((nf = nextLine()) != null)
269       {
270         file.append(nf);
271       }
272
273       nf = file.toString();
274     }
275
276     root = new SequenceNode();
277
278     SequenceNode realroot = null;
279     SequenceNode c = root;
280
281     int d = -1;
282     int cp = 0;
283     // int flen = nf.length();
284
285     String Error = null;
286     String nodename = null;
287     String commentString2 = null; // comments after simple node props
288
289     double DefDistance = (float) 0.001; // @param Default distance for a node -
290     // very very small
291     int DefBootstrap = -1; // @param Default bootstrap for a node
292
293     double distance = DefDistance;
294     int bootstrap = DefBootstrap;
295
296     boolean ascending = false; // flag indicating that we are leaving the
297     // current node
298
299     Regex majorsyms = new Regex("[(\\['),;]");
300
301     int nextcp = 0;
302     int ncp = cp;
303     boolean parsednodename = false;
304     while (majorsyms.searchFrom(nf, cp) && (Error == null))
305     {
306       int fcp = majorsyms.matchedFrom();
307       char schar;
308       switch (schar = nf.charAt(fcp))
309       {
310       case '(':
311
312         // ascending should not be set
313         // New Internal node
314         if (ascending)
315         {
316           Error = ErrorStringrange(Error, "Unexpected '('", 7, fcp, nf);
317
318           continue;
319         }
320         d++;
321
322         if (c.right() == null)
323         {
324           c.setRight(new SequenceNode(null, c, null, DefDistance,
325                   DefBootstrap, false));
326           c = (SequenceNode) c.right();
327         }
328         else
329         {
330           if (c.left() != null)
331           {
332             // Dummy node for polytomy - keeps c.left free for new node
333             SequenceNode tmpn = new SequenceNode(null, c, null, 0, 0, true);
334             tmpn.SetChildren(c.left(), c.right());
335             c.setRight(tmpn);
336           }
337
338           c.setLeft(new SequenceNode(null, c, null, DefDistance,
339                   DefBootstrap, false));
340           c = (SequenceNode) c.left();
341         }
342
343         if (realroot == null)
344         {
345           realroot = c;
346         }
347
348         nodename = null;
349         distance = DefDistance;
350         bootstrap = DefBootstrap;
351         cp = fcp + 1;
352
353         break;
354
355       // Deal with quoted fields
356       case '\'':
357
358         Regex qnodename = new Regex("'([^']|'')+'");
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.perlCode("s/''/'/");
367           String widernodename = xpandquotes.replaceAll(nodename);
368           nodename = widernodename;
369           // jump to after end of quoted nodename
370           nextcp = fcp + nl + 1;
371           parsednodename = true;
372         }
373         else
374         {
375           Error = ErrorStringrange(Error,
376                   "Unterminated quotes for nodename", 7, fcp, nf);
377         }
378
379         break;
380
381       default:
382         if (schar == ';')
383         {
384           if (d != -1)
385           {
386             Error = ErrorStringrange(Error,
387                     "Wayward semicolon (depth=" + d + ")", 7, fcp, nf);
388           }
389           // cp advanced at the end of default
390         }
391         if (schar == '[')
392         {
393           // node string contains Comment or structured/extended NH format info
394           /*
395            * if ((fcp-cp>1 && nf.substring(cp,fcp).trim().length()>1)) { // will
396            * process in remains System.err.println("skipped text:
397            * '"+nf.substring(cp,fcp)+"'"); }
398            */
399           // verify termination.
400           Regex comment = new Regex("]");
401           if (comment.searchFrom(nf, fcp))
402           {
403             // Skip the comment field
404             nextcp = comment.matchedFrom() + 1;
405             warningMessage = "Tree file contained comments which may confuse input algorithm.";
406             break;
407
408             // cp advanced at the end of default to nextcp, ncp is unchanged so
409             // any node info can be read.
410           }
411           else
412           {
413             Error = ErrorStringrange(Error, "Unterminated comment", 3, fcp,
414                     nf);
415           }
416         }
417         // Parse simpler field strings
418         String fstring = nf.substring(ncp, fcp);
419         // remove any comments before we parse the node info
420         // TODO: test newick file with quoted square brackets in node name (is
421         // this allowed?)
422         while (fstring.indexOf(']') > -1)
423         {
424           int cstart = fstring.indexOf('[');
425           int cend = fstring.indexOf(']');
426           commentString2 = fstring.substring(cstart + 1, cend);
427           fstring = fstring.substring(0, cstart)
428                   + fstring.substring(cend + 1);
429
430         }
431         Regex uqnodename = new Regex("\\b([^' :;\\](),]+)");
432         Regex nbootstrap = new Regex("\\s*([0-9+]+)\\s*:");
433         Regex ndist = new Regex(":([-0-9Ee.+]+)");
434
435         if (!parsednodename && uqnodename.search(fstring)
436                 && ((uqnodename.matchedFrom(1) == 0) || (fstring
437                         .charAt(uqnodename.matchedFrom(1) - 1) != ':'))) // JBPNote
438         // HACK!
439         {
440           if (nodename == null)
441           {
442             if (ReplaceUnderscores)
443             {
444               nodename = uqnodename.stringMatched(1).replace('_', ' ');
445             }
446             else
447             {
448               nodename = uqnodename.stringMatched(1);
449             }
450           }
451           else
452           {
453             Error = ErrorStringrange(Error,
454                     "File has broken algorithm - overwritten nodename", 10,
455                     fcp, nf);
456           }
457         }
458         // get comment bootstraps
459
460         if (nbootstrap.search(fstring))
461         {
462           if (nbootstrap.stringMatched(1)
463                   .equals(uqnodename.stringMatched(1)))
464           {
465             nodename = null; // no nodename here.
466           }
467           if (nodename == null || nodename.length() == 0
468                   || nbootstrap.matchedFrom(1) > (uqnodename.matchedFrom(1)
469                           + uqnodename.stringMatched().length()))
470           {
471             try
472             {
473               bootstrap = (Integer.valueOf(nbootstrap.stringMatched(1)))
474                       .intValue();
475               HasBootstrap = true;
476             } catch (Exception e)
477             {
478               Error = ErrorStringrange(Error, "Can't parse bootstrap value",
479                       4, ncp + nbootstrap.matchedFrom(), nf);
480             }
481           }
482         }
483
484         boolean nodehasdistance = false;
485
486         if (ndist.search(fstring))
487         {
488           try
489           {
490             distance = (Double.valueOf(ndist.stringMatched(1)))
491                     .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           SequenceNode 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               SequenceNode 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 = (SequenceNode) 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(SequenceNode 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(SequenceNode 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(SequenceNode 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         Jalview.exit(
949                 "Takes one argument - file name of a newick tree file.", 0);
950       }
951
952       File fn = new File(args[0]);
953
954       StringBuffer newickfile = new StringBuffer();
955       BufferedReader treefile = new BufferedReader(new FileReader(fn));
956       String l;
957
958       while ((l = treefile.readLine()) != null)
959       {
960         newickfile.append(l);
961       }
962
963       treefile.close();
964       System.out.println("Read file :\n");
965
966       NewickFile trf = new NewickFile(args[0], DataSourceType.FILE);
967       trf.parse();
968       System.out.println("Original file :\n");
969
970       Regex nonl = new Regex("\n+", "");
971       System.out.println(nonl.replaceAll(newickfile.toString()) + "\n");
972
973       System.out.println("Parsed file.\n");
974       System.out.println("Default output type for original input.\n");
975       System.out.println(trf.print());
976       System.out.println("Without bootstraps.\n");
977       System.out.println(trf.print(false));
978       System.out.println("Without distances.\n");
979       System.out.println(trf.print(true, false));
980       System.out.println("Without bootstraps but with distanecs.\n");
981       System.out.println(trf.print(false, true));
982       System.out.println("Without bootstraps or distanecs.\n");
983       System.out.println(trf.print(false, false));
984       System.out.println("With bootstraps and with distances.\n");
985       System.out.println(trf.print(true, true));
986     } catch (java.io.IOException e)
987     {
988       System.err.println("Exception\n" + e);
989       e.printStackTrace();
990     }
991   }
992 }