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