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