initial commit
[jalview.git] / forester / java / src / org / forester / io / parsers / phyloxml / PhyloXmlParser.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.io.File;
29 import java.io.FileReader;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.io.InputStreamReader;
33 import java.io.Reader;
34 import java.io.StringReader;
35 import java.net.URL;
36 import java.util.Date;
37 import java.util.Enumeration;
38 import java.util.zip.ZipEntry;
39 import java.util.zip.ZipFile;
40 import java.util.zip.ZipInputStream;
41
42 import javax.xml.parsers.ParserConfigurationException;
43 import javax.xml.parsers.SAXParser;
44 import javax.xml.parsers.SAXParserFactory;
45
46 import org.forester.io.parsers.PhylogenyParser;
47 import org.forester.io.parsers.util.PhylogenyParserException;
48 import org.forester.phylogeny.Phylogeny;
49 import org.forester.util.ForesterConstants;
50 import org.forester.util.ForesterUtil;
51 import org.xml.sax.InputSource;
52 import org.xml.sax.SAXException;
53 import org.xml.sax.SAXNotRecognizedException;
54 import org.xml.sax.SAXNotSupportedException;
55 import org.xml.sax.SAXParseException;
56 import org.xml.sax.XMLReader;
57 import org.xml.sax.helpers.DefaultHandler;
58
59 public class PhyloXmlParser implements PhylogenyParser {
60
61     final public static String   JAXP_SCHEMA_LANGUAGE                       = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
62     final public static String   W3C_XML_SCHEMA                             = "http://www.w3.org/2001/XMLSchema";
63     final public static String   JAXP_SCHEMA_SOURCE                         = "http://java.sun.com/xml/jaxp/properties/schemaSource";
64     final public static String   SAX_FEATURES_VALIDATION                    = "http://xml.org/sax/features/validation";
65     final public static String   APACHE_FEATURES_VALIDATION_SCHEMA          = "http://apache.org/xml/features/validation/schema";
66     final public static String   APACHE_FEATURES_VALIDATION_SCHEMA_FULL     = "http://apache.org/xml/features/validation/schema-full-checking";
67     final public static String   APACHE_PROPERTIES_SCHEMA_EXTERNAL_LOCATION = "http://apache.org/xml/properties/schema/external-schemaLocation";
68     final static private boolean TIME                                       = false;
69     private Object               _source;
70     private boolean              _valid;
71     private boolean              _zipped_inputstream;
72     private int                  _error_count;
73     private int                  _warning_count;
74     private String               _schema_location;
75     private StringBuffer         _error_messages;
76     private StringBuffer         _warning_messages;
77
78     public PhyloXmlParser() {
79         init();
80         reset();
81     }
82
83     public int getErrorCount() {
84         return _error_count;
85     }
86
87     public StringBuffer getErrorMessages() {
88         return _error_messages;
89     }
90
91     private Reader getReaderFromZipFile() throws IOException {
92         Reader reader = null;
93         final ZipFile zip_file = new ZipFile( getSource().toString() );
94         final Enumeration<?> zip_file_entries = zip_file.entries();
95         while ( zip_file_entries.hasMoreElements() ) {
96             final ZipEntry zip_file_entry = ( ZipEntry ) zip_file_entries.nextElement();
97             if ( !zip_file_entry.isDirectory() && ( zip_file_entry.getSize() > 0 ) ) {
98                 final InputStream is = zip_file.getInputStream( zip_file_entry );
99                 reader = new InputStreamReader( is );
100                 break;
101             }
102         }
103         return reader;
104     }
105
106     private String getSchemaLocation() {
107         return _schema_location;
108     }
109
110     private Object getSource() {
111         return _source;
112     }
113
114     public int getWarningCount() {
115         return _warning_count;
116     }
117
118     public StringBuffer getWarningMessages() {
119         return _warning_messages;
120     }
121
122     private void init() {
123         setZippedInputstream( false );
124     }
125
126     public boolean isValid() {
127         return _valid;
128     }
129
130     private boolean isZippedInputstream() {
131         return _zipped_inputstream;
132     }
133
134     public Phylogeny[] parse() throws IOException, PhylogenyParserException {
135         reset();
136         final PhyloXmlHandler handler = new PhyloXmlHandler();
137         final SAXParserFactory factory = SAXParserFactory.newInstance();
138         factory.setNamespaceAware( true );
139         try {
140             if ( !ForesterUtil.isEmpty( getSchemaLocation() ) ) {
141                 factory.setFeature( SAX_FEATURES_VALIDATION, true );
142                 factory.setFeature( APACHE_FEATURES_VALIDATION_SCHEMA, true );
143                 factory.setFeature( APACHE_FEATURES_VALIDATION_SCHEMA_FULL, true );
144             }
145         }
146         catch ( final SAXNotRecognizedException e ) {
147             e.printStackTrace();
148             throw new PhylogenyParserException( "sax not recognized exception: " + e.getLocalizedMessage() );
149         }
150         catch ( final SAXNotSupportedException e ) {
151             e.printStackTrace();
152             throw new PhylogenyParserException( "sax not supported exception: " + e.getLocalizedMessage() );
153         }
154         catch ( final ParserConfigurationException e ) {
155             e.printStackTrace();
156             throw new PhylogenyParserException( "parser configuration exception: " + e.getLocalizedMessage() );
157         }
158         catch ( final Exception e ) {
159             e.printStackTrace();
160             throw new PhylogenyParserException( "error while configuring sax parser: " + e.getLocalizedMessage() );
161         }
162         try {
163             final SAXParser parser = factory.newSAXParser();
164             if ( !ForesterUtil.isEmpty( getSchemaLocation() ) ) {
165                 parser.setProperty( JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA );
166                 parser.setProperty( JAXP_SCHEMA_SOURCE, getSchemaLocation() );
167                 parser.setProperty( APACHE_PROPERTIES_SCHEMA_EXTERNAL_LOCATION, getSchemaLocation() );
168             }
169             final XMLReader xml_reader = parser.getXMLReader();
170             xml_reader.setContentHandler( handler );
171             xml_reader.setErrorHandler( new PhyloXmlParserErrorHandler() );
172             long start_time = 0;
173             if ( TIME ) {
174                 start_time = new Date().getTime();
175             }
176             if ( getSource() instanceof File ) {
177                 if ( !getSource().toString().toLowerCase().endsWith( ".zip" ) ) {
178                     xml_reader.parse( new InputSource( new FileReader( ( File ) getSource() ) ) );
179                 }
180                 else {
181                     final Reader reader = getReaderFromZipFile();
182                     if ( reader == null ) {
183                         throw new PhylogenyParserException( "zip file \"" + getSource()
184                                 + "\" appears not to contain any entries" );
185                     }
186                     xml_reader.parse( new InputSource( reader ) );
187                 }
188             }
189             else if ( getSource() instanceof InputSource ) {
190                 xml_reader.parse( ( InputSource ) getSource() );
191             }
192             else if ( getSource() instanceof InputStream ) {
193                 if ( !isZippedInputstream() ) {
194                     final InputStream is = ( InputStream ) getSource();
195                     final Reader reader = new InputStreamReader( is );
196                     xml_reader.parse( new InputSource( reader ) );
197                 }
198                 else {
199                     final ZipInputStream zip_is = new ZipInputStream( ( InputStream ) getSource() );
200                     zip_is.getNextEntry();
201                     final Reader reader = new InputStreamReader( zip_is );
202                     if ( reader == null ) {
203                         throw new PhylogenyParserException( "zip input stream \"" + getSource()
204                                 + "\" appears not to contain any (phyloXML) data" );
205                     }
206                     xml_reader.parse( new InputSource( reader ) );
207                 }
208             }
209             else if ( getSource() instanceof String ) {
210                 final File file = new File( getSource().toString() );
211                 final Reader reader = new FileReader( file );
212                 xml_reader.parse( new InputSource( reader ) );
213             }
214             else if ( getSource() instanceof StringBuffer ) {
215                 final StringReader string_reader = new StringReader( getSource().toString() );
216                 xml_reader.parse( new InputSource( string_reader ) );
217             }
218             else {
219                 throw new PhylogenyParserException( "phyloXML parser: attempt to parse object of unsupported type: \""
220                         + getSource().getClass() + "\"" );
221             }
222             if ( TIME ) {
223                 System.out.println( "[TIME] phyloXML parsing: " + ( new Date().getTime() - start_time ) + "ms." );
224             }
225         }
226         catch ( final SAXException sax_exception ) {
227             throw new PhylogenyParserException( "failed to parse [" + getSource() + "]: "
228                     + sax_exception.getLocalizedMessage() );
229         }
230         catch ( final ParserConfigurationException parser_config_exception ) {
231             throw new PhylogenyParserException( "failed to parse [" + getSource()
232                     + "]. Problem with XML parser configuration: " + parser_config_exception.getLocalizedMessage() );
233         }
234         catch ( final IOException e ) {
235             throw new PhylogenyParserException( "problem with input source: " + e.getLocalizedMessage() );
236         }
237         catch ( final Exception e ) {
238             throw new PhylogenyParserException( e.getLocalizedMessage() );
239         }
240         catch ( final Error err ) {
241             err.printStackTrace();
242             throw new PhylogenyParserException( "severe error: " + err.getLocalizedMessage() );
243         }
244         final Phylogeny[] ps = new Phylogeny[ handler.getPhylogenies().size() ];
245         int i = 0;
246         for( final Phylogeny phylogeny : handler.getPhylogenies() ) {
247             ps[ i++ ] = phylogeny;
248         }
249         return ps;
250     }
251
252     private void reset() {
253         _valid = true;
254         _error_count = 0;
255         _warning_count = 0;
256         _error_messages = new StringBuffer();
257         _warning_messages = new StringBuffer();
258     }
259
260     public void setSource( final Object source ) {
261         _source = source;
262     }
263
264     public void setValidateAgainstSchema( final String schema_location ) {
265         _schema_location = schema_location;
266     }
267
268     public void setZippedInputstream( final boolean zipped_inputstream ) {
269         _zipped_inputstream = zipped_inputstream;
270     }
271
272     public static PhyloXmlParser createPhyloXmlParserXsdValidating() {
273         final PhyloXmlParser xml_parser = new PhyloXmlParser();
274         final ClassLoader cl = PhyloXmlParser.class.getClassLoader();
275         final URL xsd_url = cl.getResource( ForesterConstants.LOCAL_PHYLOXML_XSD_RESOURCE );
276         if ( xsd_url != null ) {
277             xml_parser.setValidateAgainstSchema( xsd_url.toString() );
278         }
279         else {
280             throw new RuntimeException( "failed to get URL for phyloXML XSD from jar file from ["
281                     + ForesterConstants.LOCAL_PHYLOXML_XSD_RESOURCE + "]" );
282         }
283         return xml_parser;
284     }
285
286     private class PhyloXmlParserErrorHandler extends DefaultHandler {
287
288         @Override
289         public void error( final SAXParseException e ) {
290             ++_error_count;
291             _valid = false;
292             throw new PhyloXmlException( "phyloXML error at line " + e.getLineNumber() + ": \n"
293                     + e.getLocalizedMessage() );
294         }
295
296         @Override
297         public void fatalError( final SAXParseException e ) {
298             ++_error_count;
299             _valid = false;
300             throw new PhyloXmlException( "fatal XML error at line " + e.getLineNumber() + ": \n"
301                     + e.getLocalizedMessage() );
302         }
303
304         @Override
305         public void warning( final SAXParseException e ) {
306             ++_warning_count;
307             if ( _error_messages.length() > 1 ) {
308                 _error_messages.append( ForesterUtil.LINE_SEPARATOR );
309             }
310             _warning_messages.append( "[line: " + e.getLineNumber() + "] " + e.getMessage() );
311         }
312     }
313 }