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