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