inprogress
[jalview.git] / forester / java / src / org / forester / io / parsers / phyloxml / PhyloXmlHandler.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.phyloxml;
27
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33
34 import org.forester.io.parsers.phyloxml.data.BinaryCharactersParser;
35 import org.forester.io.parsers.phyloxml.data.BranchWidthParser;
36 import org.forester.io.parsers.phyloxml.data.ColorParser;
37 import org.forester.io.parsers.phyloxml.data.ConfidenceParser;
38 import org.forester.io.parsers.phyloxml.data.DateParser;
39 import org.forester.io.parsers.phyloxml.data.DistributionParser;
40 import org.forester.io.parsers.phyloxml.data.EventParser;
41 import org.forester.io.parsers.phyloxml.data.IdentifierParser;
42 import org.forester.io.parsers.phyloxml.data.PropertyParser;
43 import org.forester.io.parsers.phyloxml.data.ReferenceParser;
44 import org.forester.io.parsers.phyloxml.data.SequenceParser;
45 import org.forester.io.parsers.phyloxml.data.SequenceRelationParser;
46 import org.forester.io.parsers.phyloxml.data.TaxonomyParser;
47 import org.forester.io.parsers.util.PhylogenyParserException;
48 import org.forester.phylogeny.Phylogeny;
49 import org.forester.phylogeny.PhylogenyNode;
50 import org.forester.phylogeny.data.BinaryCharacters;
51 import org.forester.phylogeny.data.BranchColor;
52 import org.forester.phylogeny.data.BranchWidth;
53 import org.forester.phylogeny.data.Confidence;
54 import org.forester.phylogeny.data.Date;
55 import org.forester.phylogeny.data.Distribution;
56 import org.forester.phylogeny.data.Event;
57 import org.forester.phylogeny.data.Identifier;
58 import org.forester.phylogeny.data.NodeVisualData;
59 import org.forester.phylogeny.data.PhylogenyDataUtil;
60 import org.forester.phylogeny.data.PropertiesMap;
61 import org.forester.phylogeny.data.Property;
62 import org.forester.phylogeny.data.Reference;
63 import org.forester.phylogeny.data.Sequence;
64 import org.forester.phylogeny.data.SequenceRelation;
65 import org.forester.phylogeny.data.SequenceRelation.SEQUENCE_RELATION_TYPE;
66 import org.forester.phylogeny.data.Taxonomy;
67 import org.forester.util.FailedConditionCheckException;
68 import org.forester.util.ForesterConstants;
69 import org.forester.util.ForesterUtil;
70 import org.xml.sax.Attributes;
71 import org.xml.sax.SAXException;
72 import org.xml.sax.helpers.DefaultHandler;
73
74 public final class PhyloXmlHandler extends DefaultHandler {
75
76     private static final String                              PHYLOXML               = "phyloxml";
77     private String                                           _current_element_name;
78     private Phylogeny                                        _current_phylogeny;
79     private List<Phylogeny>                                  _phylogenies;
80     private XmlElement                                       _current_xml_element;
81     private PhylogenyNode                                    _current_node;
82     private static Map<Phylogeny, HashMap<String, Sequence>> phylogenySequencesById = new HashMap<Phylogeny, HashMap<String, Sequence>>();
83
84     PhyloXmlHandler() {
85         // Constructor.
86     }
87
88     private void addNode() {
89         final PhylogenyNode new_node = new PhylogenyNode();
90         getCurrentNode().addAsChild( new_node );
91         setCurrentNode( new_node );
92     }
93
94     @Override
95     public void characters( final char[] chars, final int start_index, final int end_index ) {
96         if ( ( ( getCurrentXmlElement() != null ) && ( getCurrentElementName() != null ) )
97                 && !getCurrentElementName().equals( PhyloXmlMapping.CLADE )
98                 && !getCurrentElementName().equals( PhyloXmlMapping.PHYLOGENY ) ) {
99             if ( !ForesterUtil.isEmpty( getCurrentXmlElement().getValueAsString() ) ) {
100                 getCurrentXmlElement().appendValue( new String( chars, start_index, end_index ) );
101             }
102             else {
103                 getCurrentXmlElement().setValue( new String( chars, start_index, end_index ) );
104             }
105         }
106     }
107
108     @Override
109     public void endElement( final String namespace_uri, final String local_name, final String qualified_name )
110             throws SAXException {
111         if ( ForesterUtil.isEmpty( namespace_uri ) || namespace_uri.startsWith( ForesterConstants.PHYLO_XML_LOCATION ) ) {
112             if ( local_name.equals( PhyloXmlMapping.CLADE ) ) {
113                 try {
114                     mapElementToPhylogenyNode( getCurrentXmlElement(), getCurrentNode() );
115                     if ( !getCurrentNode().isRoot() ) {
116                         setCurrentNode( getCurrentNode().getParent() );
117                     }
118                     getCurrentXmlElement().setValue( null );
119                     setCurrentXmlElement( getCurrentXmlElement().getParent() );
120                 }
121                 catch ( final PhylogenyParserException ex ) {
122                     throw new SAXException( ex.getMessage() );
123                 }
124                 catch ( final PhyloXmlDataFormatException e ) {
125                     throw new SAXException( e.getMessage() );
126                 }
127             }
128             else if ( local_name.equals( PhyloXmlMapping.SEQUENCE_RELATION ) ) {
129                 try {
130                     if ( getCurrentPhylogeny() != null ) {
131                         final SequenceRelation seqRelation = ( SequenceRelation ) SequenceRelationParser
132                                 .getInstance( getCurrentPhylogeny() ).parse( getCurrentXmlElement() );
133                         final Map<String, Sequence> sequencesById = getSequenceMapByIdForPhylogeny( getCurrentPhylogeny() );
134                         final Sequence ref0 = sequencesById.get( seqRelation.getRef0().getSourceId() ), ref1 = sequencesById
135                                 .get( seqRelation.getRef1().getSourceId() );
136                         if ( ref0 != null ) {
137                             // check for reverse relation
138                             boolean fFoundReverse = false;
139                             for( final SequenceRelation sr : ref0.getSequenceRelations() ) {
140                                 if ( sr.getType().equals( seqRelation.getType() )
141                                         && ( ( sr.getRef0().isEqual( ref1 ) && sr.getRef1().isEqual( ref0 ) ) || ( sr
142                                                 .getRef0().isEqual( ref0 ) && sr.getRef1().isEqual( ref1 ) ) ) ) {
143                                     // in this case we don't need to re-add it, but we make sure we don't loose the confidence value
144                                     fFoundReverse = true;
145                                     if ( ( sr.getConfidence() == null ) && ( seqRelation.getConfidence() != null ) ) {
146                                         sr.setConfidence( seqRelation.getConfidence() );
147                                     }
148                                 }
149                             }
150                             if ( !fFoundReverse ) {
151                                 ref0.addSequenceRelation( seqRelation );
152                             }
153                         }
154                         if ( ref1 != null ) {
155                             // check for reverse relation
156                             boolean fFoundReverse = false;
157                             for( final SequenceRelation sr : ref1.getSequenceRelations() ) {
158                                 if ( sr.getType().equals( seqRelation.getType() )
159                                         && ( ( sr.getRef0().isEqual( ref1 ) && sr.getRef1().isEqual( ref0 ) ) || ( sr
160                                                 .getRef0().isEqual( ref0 ) && sr.getRef1().isEqual( ref1 ) ) ) ) {
161                                     // in this case we don't need to re-add it, but we make sure we don't loose the confidence value
162                                     fFoundReverse = true;
163                                     if ( ( sr.getConfidence() == null ) && ( seqRelation.getConfidence() != null ) ) {
164                                         sr.setConfidence( seqRelation.getConfidence() );
165                                     }
166                                 }
167                             }
168                             if ( !fFoundReverse ) {
169                                 ref1.addSequenceRelation( seqRelation );
170                             }
171                         }
172                         // we add the type to the current phylogeny so we can know it needs to be displayed in the combo
173                         final Collection<SEQUENCE_RELATION_TYPE> relationTypesForCurrentPhylogeny = getCurrentPhylogeny()
174                                 .getRelevantSequenceRelationTypes();
175                         if ( !relationTypesForCurrentPhylogeny.contains( seqRelation.getType() ) ) {
176                             relationTypesForCurrentPhylogeny.add( seqRelation.getType() );
177                         }
178                     }
179                 }
180                 catch ( final PhyloXmlDataFormatException ex ) {
181                     throw new SAXException( ex.getMessage() );
182                 }
183             }
184             else if ( local_name.equals( PhyloXmlMapping.PHYLOGENY ) ) {
185                 try {
186                     PhyloXmlHandler.mapElementToPhylogeny( getCurrentXmlElement(), getCurrentPhylogeny() );
187                 }
188                 catch ( final PhylogenyParserException e ) {
189                     throw new SAXException( e.getMessage() );
190                 }
191                 catch ( final PhyloXmlDataFormatException e ) {
192                     throw new SAXException( e.getMessage() );
193                 }
194                 finishPhylogeny();
195                 reset();
196             }
197             else if ( local_name.equals( PHYLOXML ) ) {
198                 // Do nothing.
199             }
200             else if ( ( getCurrentPhylogeny() != null ) && ( getCurrentXmlElement().getParent() != null ) ) {
201                 setCurrentXmlElement( getCurrentXmlElement().getParent() );
202             }
203             setCurrentElementName( null );
204         }
205     }
206
207     private void finishPhylogeny() throws SAXException {
208         getCurrentPhylogeny().recalculateNumberOfExternalDescendants( false );
209         getPhylogenies().add( getCurrentPhylogeny() );
210         final HashMap<String, Sequence> phyloSequences = phylogenySequencesById.get( getCurrentPhylogeny() );
211         if ( phyloSequences != null ) {
212             getCurrentPhylogeny().setSequenceRelationQueries( phyloSequences.values() );
213             phylogenySequencesById.remove( getCurrentPhylogeny() );
214         }
215     }
216
217     private String getCurrentElementName() {
218         return _current_element_name;
219     }
220
221     private PhylogenyNode getCurrentNode() {
222         return _current_node;
223     }
224
225     private Phylogeny getCurrentPhylogeny() {
226         return _current_phylogeny;
227     }
228
229     private XmlElement getCurrentXmlElement() {
230         return _current_xml_element;
231     }
232
233     List<Phylogeny> getPhylogenies() {
234         return _phylogenies;
235     }
236
237     private void init() {
238         reset();
239         setPhylogenies( new ArrayList<Phylogeny>() );
240     }
241
242     private void initCurrentNode() {
243         if ( getCurrentNode() != null ) {
244             throw new FailedConditionCheckException( "attempt to create new current node when current node already exists" );
245         }
246         if ( getCurrentPhylogeny() == null ) {
247             throw new FailedConditionCheckException( "attempt to create new current node for non-existing phylogeny" );
248         }
249         final PhylogenyNode node = new PhylogenyNode();
250         getCurrentPhylogeny().setRoot( node );
251         setCurrentNode( getCurrentPhylogeny().getRoot() );
252     }
253
254     private void mapElementToPhylogenyNode( final XmlElement xml_element, final PhylogenyNode node )
255             throws PhylogenyParserException, PhyloXmlDataFormatException {
256         if ( xml_element.isHasAttribute( PhyloXmlMapping.BRANCH_LENGTH ) ) {
257             double d = 0;
258             try {
259                 d = Double.parseDouble( xml_element.getAttribute( PhyloXmlMapping.BRANCH_LENGTH ) );
260             }
261             catch ( final NumberFormatException e ) {
262                 throw new PhylogenyParserException( "ill formatted distance in clade attribute ["
263                         + xml_element.getAttribute( PhyloXmlMapping.BRANCH_LENGTH ) + "]: " + e.getMessage() );
264             }
265             node.setDistanceToParent( d );
266         }
267         if ( xml_element.isHasAttribute( PhyloXmlMapping.NODE_COLLAPSE ) ) {
268             final String collapse_str = xml_element.getAttribute( PhyloXmlMapping.NODE_COLLAPSE );
269             if ( !ForesterUtil.isEmpty( collapse_str ) && collapse_str.trim().equalsIgnoreCase( "true" ) ) {
270                 node.setCollapse( true );
271             }
272         }
273         for( int i = 0; i < xml_element.getNumberOfChildElements(); ++i ) {
274             final XmlElement element = xml_element.getChildElement( i );
275             final String qualified_name = element.getQualifiedName();
276             if ( qualified_name.equals( PhyloXmlMapping.BRANCH_LENGTH ) ) {
277                 if ( node.getDistanceToParent() != PhylogenyDataUtil.BRANCH_LENGTH_DEFAULT ) {
278                     throw new PhylogenyParserException( "ill advised attempt to set distance twice for the same clade (probably via element and via attribute)" );
279                 }
280                 node.setDistanceToParent( element.getValueAsDouble() );
281             }
282             if ( qualified_name.equals( PhyloXmlMapping.NODE_NAME ) ) {
283                 node.setName( element.getValueAsString() );
284             }
285             //  else if ( qualified_name.equals( PhyloXmlMapping.NODE_IDENTIFIER ) ) {
286             //      node.getNodeData().setNodeIdentifier( ( Identifier ) IdentifierParser.getInstance().parse( element ) );
287             //  }
288             else if ( qualified_name.equals( PhyloXmlMapping.TAXONOMY ) ) {
289                 node.getNodeData().addTaxonomy( ( Taxonomy ) TaxonomyParser.getInstance().parse( element ) );
290             }
291             else if ( qualified_name.equals( PhyloXmlMapping.SEQUENCE ) ) {
292                 final Sequence sequence = ( Sequence ) SequenceParser.getInstance().parse( element );
293                 node.getNodeData().addSequence( sequence );
294                 // we temporarily store all sequences that have a source ID so we can access them easily when we need to attach relations to them
295                 final String sourceId = sequence.getSourceId();
296                 if ( ( getCurrentPhylogeny() != null ) && !ForesterUtil.isEmpty( sourceId ) ) {
297                     getSequenceMapByIdForPhylogeny( getCurrentPhylogeny() ).put( sourceId, sequence );
298                 }
299             }
300             else if ( qualified_name.equals( PhyloXmlMapping.DISTRIBUTION ) ) {
301                 node.getNodeData().addDistribution( ( Distribution ) DistributionParser.getInstance().parse( element ) );
302             }
303             else if ( qualified_name.equals( PhyloXmlMapping.CLADE_DATE ) ) {
304                 node.getNodeData().setDate( ( Date ) DateParser.getInstance().parse( element ) );
305             }
306             else if ( qualified_name.equals( PhyloXmlMapping.REFERENCE ) ) {
307                 node.getNodeData().addReference( ( Reference ) ReferenceParser.getInstance().parse( element ) );
308             }
309             else if ( qualified_name.equals( PhyloXmlMapping.BINARY_CHARACTERS ) ) {
310                 node.getNodeData().setBinaryCharacters( ( BinaryCharacters ) BinaryCharactersParser.getInstance()
311                         .parse( element ) );
312             }
313             else if ( qualified_name.equals( PhyloXmlMapping.COLOR ) ) {
314                 node.getBranchData().setBranchColor( ( BranchColor ) ColorParser.getInstance().parse( element ) );
315             }
316             else if ( qualified_name.equals( PhyloXmlMapping.CONFIDENCE ) ) {
317                 node.getBranchData().addConfidence( ( Confidence ) ConfidenceParser.getInstance().parse( element ) );
318             }
319             else if ( qualified_name.equals( PhyloXmlMapping.WIDTH ) ) {
320                 node.getBranchData().setBranchWidth( ( BranchWidth ) BranchWidthParser.getInstance().parse( element ) );
321             }
322             else if ( qualified_name.equals( PhyloXmlMapping.EVENTS ) ) {
323                 node.getNodeData().setEvent( ( Event ) EventParser.getInstance().parse( element ) );
324             }
325             else if ( qualified_name.equals( PhyloXmlMapping.PROPERTY ) ) {
326                 final Property prop = ( Property ) PropertyParser.getInstance().parse( element );
327                 if ( prop.getRef().startsWith( NodeVisualData.APTX_VISUALIZATION_REF ) ) {
328                     if ( node.getNodeData().getNodeVisualData() == null ) {
329                         node.getNodeData().setNodeVisualData( new NodeVisualData() );
330                     }
331                     final NodeVisualData vd = node.getNodeData().getNodeVisualData();
332                     vd.parseProperty( prop );
333                 }
334                 else {
335                     if ( !node.getNodeData().isHasProperties() ) {
336                         node.getNodeData().setProperties( new PropertiesMap() );
337                     }
338                     node.getNodeData().getProperties().addProperty( prop );
339                 }
340             }
341         }
342     }
343
344     private void newClade() {
345         if ( getCurrentNode() == null ) {
346             initCurrentNode();
347         }
348         else {
349             addNode();
350         }
351     }
352
353     private void newPhylogeny() {
354         setCurrentPhylogeny( new Phylogeny() );
355     }
356
357     private void reset() {
358         setCurrentPhylogeny( null );
359         setCurrentNode( null );
360         setCurrentElementName( null );
361         setCurrentXmlElement( null );
362     }
363
364     private void setCurrentElementName( final String element_name ) {
365         _current_element_name = element_name;
366     }
367
368     private void setCurrentNode( final PhylogenyNode current_node ) {
369         _current_node = current_node;
370     }
371
372     private void setCurrentPhylogeny( final Phylogeny phylogeny ) {
373         _current_phylogeny = phylogeny;
374     }
375
376     private void setCurrentXmlElement( final XmlElement element ) {
377         _current_xml_element = element;
378     }
379
380     private void setPhylogenies( final List<Phylogeny> phylogenies ) {
381         _phylogenies = phylogenies;
382     }
383
384     @Override
385     public void startDocument() throws SAXException {
386         init();
387     }
388
389     @Override
390     public void startElement( final String namespace_uri,
391                               final String local_name,
392                               final String qualified_name,
393                               final Attributes attributes ) throws SAXException {
394         if ( ForesterUtil.isEmpty( namespace_uri ) || namespace_uri.startsWith( ForesterConstants.PHYLO_XML_LOCATION ) ) {
395             setCurrentElementName( local_name );
396             if ( local_name.equals( PhyloXmlMapping.CLADE ) ) {
397                 final XmlElement element = new XmlElement( namespace_uri, local_name, local_name, attributes );
398                 getCurrentXmlElement().addChildElement( element );
399                 setCurrentXmlElement( element );
400                 newClade();
401             }
402             else if ( local_name.equals( PhyloXmlMapping.PHYLOGENY ) ) {
403                 setCurrentXmlElement( new XmlElement( "", "", "", null ) );
404                 newPhylogeny();
405                 final XmlElement element = new XmlElement( namespace_uri, local_name, local_name, attributes );
406                 if ( element.isHasAttribute( PhyloXmlMapping.PHYLOGENY_IS_REROOTABLE_ATTR ) ) {
407                     getCurrentPhylogeny().setRerootable( Boolean.parseBoolean( element
408                             .getAttribute( PhyloXmlMapping.PHYLOGENY_IS_REROOTABLE_ATTR ) ) );
409                 }
410                 if ( element.isHasAttribute( PhyloXmlMapping.PHYLOGENY_BRANCHLENGTH_UNIT_ATTR ) ) {
411                     getCurrentPhylogeny()
412                             .setDistanceUnit( element.getAttribute( PhyloXmlMapping.PHYLOGENY_BRANCHLENGTH_UNIT_ATTR ) );
413                 }
414                 if ( element.isHasAttribute( PhyloXmlMapping.PHYLOGENY_IS_ROOTED_ATTR ) ) {
415                     getCurrentPhylogeny().setRooted( Boolean.parseBoolean( element
416                             .getAttribute( PhyloXmlMapping.PHYLOGENY_IS_ROOTED_ATTR ) ) );
417                 }
418                 if ( element.isHasAttribute( PhyloXmlMapping.PHYLOGENY_TYPE_ATTR ) ) {
419                     getCurrentPhylogeny().setType( ( element.getAttribute( PhyloXmlMapping.PHYLOGENY_TYPE_ATTR ) ) );
420                 }
421             }
422             else if ( local_name.equals( PHYLOXML ) ) {
423             }
424             else if ( getCurrentPhylogeny() != null ) {
425                 final XmlElement element = new XmlElement( namespace_uri, local_name, local_name, attributes );
426                 getCurrentXmlElement().addChildElement( element );
427                 setCurrentXmlElement( element );
428             }
429         }
430     }
431
432     public static boolean attributeEqualsValue( final XmlElement element,
433                                                 final String attributeName,
434                                                 final String attributeValue ) {
435         final String attr = element.getAttribute( attributeName );
436         return ( ( attr != null ) && attr.equals( attributeValue ) );
437     }
438
439     public static String getAtttributeValue( final XmlElement element, final String attributeName ) {
440         final String attr = element.getAttribute( attributeName );
441         if ( attr != null ) {
442             return attr;
443         }
444         else {
445             return "";
446         }
447     }
448
449     static public Map<String, Sequence> getSequenceMapByIdForPhylogeny( final Phylogeny ph ) {
450         HashMap<String, Sequence> seqMap = phylogenySequencesById.get( ph );
451         if ( seqMap == null ) {
452             seqMap = new HashMap<String, Sequence>();
453             phylogenySequencesById.put( ph, seqMap );
454         }
455         return seqMap;
456     }
457
458     private static void mapElementToPhylogeny( final XmlElement xml_element, final Phylogeny phylogeny )
459             throws PhylogenyParserException, PhyloXmlDataFormatException {
460         for( int i = 0; i < xml_element.getNumberOfChildElements(); ++i ) {
461             final XmlElement element = xml_element.getChildElement( i );
462             final String qualified_name = element.getQualifiedName();
463             if ( qualified_name.equals( PhyloXmlMapping.PHYLOGENY_NAME ) ) {
464                 phylogeny.setName( element.getValueAsString() );
465             }
466             else if ( qualified_name.equals( PhyloXmlMapping.PHYLOGENY_DESCRIPTION ) ) {
467                 phylogeny.setDescription( element.getValueAsString() );
468             }
469             else if ( qualified_name.equals( PhyloXmlMapping.IDENTIFIER ) ) {
470                 phylogeny.setIdentifier( ( Identifier ) IdentifierParser.getInstance().parse( element ) );
471             }
472             else if ( qualified_name.equals( PhyloXmlMapping.CONFIDENCE ) ) {
473                 phylogeny.setConfidence( ( Confidence ) ConfidenceParser.getInstance().parse( element ) );
474             }
475         }
476     }
477 }