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