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