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