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