001/* ========================================================================
002 * JCommon : a free general purpose class library for the Java(tm) platform
003 * ========================================================================
004 *
005 * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006 * 
007 * Project Info:  http://www.jfree.org/jcommon/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it 
010 * under the terms of the GNU Lesser General Public License as published by 
011 * the Free Software Foundation; either version 2.1 of the License, or 
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but 
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
022 * USA.  
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
025 * in the United States and other countries.]
026 * 
027 * --------------------
028 * ReportGenerator.java
029 * --------------------
030 * (C)opyright 2002-2005, by Thomas Morgner and Contributors.
031 *
032 * Original Author:  Thomas Morgner (taquera@sherito.org);
033 * Contributor(s):   David Gilbert (for Object Refinery Limited);
034 *
035 * $Id: ParserFrontend.java,v 1.8 2005/11/14 10:58:19 mungady Exp $
036 *
037 * Changes
038 * -------
039 * 10-May-2002 : Initial version
040 * 12-Dec-2002 : Fixed issues reported by Checkstyle (DG);
041 * 29-Apr-2003 : Distilled from the JFreeReport project and moved into JCommon
042 *
043 */
044
045package org.jfree.xml;
046
047import java.io.BufferedInputStream;
048import java.io.IOException;
049import java.net.URL;
050import javax.xml.parsers.ParserConfigurationException;
051import javax.xml.parsers.SAXParser;
052import javax.xml.parsers.SAXParserFactory;
053
054import org.jfree.util.Log;
055import org.xml.sax.EntityResolver;
056import org.xml.sax.InputSource;
057import org.xml.sax.SAXException;
058import org.xml.sax.XMLReader;
059
060/**
061 * The reportgenerator initializes the parser and provides an interface
062 * the the default parser.
063 *
064 * To create a report from an URL, use
065 * <code>
066 * ReportGenerator.getInstance().parseReport (URL myURl, URL contentBase);
067 * </code>
068 *
069 * @author Thomas Morgner
070 */
071public class ParserFrontend {
072
073    /** The report handler. */
074    private FrontendDefaultHandler defaulthandler;
075
076    /** The parser factory. */
077    private SAXParserFactory factory;
078
079    /** The DTD. */
080    private EntityResolver entityResolver;
081
082    /** A flag indicating whether to use a DTD to validate the xml input. */
083    private boolean validateDTD;
084
085    /**
086     * Creates a new report generator. The generator uses the singleton pattern by default,
087     * so use generator.getInstance() to get the generator.
088     *
089     * @param parser the parser that is used to coordinate the parsing process.
090     */
091    protected ParserFrontend(final FrontendDefaultHandler parser) {
092        if (parser == null) {
093            throw new NullPointerException();
094        }
095        this.defaulthandler = parser;
096    }
097
098    /**
099     * Returns <code>true</code> if the report definition should be validated against the
100     * DTD, and <code>false</code> otherwise.
101     *
102     * @return A boolean.
103     */
104    public boolean isValidateDTD() {
105        return this.validateDTD;
106    }
107
108    /**
109     * Sets a flag that controls whether or not the report definition is validated
110     * against the DTD.
111     *
112     * @param validateDTD  the flag.
113     */
114    public void setValidateDTD(final boolean validateDTD) {
115        this.validateDTD = validateDTD;
116    }
117
118    /**
119     * Returns the entity resolver.
120     *
121     * @return The entity resolver.
122     */
123    public EntityResolver getEntityResolver() {
124        return this.entityResolver;
125    }
126
127    /**
128     * Sets the entity resolver.
129     *
130     * @param entityResolver  the entity resolver.
131     */
132    public void setEntityResolver(final EntityResolver entityResolver) {
133        this.entityResolver = entityResolver;
134    }
135
136    /**
137     * Returns a SAX parser.
138     *
139     * @return a SAXParser.
140     *
141     * @throws ParserConfigurationException if there is a problem configuring the parser.
142     * @throws SAXException if there is a problem with the parser initialisation
143     */
144    protected SAXParser getParser() throws ParserConfigurationException, SAXException {
145        if (this.factory == null) {
146            this.factory = SAXParserFactory.newInstance();
147            if (isValidateDTD()) {
148                try {
149                    // dont touch the validating feature, if not needed ..
150                    this.factory.setValidating(true);
151                }
152                catch (Exception ex) {
153                    // the parser does not like the idea of validating ...
154                    Log.debug("The parser will not validate the xml document.", ex);
155                }
156            }
157        }
158        return this.factory.newSAXParser();
159    }
160
161    /**
162     * Sets the default handler used for parsing reports. This handler is used to
163     * initiate parsing.
164     *
165     * @param handler  the handler.
166     */
167    public void setDefaultHandler(final FrontendDefaultHandler handler) {
168        if (handler == null) {
169            throw new NullPointerException();
170        }
171        this.defaulthandler = handler;
172    }
173
174    /**
175     * Returns the ElementDefinitionHandler used for parsing reports.
176     *
177     * @return the report handler.
178     */
179    public FrontendDefaultHandler getDefaultHandler() {
180        return this.defaulthandler;
181    }
182
183    /**
184     * Creates a new instance of the currently set default handler and sets the contentbase
185     * for the handler to <code>contentBase</code>.
186     *
187     * @param contentBase  the content base.
188     *
189     * @return the report handler.
190     */
191    protected FrontendDefaultHandler createDefaultHandler(final URL contentBase) {
192        final FrontendDefaultHandler handler = getDefaultHandler().newInstance();
193        if (contentBase != null) {
194            handler.setConfigProperty(Parser.CONTENTBASE_KEY, contentBase.toExternalForm());
195        }
196        return handler;
197    }
198
199    /**
200     * Parses an XML report template file.
201     *
202     * @param input  the input source.
203     * @param contentBase  the content base.
204     *
205     * @return the report.
206     *
207     * @throws ElementDefinitionException if an error occurred.
208     */
209    protected Object parse(final InputSource input, final URL contentBase)
210        throws ElementDefinitionException {
211        try {
212            final SAXParser parser = getParser();
213            final XMLReader reader = parser.getXMLReader();
214
215            try {
216                reader.setFeature("http://xml.org/sax/features/validation", isValidateDTD());
217            }
218            catch (SAXException se) {
219                Log.debug("The XMLReader will not validate the xml document.", se);
220            }
221            final FrontendDefaultHandler handler = createDefaultHandler(contentBase);
222            configureReader(reader, handler);
223            try {
224                reader.setContentHandler(handler);
225                reader.setDTDHandler(handler);
226                if (getEntityResolver() != null) {
227                    reader.setEntityResolver(getEntityResolver());
228                }
229                reader.setErrorHandler(handler);
230                reader.parse(input);
231                return handler.getResult();
232            }
233            catch (IOException e) {
234                throw new ElementDefinitionException(e);
235            }
236        }
237        catch (ParserConfigurationException e) {
238            throw new ElementDefinitionException(e);
239        }
240        catch (SAXException e) {
241            throw new ElementDefinitionException(e);
242        }
243    }
244
245    /**
246     * Configures the xml reader. Use this to set features or properties
247     * before the documents get parsed.
248     *
249     * @param handler the parser implementation that will handle the SAX-Callbacks.
250     * @param reader the xml reader that should be configured.
251     */
252    protected void configureReader(final XMLReader reader, final FrontendDefaultHandler handler) {
253        try {
254            reader.setProperty
255                ("http://xml.org/sax/properties/lexical-handler", handler.getCommentHandler());
256        }
257        catch (SAXException se) {
258            Log.debug("Comments are not supported by this SAX implementation.");
259        }
260    }
261
262    /**
263     * Parses an XML file which is loaded using the given URL. All
264     * needed relative file- and resourcespecification are loaded
265     * using the URL <code>contentBase</code> as base.
266     * <p>
267     * After the report is generated, the ReportDefinition-source and the contentbase are
268     * stored as string in the reportproperties.
269     *
270     * @param file  the URL for the report template file.
271     * @param contentBase  the URL for the report template content base.
272     *
273     * @return the parsed report.
274     *
275     * @throws IOException if an I/O error occurs.
276     * @throws ElementDefinitionException if there is a problem parsing the report template.
277     */
278    public Object parse(final URL file, final URL contentBase)
279        throws ElementDefinitionException, IOException {
280        if (file == null) {
281            throw new NullPointerException("File may not be null");
282        }
283
284        final BufferedInputStream bin = new BufferedInputStream(file.openStream());
285        final InputSource in = new InputSource(bin);
286        in.setSystemId(file.toString());
287        final Object result = parse(in, contentBase);
288        bin.close();
289        return result;
290    }
291
292}