iterating nh parser
[jalview.git] / forester / java / src / org / forester / io / parsers / nhx / NHXParser2.java
1 // $Id:
2 // FORESTER -- software libraries and applications
3 // for evolutionary biology research and applications.
4 //
5 // Copyright (C) 2008-2009 Christian M. Zmasek
6 // Copyright (C) 2008-2009 Burnham Institute for Medical Research
7 // All rights reserved
8 //
9 // This library is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU Lesser General Public
11 // License as published by the Free Software Foundation; either
12 // version 2.1 of the License, or (at your option) any later version.
13 //
14 // This library is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 // Lesser General Public License for more details.
18 //
19 // You should have received a copy of the GNU Lesser General Public
20 // License along with this library; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 //
23 // Contact: phylosoft @ gmail . com
24 // WWW: https://sites.google.com/site/cmzmasek/home/software/forester
25
26 package org.forester.io.parsers.nhx;
27
28 import java.awt.Color;
29 import java.io.BufferedReader;
30 import java.io.File;
31 import java.io.FileReader;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.InputStreamReader;
35 import java.util.StringTokenizer;
36 import java.util.regex.Matcher;
37 import java.util.regex.Pattern;
38
39 import org.forester.io.parsers.PhylogenyParser;
40 import org.forester.io.parsers.nhx.NHXParser.TAXONOMY_EXTRACTION;
41 import org.forester.io.parsers.phyloxml.PhyloXmlDataFormatException;
42 import org.forester.io.parsers.util.ParserUtils;
43 import org.forester.io.parsers.util.PhylogenyParserException;
44 import org.forester.phylogeny.Phylogeny;
45 import org.forester.phylogeny.PhylogenyMethods;
46 import org.forester.phylogeny.PhylogenyNode;
47 import org.forester.phylogeny.data.Accession;
48 import org.forester.phylogeny.data.Annotation;
49 import org.forester.phylogeny.data.Confidence;
50 import org.forester.phylogeny.data.DomainArchitecture;
51 import org.forester.phylogeny.data.Event;
52 import org.forester.phylogeny.data.Identifier;
53 import org.forester.phylogeny.data.PhylogenyDataUtil;
54 import org.forester.phylogeny.data.PropertiesMap;
55 import org.forester.phylogeny.data.Property;
56 import org.forester.phylogeny.data.Sequence;
57 import org.forester.phylogeny.data.Taxonomy;
58 import org.forester.phylogeny.iterators.PhylogenyNodeIterator;
59 import org.forester.util.ForesterUtil;
60
61 public final class NHXParser2 implements PhylogenyParser {
62
63     public static final TAXONOMY_EXTRACTION TAXONOMY_EXTRACTION_DEFAULT = TAXONOMY_EXTRACTION.NO;
64     final static private boolean            GUESS_ROOTEDNESS_DEFAULT    = true;
65     final static private boolean            GUESS_IF_SUPPORT_VALUES     = true;
66     final static private boolean            IGNORE_QUOTES_DEFAULT       = false;
67     final static public boolean             REPLACE_UNDERSCORES_DEFAULT = false;
68     private boolean                         _saw_closing_paren;
69     final static private byte               STRING                      = 0;
70     final static private byte               STRING_BUFFER               = 1;
71     final static private byte               CHAR_ARRAY                  = 2;
72     final static private byte               BUFFERED_READER             = 3;
73     final static private byte               STRING_BUILDER              = 4;
74     private boolean                         _guess_rootedness;
75     private boolean                         _has_next;
76     private boolean                         _ignore_quotes;
77     private byte                            _input_type;
78     private int                             _source_length;
79     private PhylogenyNode                   _current_node;
80     private StringBuilder                   _current_anotation;
81     private Object                          _nhx_source;
82     private int                             _clade_level;
83     private Phylogeny                       _current_phylogeny;
84     private TAXONOMY_EXTRACTION             _taxonomy_extraction;
85     private boolean                         _replace_underscores;
86     public final static Pattern             UC_LETTERS_NUMBERS_PATTERN  = Pattern.compile( "^[A-Z0-9]+$" );
87     public final static Pattern             NUMBERS_ONLY_PATTERN        = Pattern.compile( "^[0-9\\.]+$" );
88     public final static Pattern             MB_PROB_PATTERN             = Pattern.compile( "prob=([^,]+)" );
89     public final static Pattern             MB_PROB_SD_PATTERN          = Pattern.compile( "prob_stddev=([^,]+)" );
90     public final static Pattern             MB_BL_PATTERN               = Pattern.compile( "length_median=([^,]+)" );
91     boolean                                 _in_comment                 = false;
92     boolean                                 _saw_colon                  = false;
93     boolean                                 _saw_open_bracket           = false;
94     boolean                                 _in_open_bracket            = false;
95     boolean                                 _in_double_quote            = false;
96     boolean                                 _in_single_quote            = false;
97     String                                  _my_source_str              = null;
98     StringBuffer                            _my_source_sbuff            = null;
99     StringBuilder                           _my_source_sbuil            = null;
100     char[]                                  _my_source_charary          = null;
101     BufferedReader                          _my_source_br               = null;
102     int                                     _i;
103
104     public NHXParser2() {
105         init();
106     }
107
108     public Phylogeny getNext() throws IOException, NHXFormatException {
109         while ( true ) {
110             char c = '\b';
111             if ( getInputType() == BUFFERED_READER ) {
112                 final int ci = _my_source_br.read();
113                 if ( ci >= 0 ) {
114                     c = ( char ) ci;
115                 }
116                 else {
117                     break;
118                 }
119             }
120             else {
121                 if ( _i >= getSourceLength() ) {
122                     break;
123                 }
124                 else {
125                     switch ( getInputType() ) {
126                         case STRING:
127                             c = _my_source_str.charAt( _i );
128                             break;
129                         case STRING_BUFFER:
130                             c = _my_source_sbuff.charAt( _i );
131                             break;
132                         case STRING_BUILDER:
133                             c = _my_source_sbuil.charAt( _i );
134                             break;
135                         case CHAR_ARRAY:
136                             c = _my_source_charary[ _i ];
137                             break;
138                     }
139                 }
140             }
141             if ( !_in_single_quote && !_in_double_quote ) {
142                 if ( c == ':' ) {
143                     _saw_colon = true;
144                 }
145                 else if ( !( ( c < 33 ) || ( c > 126 ) ) && _saw_colon
146                         && ( ( c != '[' ) && ( c != '.' ) && ( ( c < 48 ) || ( c > 57 ) ) ) ) {
147                     _saw_colon = false;
148                 }
149                 if ( _in_open_bracket && ( c == ']' ) ) {
150                     _in_open_bracket = false;
151                 }
152             }
153             // \n\t is always ignored,
154             // as is " (34) and ' (39) (space is 32):
155             if ( ( isIgnoreQuotes() && ( ( c < 33 ) || ( c > 126 ) || ( c == 34 ) || ( c == 39 ) || ( ( getCladeLevel() == 0 ) && ( c == ';' ) ) ) )
156                     || ( !isIgnoreQuotes() && ( ( c < 32 ) || ( c > 126 ) || ( ( getCladeLevel() == 0 ) && ( c == ';' ) ) ) ) ) {
157                 // Do nothing.
158             }
159             else if ( ( c == 32 ) && ( !_in_single_quote && !_in_double_quote ) ) {
160                 // Do nothing.
161             }
162             else if ( _in_comment ) {
163                 if ( c == ']' ) {
164                     _in_comment = false;
165                 }
166             }
167             else if ( _in_double_quote ) {
168                 if ( c == '"' ) {
169                     _in_double_quote = false;
170                 }
171                 else {
172                     getCurrentAnotation().append( c );
173                 }
174             }
175             else if ( c == '"' ) {
176                 _in_double_quote = true;
177             }
178             else if ( _in_single_quote ) {
179                 if ( c == 39 ) {
180                     _in_single_quote = false;
181                 }
182                 else {
183                     getCurrentAnotation().append( c );
184                 }
185             }
186             else if ( c == 39 ) {
187                 _in_single_quote = true;
188             }
189             else if ( c == '[' ) {
190                 _saw_open_bracket = true;
191                 _in_open_bracket = true;
192             }
193             else if ( _saw_open_bracket ) {
194                 if ( c != ']' ) {
195                     // everything not starting with "[&" is considered a comment
196                     // unless ":digits and/or . [bootstrap]":
197                     if ( c == '&' ) {
198                         getCurrentAnotation().append( "[&" );
199                     }
200                     else if ( _saw_colon ) {
201                         getCurrentAnotation().append( "[" + c );
202                     }
203                     else {
204                         _in_comment = true;
205                     }
206                 }
207                 // comment consisting just of "[]":
208                 _saw_open_bracket = false;
209             }
210             else if ( ( c == '(' ) && !_in_open_bracket ) {
211                 Phylogeny phy = processOpenParen2();
212                 if ( phy != null ) {
213                     ++_i;
214                     return phy;
215                 }
216             }
217             else if ( ( c == ')' ) && !_in_open_bracket ) {
218                 processCloseParen();
219             }
220             else if ( ( c == ',' ) && !_in_open_bracket ) {
221                 processComma();
222             }
223             else {
224                 getCurrentAnotation().append( c );
225             }
226             ++_i;
227         } //  while ( true ) 
228         System.out.println( "done with loop" );
229         if ( getCladeLevel() != 0 ) {
230             //   setPhylogenies( null );
231             //   throw new PhylogenyParserException( "error in NH (Newick)/NHX formatted data: most likely cause: number of open parens does not equal number of close parens" );
232         }
233         if ( getCurrentPhylogeny() != null ) {
234             return finishPhylogeny2();
235         }
236         else if ( getCurrentAnotation().length() > 0 ) {
237             System.out.println( "1node=" + getCurrentAnotation() );
238             return finishSingleNodePhylogeny2();
239         }
240         else {
241             return null;
242         }
243     } // parse()
244
245     public TAXONOMY_EXTRACTION getTaxonomyExtraction() {
246         return _taxonomy_extraction;
247     }
248
249     public boolean hasNext() {
250         return _has_next;
251     }
252
253     public void setGuessRootedness( final boolean guess_rootedness ) {
254         _guess_rootedness = guess_rootedness;
255     }
256
257     public void setIgnoreQuotes( final boolean ignore_quotes ) {
258         _ignore_quotes = ignore_quotes;
259     }
260
261     public void setReplaceUnderscores( final boolean replace_underscores ) {
262         _replace_underscores = replace_underscores;
263     }
264
265     /**
266      * This sets the source to be parsed. The source can be: String,
267      * StringBuffer, char[], File, or InputStream. The source can contain more
268      * than one phylogenies in either New Hamphshire (NH) or New Hamphshire
269      * Extended (NHX) format. There is no need to separate phylogenies with any
270      * special character. White space is always ignored, as are semicolons
271      * inbetween phylogenies. Example of a source describing two phylogenies
272      * (source is a String, in this example): "(A,(B,(C,(D,E)de)cde)bcde)abcde
273      * ((((A,B)ab,C)abc,D)abcd,E)abcde". Everything between a '[' followed by any
274      * character other than '&' and ']' is considered a comment and ignored
275      * (example: "[this is a comment]"). NHX tags are surrounded by '[&&NHX' and
276      * ']' (example: "[&&NHX:S=Varanus_storri]"). A sequence like "[& some
277      * info]" is ignored, too (at the PhylogenyNode level, though).
278      * Exception: numbers only between [ and ] (e.g. [90]) are interpreted as support values.
279      * 
280      * @see #parse()
281      * @see org.forester.io.parsers.PhylogenyParser#setSource(java.lang.Object)
282      * @param nhx_source
283      *            the source to be parsed (String, StringBuffer, char[], File,
284      *            or InputStream)
285      * @throws IOException
286      * @throws PhylogenyParserException
287      */
288     @Override
289     public void setSource( final Object nhx_source ) throws PhylogenyParserException, IOException {
290         if ( nhx_source == null ) {
291             throw new PhylogenyParserException( getClass() + ": attempt to parse null object." );
292         }
293         else if ( nhx_source instanceof String ) {
294             setInputType( NHXParser2.STRING );
295             setSourceLength( ( ( String ) nhx_source ).length() );
296             setNhxSource( nhx_source );
297         }
298         else if ( nhx_source instanceof StringBuilder ) {
299             setInputType( NHXParser2.STRING_BUILDER );
300             setSourceLength( ( ( StringBuilder ) nhx_source ).length() );
301             setNhxSource( nhx_source );
302         }
303         else if ( nhx_source instanceof StringBuffer ) {
304             setInputType( NHXParser2.STRING_BUFFER );
305             setSourceLength( ( ( StringBuffer ) nhx_source ).length() );
306             setNhxSource( nhx_source );
307         }
308         else if ( nhx_source instanceof StringBuilder ) {
309             setInputType( NHXParser2.STRING_BUILDER );
310             setSourceLength( ( ( StringBuilder ) nhx_source ).length() );
311             setNhxSource( nhx_source );
312         }
313         else if ( nhx_source instanceof char[] ) {
314             setInputType( NHXParser2.CHAR_ARRAY );
315             setSourceLength( ( ( char[] ) nhx_source ).length );
316             setNhxSource( nhx_source );
317         }
318         else if ( nhx_source instanceof File ) {
319             setInputType( NHXParser2.BUFFERED_READER );
320             setSourceLength( 0 );
321             final File f = ( File ) nhx_source;
322             final String error = ForesterUtil.isReadableFile( f );
323             if ( !ForesterUtil.isEmpty( error ) ) {
324                 throw new PhylogenyParserException( error );
325             }
326             setNhxSource( new BufferedReader( new FileReader( f ) ) );
327         }
328         else if ( nhx_source instanceof InputStream ) {
329             setInputType( NHXParser2.BUFFERED_READER );
330             setSourceLength( 0 );
331             final InputStreamReader isr = new InputStreamReader( ( InputStream ) nhx_source );
332             setNhxSource( new BufferedReader( isr ) );
333         }
334         else {
335             throw new IllegalArgumentException( getClass() + " can only parse objects of type String,"
336                     + " StringBuffer, char[], File," + " or InputStream " + " [attempt to parse object of "
337                     + nhx_source.getClass() + "]." );
338         }
339         setHasNext( true );
340         reset();
341     }
342
343     public void setTaxonomyExtraction( final TAXONOMY_EXTRACTION taxonomy_extraction ) {
344         _taxonomy_extraction = taxonomy_extraction;
345     }
346
347     public void reset() {
348         setHasNext( false );
349         _i = 0;
350         _in_comment = false;
351         _saw_colon = false;
352         _saw_open_bracket = false;
353         _in_open_bracket = false;
354         _in_double_quote = false;
355         _in_single_quote = false;
356         setCladeLevel( 0 );
357         newCurrentAnotation();
358         setCurrentPhylogeny( null );
359         setCurrentNode( null );
360         _my_source_str = null;
361         _my_source_sbuff = null;
362         _my_source_sbuil = null;
363         _my_source_charary = null;
364         _my_source_br = null;
365         switch ( getInputType() ) {
366             case STRING:
367                 _my_source_str = ( String ) getNhxSource();
368                 break;
369             case STRING_BUFFER:
370                 _my_source_sbuff = ( StringBuffer ) getNhxSource();
371                 break;
372             case STRING_BUILDER:
373                 _my_source_sbuil = ( StringBuilder ) getNhxSource();
374                 break;
375             case CHAR_ARRAY:
376                 _my_source_charary = ( char[] ) getNhxSource();
377                 break;
378             case BUFFERED_READER:
379                 _my_source_br = ( BufferedReader ) getNhxSource();
380                 break;
381             default:
382                 throw new RuntimeException( "unknown input type" );
383         }
384     }
385
386     /**
387      * Decreases the clade level by one.
388      * 
389      * @throws PhylogenyParserException
390      *             if level goes below zero.
391      */
392     private void decreaseCladeLevel() throws PhylogenyParserException {
393         if ( getCladeLevel() < 0 ) {
394             throw new PhylogenyParserException( "error in NH (Newick)/NHX formatted data: most likely cause: number of close parens is larger than number of open parens" );
395         }
396         --_clade_level;
397     }
398
399     private Phylogeny finishPhylogeny2() throws PhylogenyParserException, NHXFormatException,
400             PhyloXmlDataFormatException {
401         //setCladeLevel( 0 );
402         if ( getCurrentPhylogeny() != null ) {
403             System.out.println( "cp=" + getCurrentPhylogeny() );
404             System.out.println( "ca=" + getCurrentAnotation().toString() );
405             parseNHX( getCurrentAnotation().toString(),
406                       getCurrentPhylogeny().getRoot(),
407                       getTaxonomyExtraction(),
408                       isReplaceUnderscores() );
409             if ( GUESS_IF_SUPPORT_VALUES ) {
410                 if ( isBranchLengthsLikeBootstrapValues( getCurrentPhylogeny() ) ) {
411                     moveBranchLengthsToConfidenceValues( getCurrentPhylogeny() );
412                 }
413             }
414             if ( isGuessRootedness() ) {
415                 final PhylogenyNode root = getCurrentPhylogeny().getRoot();
416                 if ( ( root.getDistanceToParent() >= 0.0 ) || !ForesterUtil.isEmpty( root.getName() )
417                         || !ForesterUtil.isEmpty( PhylogenyMethods.getSpecies( root ) ) || root.isHasAssignedEvent() ) {
418                     getCurrentPhylogeny().setRooted( true );
419                 }
420             }
421             return getCurrentPhylogeny();
422         }
423         return null;
424     }
425
426     private Phylogeny finishSingleNodePhylogeny2() throws PhylogenyParserException, NHXFormatException,
427             PhyloXmlDataFormatException {
428         // setCladeLevel( 0 );
429         final PhylogenyNode new_node = new PhylogenyNode();
430         parseNHX( getCurrentAnotation().toString(), new_node, getTaxonomyExtraction(), isReplaceUnderscores() );
431         setCurrentPhylogeny( new Phylogeny() );
432         getCurrentPhylogeny().setRoot( new_node );
433         return getCurrentPhylogeny();
434     }
435
436     private int getCladeLevel() {
437         return _clade_level;
438     }
439
440     private StringBuilder getCurrentAnotation() {
441         return _current_anotation;
442     }
443
444     private PhylogenyNode getCurrentNode() {
445         return _current_node;
446     }
447
448     private Phylogeny getCurrentPhylogeny() {
449         return _current_phylogeny;
450     }
451
452     private byte getInputType() {
453         return _input_type;
454     }
455
456     private Object getNhxSource() {
457         return _nhx_source;
458     }
459
460     private int getSourceLength() {
461         return _source_length;
462     }
463
464     private void increaseCladeLevel() {
465         ++_clade_level;
466     }
467
468     private void init() {
469         setTaxonomyExtraction( TAXONOMY_EXTRACTION_DEFAULT );
470         setReplaceUnderscores( REPLACE_UNDERSCORES_DEFAULT );
471         setGuessRootedness( GUESS_ROOTEDNESS_DEFAULT );
472         setIgnoreQuotes( IGNORE_QUOTES_DEFAULT );
473         setHasNext( false );
474     }
475
476     private boolean isGuessRootedness() {
477         return _guess_rootedness;
478     }
479
480     private boolean isIgnoreQuotes() {
481         return _ignore_quotes;
482     }
483
484     private boolean isReplaceUnderscores() {
485         return _replace_underscores;
486     }
487
488     private boolean isSawClosingParen() {
489         return _saw_closing_paren;
490     }
491
492     /**
493      * Replaces the current annotation with a new StringBuffer.
494      */
495     private void newCurrentAnotation() {
496         setCurrentAnotation( new StringBuilder() );
497     }
498
499     /**
500      * Called if a closing paren is encountered.
501      * 
502      * @throws PhylogenyParserException
503      * @throws NHXFormatException
504      * @throws PhyloXmlDataFormatException 
505      */
506     private void processCloseParen() throws PhylogenyParserException, NHXFormatException, PhyloXmlDataFormatException {
507         decreaseCladeLevel();
508         if ( !isSawClosingParen() ) {
509             final PhylogenyNode new_node = new PhylogenyNode();
510             parseNHX( getCurrentAnotation().toString(), new_node, getTaxonomyExtraction(), isReplaceUnderscores() );
511             newCurrentAnotation();
512             getCurrentNode().addAsChild( new_node );
513         }
514         else {
515             parseNHX( getCurrentAnotation().toString(),
516                       getCurrentNode().getLastChildNode(),
517                       getTaxonomyExtraction(),
518                       isReplaceUnderscores() );
519             newCurrentAnotation();
520         }
521         if ( !getCurrentNode().isRoot() ) {
522             setCurrentNode( getCurrentNode().getParent() );
523         }
524         setSawClosingParen( true );
525     }
526
527     /**
528      * Called if a comma is encountered.
529      * 
530      * @throws PhylogenyParserException
531      * @throws NHXFormatException
532      * @throws PhyloXmlDataFormatException 
533      */
534     private void processComma() throws PhylogenyParserException, NHXFormatException, PhyloXmlDataFormatException {
535         if ( !isSawClosingParen() ) {
536             final PhylogenyNode new_node = new PhylogenyNode();
537             parseNHX( getCurrentAnotation().toString(), new_node, getTaxonomyExtraction(), isReplaceUnderscores() );
538             if ( getCurrentNode() == null ) {
539                 throw new NHXFormatException( "format might not be NH or NHX" );
540             }
541             getCurrentNode().addAsChild( new_node );
542         }
543         else {
544             parseNHX( getCurrentAnotation().toString(),
545                       getCurrentNode().getLastChildNode(),
546                       getTaxonomyExtraction(),
547                       isReplaceUnderscores() );
548         }
549         newCurrentAnotation();
550         setSawClosingParen( false );
551     }
552
553     private Phylogeny processOpenParen2() throws PhylogenyParserException, NHXFormatException,
554             PhyloXmlDataFormatException {
555         Phylogeny phy = null;
556         final PhylogenyNode new_node = new PhylogenyNode();
557         if ( getCladeLevel() == 0 ) {
558             if ( getCurrentPhylogeny() != null ) {
559                 phy = finishPhylogeny2();
560             }
561             setCladeLevel( 1 );
562             newCurrentAnotation();
563             setCurrentPhylogeny( new Phylogeny() );
564             getCurrentPhylogeny().setRoot( new_node );
565         }
566         else {
567             increaseCladeLevel();
568             getCurrentNode().addAsChild( new_node );
569         }
570         setCurrentNode( new_node );
571         setSawClosingParen( false );
572         return phy;
573     }
574
575     private void setCladeLevel( final int clade_level ) {
576         if ( clade_level < 0 ) {
577             throw new IllegalArgumentException( "attempt to set clade level to a number smaller than zero" );
578         }
579         _clade_level = clade_level;
580     }
581
582     private void setCurrentAnotation( final StringBuilder current_anotation ) {
583         _current_anotation = current_anotation;
584     }
585
586     private void setCurrentNode( final PhylogenyNode current_node ) {
587         _current_node = current_node;
588     }
589
590     private void setCurrentPhylogeny( final Phylogeny current_phylogeny ) {
591         _current_phylogeny = current_phylogeny;
592     }
593
594     private void setHasNext( final boolean has_next ) {
595         _has_next = has_next;
596     }
597
598     private void setInputType( final byte input_type ) {
599         _input_type = input_type;
600     }
601
602     private void setNhxSource( final Object nhx_source ) {
603         _nhx_source = nhx_source;
604     }
605
606     private void setSawClosingParen( final boolean saw_closing_paren ) {
607         _saw_closing_paren = saw_closing_paren;
608     }
609
610     private void setSourceLength( final int source_length ) {
611         _source_length = source_length;
612     }
613
614     public static void parseNHX( String s,
615                                  final PhylogenyNode node_to_annotate,
616                                  final TAXONOMY_EXTRACTION taxonomy_extraction,
617                                  final boolean replace_underscores ) throws NHXFormatException,
618             PhyloXmlDataFormatException {
619         if ( ( taxonomy_extraction != TAXONOMY_EXTRACTION.NO ) && replace_underscores ) {
620             throw new IllegalArgumentException( "cannot extract taxonomies and replace under scores at the same time" );
621         }
622         if ( ( s != null ) && ( s.length() > 0 ) ) {
623             if ( replace_underscores ) {
624                 s = s.replaceAll( "_+", " " );
625             }
626             boolean is_nhx = false;
627             final int ob = s.indexOf( "[" );
628             if ( ob > -1 ) {
629                 String b = "";
630                 is_nhx = true;
631                 final int cb = s.indexOf( "]" );
632                 if ( cb < 0 ) {
633                     throw new NHXFormatException( "error in NHX formatted data: no closing \"]\" in \"" + s + "\"" );
634                 }
635                 if ( s.indexOf( "&&NHX" ) == ( ob + 1 ) ) {
636                     b = s.substring( ob + 6, cb );
637                 }
638                 else {
639                     // No &&NHX and digits only: is likely to be a support value.
640                     final String bracketed = s.substring( ob + 1, cb );
641                     final Matcher numbers_only = NUMBERS_ONLY_PATTERN.matcher( bracketed );
642                     if ( numbers_only.matches() ) {
643                         b = ":" + NHXtags.SUPPORT + bracketed;
644                     }
645                     else if ( s.indexOf( "prob=" ) > -1 ) {
646                         processMrBayes3Data( s, node_to_annotate );
647                     }
648                 }
649                 s = s.substring( 0, ob ) + b;
650                 if ( ( s.indexOf( "[" ) > -1 ) || ( s.indexOf( "]" ) > -1 ) ) {
651                     throw new NHXFormatException( "error in NHX formatted data: more than one \"]\" or \"[\"" );
652                 }
653             }
654             final StringTokenizer t = new StringTokenizer( s, ":" );
655             if ( t.countTokens() > 0 ) {
656                 if ( !s.startsWith( ":" ) ) {
657                     node_to_annotate.setName( t.nextToken() );
658                     if ( !replace_underscores && ( !is_nhx && ( taxonomy_extraction != TAXONOMY_EXTRACTION.NO ) ) ) {
659                         ParserUtils.extractTaxonomyDataFromNodeName( node_to_annotate, taxonomy_extraction );
660                     }
661                 }
662                 while ( t.hasMoreTokens() ) {
663                     s = t.nextToken();
664                     if ( s.startsWith( org.forester.io.parsers.nhx.NHXtags.SPECIES_NAME ) ) {
665                         if ( !node_to_annotate.getNodeData().isHasTaxonomy() ) {
666                             node_to_annotate.getNodeData().setTaxonomy( new Taxonomy() );
667                         }
668                         node_to_annotate.getNodeData().getTaxonomy().setScientificName( s.substring( 2 ) );
669                     }
670                     else if ( s.startsWith( org.forester.io.parsers.nhx.NHXtags.ANNOTATION ) ) {
671                         if ( !node_to_annotate.getNodeData().isHasSequence() ) {
672                             node_to_annotate.getNodeData().setSequence( new Sequence() );
673                         }
674                         final Annotation annotation = new Annotation( "_:_" );
675                         annotation.setDesc( s.substring( 3 ) );
676                         node_to_annotate.getNodeData().getSequence().addAnnotation( annotation );
677                     }
678                     else if ( s.startsWith( org.forester.io.parsers.nhx.NHXtags.IS_DUPLICATION ) ) {
679                         if ( ( s.charAt( 2 ) == 'Y' ) || ( s.charAt( 2 ) == 'T' ) ) {
680                             node_to_annotate.getNodeData().setEvent( Event.createSingleDuplicationEvent() );
681                         }
682                         else if ( ( s.charAt( 2 ) == 'N' ) || ( s.charAt( 2 ) == 'F' ) ) {
683                             node_to_annotate.getNodeData().setEvent( Event.createSingleSpeciationEvent() );
684                         }
685                         else if ( s.charAt( 2 ) == '?' ) {
686                             node_to_annotate.getNodeData().setEvent( Event.createSingleSpeciationOrDuplicationEvent() );
687                         }
688                         else {
689                             throw new NHXFormatException( "error in NHX formatted data: :D=Y or :D=N or :D=?" );
690                         }
691                     }
692                     else if ( s.startsWith( NHXtags.SUPPORT ) ) {
693                         PhylogenyMethods.setConfidence( node_to_annotate, doubleValue( s.substring( 2 ) ) );
694                     }
695                     else if ( s.startsWith( NHXtags.TAXONOMY_ID ) ) {
696                         if ( !node_to_annotate.getNodeData().isHasTaxonomy() ) {
697                             node_to_annotate.getNodeData().setTaxonomy( new Taxonomy() );
698                         }
699                         node_to_annotate.getNodeData().getTaxonomy().setIdentifier( new Identifier( s.substring( 2 ) ) );
700                     }
701                     else if ( s.startsWith( NHXtags.PARENT_BRANCH_WIDTH ) ) {
702                         PhylogenyMethods.setBranchWidthValue( node_to_annotate, Integer.parseInt( s.substring( 2 ) ) );
703                     }
704                     else if ( s.startsWith( NHXtags.COLOR ) ) {
705                         final Color c = NHXParser2.stringToColor( s.substring( 2 ) );
706                         if ( c != null ) {
707                             PhylogenyMethods.setBranchColorValue( node_to_annotate, c );
708                         }
709                     }
710                     else if ( s.startsWith( NHXtags.CUSTOM_DATA_ON_NODE ) ) {
711                         if ( !node_to_annotate.getNodeData().isHasProperties() ) {
712                             node_to_annotate.getNodeData().setProperties( new PropertiesMap() );
713                         }
714                         node_to_annotate.getNodeData().getProperties().addProperty( Property.createFromNhxString( s ) );
715                     }
716                     else if ( s.startsWith( NHXtags.DOMAIN_STRUCTURE ) ) {
717                         if ( !node_to_annotate.getNodeData().isHasSequence() ) {
718                             node_to_annotate.getNodeData().setSequence( new Sequence() );
719                         }
720                         node_to_annotate.getNodeData().getSequence()
721                                 .setDomainArchitecture( new DomainArchitecture( s.substring( 3 ) ) );
722                     }
723                     else if ( s.startsWith( NHXtags.SEQUENCE_ACCESSION ) ) {
724                         if ( !node_to_annotate.getNodeData().isHasSequence() ) {
725                             node_to_annotate.getNodeData().setSequence( new Sequence() );
726                         }
727                         node_to_annotate.getNodeData().getSequence()
728                                 .setAccession( new Accession( s.substring( 3 ), "?" ) );
729                     }
730                     else if ( s.startsWith( NHXtags.GENE_NAME ) ) {
731                         if ( !node_to_annotate.getNodeData().isHasSequence() ) {
732                             node_to_annotate.getNodeData().setSequence( new Sequence() );
733                         }
734                         node_to_annotate.getNodeData().getSequence().setName( s.substring( 3 ) );
735                     }
736                     else if ( s.indexOf( '=' ) < 0 ) {
737                         if ( node_to_annotate.getDistanceToParent() != PhylogenyDataUtil.BRANCH_LENGTH_DEFAULT ) {
738                             throw new NHXFormatException( "error in NHX formatted data: more than one distance to parent:"
739                                     + "\"" + s + "\"" );
740                         }
741                         node_to_annotate.setDistanceToParent( doubleValue( s ) );
742                     }
743                 } // while ( t.hasMoreTokens() ) 
744             }
745         }
746     }
747
748     private static double doubleValue( final String str ) throws NHXFormatException {
749         try {
750             return Double.valueOf( str ).doubleValue();
751         }
752         catch ( final NumberFormatException ex ) {
753             throw new NHXFormatException( "error in NH/NHX formatted data: failed to parse number from " + "\"" + str
754                     + "\"" );
755         }
756     }
757
758     private static boolean isBranchLengthsLikeBootstrapValues( final Phylogeny p ) {
759         final PhylogenyNodeIterator it = p.iteratorExternalForward();
760         final double d0 = it.next().getDistanceToParent();
761         if ( ( d0 < 10 ) || !it.hasNext() ) {
762             return false;
763         }
764         while ( it.hasNext() ) {
765             final double d = it.next().getDistanceToParent();
766             if ( ( d != d0 ) || ( d < 10 ) ) {
767                 return false;
768             }
769         }
770         return true;
771     }
772
773     private static void moveBranchLengthsToConfidenceValues( final Phylogeny p ) {
774         final PhylogenyNodeIterator it = p.iteratorPostorder();
775         while ( it.hasNext() ) {
776             final PhylogenyNode n = it.next();
777             PhylogenyMethods.setBootstrapConfidence( n, n.getDistanceToParent() );
778             n.setDistanceToParent( PhylogenyDataUtil.BRANCH_LENGTH_DEFAULT );
779         }
780     }
781
782     private static void processMrBayes3Data( final String s, final PhylogenyNode node_to_annotate )
783             throws NHXFormatException {
784         double sd = -1;
785         final Matcher mb_prob_sd_matcher = MB_PROB_SD_PATTERN.matcher( s );
786         if ( mb_prob_sd_matcher.find() ) {
787             try {
788                 sd = Double.parseDouble( mb_prob_sd_matcher.group( 1 ) );
789             }
790             catch ( final NumberFormatException e ) {
791                 throw new NHXFormatException( "failed to parse probability standard deviation (Mr Bayes output) from \""
792                         + s + "\"" );
793             }
794         }
795         final Matcher mb_prob_matcher = MB_PROB_PATTERN.matcher( s );
796         if ( mb_prob_matcher.find() ) {
797             double prob = -1;
798             try {
799                 prob = Double.parseDouble( mb_prob_matcher.group( 1 ) );
800             }
801             catch ( final NumberFormatException e ) {
802                 throw new NHXFormatException( "failed to parse probability (Mr Bayes output) from \"" + s + "\"" );
803             }
804             if ( prob >= 0.0 ) {
805                 if ( sd >= 0.0 ) {
806                     node_to_annotate.getBranchData()
807                             .addConfidence( new Confidence( prob, "posterior probability", sd ) );
808                 }
809                 else {
810                     node_to_annotate.getBranchData().addConfidence( new Confidence( prob, "posterior probability" ) );
811                 }
812             }
813         }
814         final Matcher mb_bl_matcher = MB_BL_PATTERN.matcher( s );
815         if ( mb_bl_matcher.find() ) {
816             double bl = -1;
817             try {
818                 bl = Double.parseDouble( mb_bl_matcher.group( 1 ) );
819             }
820             catch ( final NumberFormatException e ) {
821                 throw new NHXFormatException( "failed to parse median branch length (Mr Bayes output) from \"" + s
822                         + "\"" );
823             }
824             if ( bl >= 0.0 ) {
825                 node_to_annotate.setDistanceToParent( bl );
826             }
827         }
828     }
829
830     /**
831      * Parses String s in the format r.g.b (e.g. "12.34.234" ) into red, green,
832      * and blue and returns the corresponding Color.
833      */
834     private static Color stringToColor( final String s ) {
835         final StringTokenizer st = new StringTokenizer( s, "." );
836         if ( st.countTokens() != 3 ) {
837             throw new IllegalArgumentException( "illegal format for color: " + s );
838         }
839         final int red = ForesterUtil.limitRangeForColor( Integer.parseInt( st.nextToken() ) );
840         final int green = ForesterUtil.limitRangeForColor( Integer.parseInt( st.nextToken() ) );
841         final int blu = ForesterUtil.limitRangeForColor( Integer.parseInt( st.nextToken() ) );
842         return new Color( red, green, blu );
843     }
844
845     @Override
846     public Phylogeny[] parse() throws IOException {
847         // TODO Auto-generated method stub
848         return null;
849     }
850 }