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