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