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