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