Merge branch 'improvement/JAL-4023_snotation_for_trees' into develop
[jalview.git] / src / jalview / io / NewickFile.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 // NewickFile.java
22 // Tree I/O
23 // http://evolution.genetics.washington.edu/phylip/newick_doc.html
24 // TODO: Implement Basic NHX tag parsing and preservation
25 // TODO: http://evolution.genetics.wustl.edu/eddy/forester/NHX.html
26 // TODO: Extended SequenceNodeI to hold parsed NHX strings
27 package jalview.io;
28
29 import java.util.Locale;
30
31 import jalview.datamodel.SequenceNode;
32 import jalview.util.MessageManager;
33
34 import java.io.BufferedReader;
35 import java.io.File;
36 import java.io.FileReader;
37 import java.io.IOException;
38 import java.util.StringTokenizer;
39
40 import com.stevesoft.pat.Regex;
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))).floatValue();
491             HasDistances = true;
492             nodehasdistance = true;
493           } catch (Exception e)
494           {
495             Error = ErrorStringrange(Error,
496                     "Can't parse node distance value", 7,
497                     ncp + ndist.matchedFrom(), nf);
498           }
499         }
500
501         if (ascending)
502         {
503           // Write node info here
504           c.setName(nodename);
505           // Trees without distances still need a render distance
506           c.dist = (HasDistances) ? distance : DefDistance;
507           // be consistent for internal bootstrap defaults too
508           c.setBootstrap((HasBootstrap) ? bootstrap : DefBootstrap);
509           if (c == realroot)
510           {
511             RootHasDistance = nodehasdistance; // JBPNote This is really
512             // UGLY!!! Ensure root node gets
513             // its given distance
514           }
515           parseNHXNodeProps(c, commentString2);
516           commentString2 = null;
517         }
518         else
519         {
520           // Find a place to put the leaf
521           SequenceNode newnode = new SequenceNode(null, c, nodename,
522                   (HasDistances) ? distance : DefDistance,
523                   (HasBootstrap) ? bootstrap : DefBootstrap, false);
524           parseNHXNodeProps(c, commentString2);
525           commentString2 = null;
526
527           if (c.right() == null)
528           {
529             c.setRight(newnode);
530           }
531           else
532           {
533             if (c.left() == null)
534             {
535               c.setLeft(newnode);
536             }
537             else
538             {
539               // Insert a dummy node for polytomy
540               // dummy nodes have distances
541               SequenceNode newdummy = new SequenceNode(null, c, null,
542                       (HasDistances ? 0 : DefDistance), 0, true);
543               newdummy.SetChildren(c.left(), newnode);
544               c.setLeft(newdummy);
545             }
546           }
547         }
548
549         if (ascending)
550         {
551           // move back up the tree from preceding closure
552           c = c.AscendTree();
553
554           if ((d > -1) && (c == null))
555           {
556             Error = ErrorStringrange(Error,
557                     "File broke algorithm: Lost place in tree (is there an extra ')' ?)",
558                     7, fcp, nf);
559           }
560         }
561
562         if (nf.charAt(fcp) == ')')
563         {
564           d--;
565           ascending = true;
566         }
567         else
568         {
569           if (nf.charAt(fcp) == ',')
570           {
571             if (ascending)
572             {
573               ascending = false;
574             }
575             else
576             {
577               // Just advance focus, if we need to
578               if ((c.left() != null) && (!c.left().isLeaf()))
579               {
580                 c = (SequenceNode) c.left();
581               }
582             }
583           }
584         }
585
586         // Reset new node properties to obvious fakes
587         nodename = null;
588         distance = DefDistance;
589         bootstrap = DefBootstrap;
590         commentString2 = null;
591         parsednodename = false;
592       }
593       if (nextcp == 0)
594       {
595         ncp = cp = fcp + 1;
596       }
597       else
598       {
599         cp = nextcp;
600         nextcp = 0;
601       }
602     }
603
604     if (Error != null)
605     {
606       throw (new IOException(
607               MessageManager.formatMessage("exception.newfile", new String[]
608               { Error.toString() })));
609     }
610     if (root == null)
611     {
612       throw (new IOException(
613               MessageManager.formatMessage("exception.newfile", new String[]
614               { MessageManager.getString("label.no_tree_read_in") })));
615     }
616     // THe next line is failing for topali trees - not sure why yet. if
617     // (root.right()!=null && root.isDummy())
618     root = (SequenceNode) root.right().detach(); // remove the imaginary root.
619
620     if (!RootHasDistance)
621     {
622       root.dist = (HasDistances) ? 0 : DefDistance;
623     }
624   }
625
626   /**
627    * parse NHX codes in comment strings and update NewickFile state flags for
628    * distances and bootstraps, and add any additional properties onto the node.
629    * 
630    * @param c
631    * @param commentString
632    * @param commentString2
633    */
634   private void parseNHXNodeProps(SequenceNode c, String commentString)
635   {
636     // TODO: store raw comment on the sequenceNode so it can be recovered when
637     // tree is output
638     if (commentString != null && commentString.startsWith("&&NHX"))
639     {
640       StringTokenizer st = new StringTokenizer(commentString.substring(5),
641               ":");
642       while (st.hasMoreTokens())
643       {
644         String tok = st.nextToken();
645         int colpos = tok.indexOf("=");
646
647         if (colpos > -1)
648         {
649           String code = tok.substring(0, colpos);
650           String value = tok.substring(colpos + 1);
651           try
652           {
653             // parse out code/value pairs
654             if (code.toLowerCase(Locale.ROOT).equals("b"))
655             {
656               int v = -1;
657               Float iv = Float.valueOf(value);
658               v = iv.intValue(); // jalview only does integer bootstraps
659               // currently
660               c.setBootstrap(v);
661               HasBootstrap = true;
662             }
663             // more codes here.
664           } catch (Exception e)
665           {
666             System.err.println(
667                     "Couldn't parse code '" + code + "' = '" + value + "'");
668             e.printStackTrace(System.err);
669           }
670         }
671       }
672     }
673
674   }
675
676   /**
677    * DOCUMENT ME!
678    * 
679    * @return DOCUMENT ME!
680    */
681   public SequenceNode getTree()
682   {
683     return root;
684   }
685
686   /**
687    * Generate a newick format tree according to internal flags for bootstraps,
688    * distances and root distances.
689    * 
690    * @return new hampshire tree in a single line
691    */
692   public String print()
693   {
694     synchronized (this)
695     {
696       StringBuffer tf = new StringBuffer();
697       print(tf, root);
698
699       return (tf.append(";").toString());
700     }
701   }
702
703   /**
704    * 
705    * 
706    * Generate a newick format tree according to internal flags for distances and
707    * root distances and user specificied writing of bootstraps.
708    * 
709    * @param withbootstraps
710    *          controls if bootstrap values are explicitly written.
711    * 
712    * @return new hampshire tree in a single line
713    */
714   public String print(boolean withbootstraps)
715   {
716     synchronized (this)
717     {
718       boolean boots = this.HasBootstrap;
719       this.HasBootstrap = withbootstraps;
720
721       String rv = print();
722       this.HasBootstrap = boots;
723
724       return rv;
725     }
726   }
727
728   /**
729    * 
730    * Generate newick format tree according to internal flags for writing root
731    * node distances.
732    * 
733    * @param withbootstraps
734    *          explicitly write bootstrap values
735    * @param withdists
736    *          explicitly write distances
737    * 
738    * @return new hampshire tree in a single line
739    */
740   public String print(boolean withbootstraps, boolean withdists)
741   {
742     synchronized (this)
743     {
744       boolean dists = this.HasDistances;
745       this.HasDistances = withdists;
746
747       String rv = print(withbootstraps);
748       this.HasDistances = dists;
749
750       return rv;
751     }
752   }
753
754   /**
755    * Generate newick format tree according to user specified flags
756    * 
757    * @param withbootstraps
758    *          explicitly write bootstrap values
759    * @param withdists
760    *          explicitly write distances
761    * @param printRootInfo
762    *          explicitly write root distance
763    * 
764    * @return new hampshire tree in a single line
765    */
766   public String print(boolean withbootstraps, boolean withdists,
767           boolean printRootInfo)
768   {
769     synchronized (this)
770     {
771       boolean rootinfo = printRootInfo;
772       this.printRootInfo = printRootInfo;
773
774       String rv = print(withbootstraps, withdists);
775       this.printRootInfo = rootinfo;
776
777       return rv;
778     }
779   }
780
781   /**
782    * DOCUMENT ME!
783    * 
784    * @return DOCUMENT ME!
785    */
786   char getQuoteChar()
787   {
788     return QuoteChar;
789   }
790
791   /**
792    * DOCUMENT ME!
793    * 
794    * @param c
795    *          DOCUMENT ME!
796    * 
797    * @return DOCUMENT ME!
798    */
799   char setQuoteChar(char c)
800   {
801     char old = QuoteChar;
802     QuoteChar = c;
803
804     return old;
805   }
806
807   /**
808    * DOCUMENT ME!
809    * 
810    * @param name
811    *          DOCUMENT ME!
812    * 
813    * @return DOCUMENT ME!
814    */
815   private String nodeName(String name)
816   {
817     if (NodeSafeName[0].search(name))
818     {
819       return QuoteChar + NodeSafeName[1].replaceAll(name) + QuoteChar;
820     }
821     else
822     {
823       return NodeSafeName[2].replaceAll(name);
824     }
825   }
826
827   /**
828    * DOCUMENT ME!
829    * 
830    * @param c
831    *          DOCUMENT ME!
832    * 
833    * @return DOCUMENT ME!
834    */
835   private String printNodeField(SequenceNode c)
836   {
837     return ((c.getName() == null) ? "" : nodeName(c.getName()))
838             + ((HasBootstrap) ? ((c.getBootstrap() > -1)
839                     ? ((c.getName() != null ? " " : "") + c.getBootstrap())
840                     : "") : "")
841             + ((HasDistances) ? (":" + c.dist) : "");
842   }
843
844   /**
845    * DOCUMENT ME!
846    * 
847    * @param root
848    *          DOCUMENT ME!
849    * 
850    * @return DOCUMENT ME!
851    */
852   private String printRootField(SequenceNode root)
853   {
854     return (printRootInfo)
855             ? (((root.getName() == null) ? "" : nodeName(root.getName()))
856                     + ((HasBootstrap)
857                             ? ((root.getBootstrap() > -1)
858                                     ? ((root.getName() != null ? " " : "")
859                                             + +root.getBootstrap())
860                                     : "")
861                             : "")
862                     + ((RootHasDistance) ? (":" + root.dist) : ""))
863             : "";
864   }
865
866   // Non recursive call deals with root node properties
867   public void print(StringBuffer tf, SequenceNode root)
868   {
869     if (root != null)
870     {
871       if (root.isLeaf() && printRootInfo)
872       {
873         tf.append(printRootField(root));
874       }
875       else
876       {
877         if (root.isDummy())
878         {
879           _print(tf, (SequenceNode) root.right());
880           _print(tf, (SequenceNode) root.left());
881         }
882         else
883         {
884           tf.append("(");
885           _print(tf, (SequenceNode) root.right());
886
887           if (root.left() != null)
888           {
889             tf.append(",");
890           }
891
892           _print(tf, (SequenceNode) root.left());
893           tf.append(")" + printRootField(root));
894         }
895       }
896     }
897   }
898
899   // Recursive call for non-root nodes
900   public void _print(StringBuffer tf, SequenceNode c)
901   {
902     if (c != null)
903     {
904       if (c.isLeaf())
905       {
906         tf.append(printNodeField(c));
907       }
908       else
909       {
910         if (c.isDummy())
911         {
912           _print(tf, (SequenceNode) c.left());
913           if (c.left() != null)
914           {
915             tf.append(",");
916           }
917           _print(tf, (SequenceNode) c.right());
918         }
919         else
920         {
921           tf.append("(");
922           _print(tf, (SequenceNode) c.right());
923
924           if (c.left() != null)
925           {
926             tf.append(",");
927           }
928
929           _print(tf, (SequenceNode) c.left());
930           tf.append(")" + printNodeField(c));
931         }
932       }
933     }
934   }
935
936   /**
937    * 
938    * @param args
939    * @j2sIgnore
940    */
941   public static void main(String[] args)
942   {
943     try
944     {
945       if (args == null || args.length != 1)
946       {
947         System.err.println(
948                 "Takes one argument - file name of a newick tree file.");
949         System.exit(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 }