/*
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 Bull S.A.
 * Contact: jonas-team@objectweb.org
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * Initial developer(s): ____________________________________.
 * Contributor(s): Lutris Technologies Inc http://www.lutris.com
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.objectweb.jonas_ejb.deployment.api;

import org.xml.sax.*;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 *  This class provides support for complete XML parsing support including DTD
 *  location resolution and XML parsing errors. It allows parsing XML files with
 * are without DOCTYPE and with DTDs that can be found from the classpath.
 *
 * @author Brett McLaughlin
 * @author Christophe Ney
 */
public class SAXParserHelper implements ErrorHandler, EntityResolver {

    /** flag indicating if a DTD has been found in the document **/
    private boolean resolveEntityCalled = false;

    /** reference to an external <code>ErrorHandler</code> (default handler is used when null) **/
    private ErrorHandler errorHandler;

    /** reference to an external <code>EntityResolver</code> (default resolver is used when null) **/
    private EntityResolver entityResolver;

    /** hold the value of the <code>SAXParseException</code> when thrown **/
    private SAXParseException exception = null;


    /**
     * constructor for use of external ErrorHandler and EntityResolver.
     * It is important to note that a new helper instance should be used for each
     * new parsing. This ensure a correct value of  <code>resolveEntityCalled<code>
     * @param errorHandler the Errorhandler to delegate to
     * @param entityResolver the EntityResolver to delegate to
     */
    public SAXParserHelper(ErrorHandler errorHandler, EntityResolver entityResolver) {
        this.errorHandler = errorHandler;
        this.entityResolver = entityResolver;
    }

    /**
     * constructor using the default ErrorHandler and EntityResolver
     */
    public SAXParserHelper() {
        this(null,null);
    }

    /**
     * set the ErrorHandler to be used by the helper
     */
    public void setErrorHandler(ErrorHandler errorHandler) {
        this.errorHandler = errorHandler;
    }

    /**
     * set the EntityResolver to be used by the helper
     */
    public void setEntityResolver(EntityResolver entityResolver) {
        this.entityResolver = entityResolver;
    }

    /**
     * Return a wrapped ErrorHandler for use by the parser.
     * This allows storing the SAXException for precise line:column error reporting
     */
    public ErrorHandler getWrappedErrorHandler() {
        return this;
    }

    /**
     * Return a wrapped EntityResolver for use by the parser.
     * This is done to enable grammar error handling only when the DTD is present
     */
    public EntityResolver getWrappedEntityResolver() {
        return this;
    }

    /**
     *  Delegate warning callbacks to the errorHandler or ingnore them.
     * @param e the <code>SAXParseException</code> that caused the warning.
     */
    public void warning(SAXParseException e) throws SAXException {
        try {
            if (errorHandler!=null) errorHandler.warning(e);
            else return; // Ignore - warnings are not reported
        } catch(SAXParseException ex){
            exception=ex;
            throw ex;
        }
    }

    /**
     *  If a DTD has been found, this will delegate error callbacks to the
     * errorHandler or rethrow them. The exception is stored when a
     * SAXParseException is thrown.
     *
     * @param e the <code>SAXParseException</code> that caused the error.
     */
    public void error(SAXParseException e) throws SAXException {
        if (resolveEntityCalled) {
            try {
                if (errorHandler!=null) errorHandler.error(e);
                else throw e;
            } catch(SAXParseException ex){
                exception=ex;
                throw ex;
            }
        }
    }

    /**
     *  Delegate fatalerror callbacks to the errorHandler or rethrow them.
     * The exception is stored when a SAXParseException is thrown.
     *
     * @param e the <code>SAXParseException</code> that caused the fatal error.
     */
    public void fatalError(SAXParseException e) throws SAXException {
        try {
            if (errorHandler!=null) errorHandler.fatalError(e);
            else throw e;
        } catch(SAXParseException ex){
            exception=ex;
            throw ex;
        }
    }

    /**
     * Retreive the previously thrown SAXParseException.
     * @return SAXParseException previously thrown or null if not found
     */
    public SAXParseException getSAXParseException() {
        return exception;
    }

    /**
     * Method used by the parser to read the DTD specified in DOCTYPE.
     * This delegates to the  <code>entityResolver</code> if set. Otherwise,
     * the default behaviour is as follow:<p>
     * The <code>filename</code> of the systemID parameter (which matches
     * to the filename of the PUBLIC url or SYSTEM path) is used as
     * a name for loading the DTD as a classloader resource.
     * @param publicID the name of DOCTYPE SYSTEM
     * @param systemID the url of DOCTYPE PUBLIC or path of DOCTYPE SYSTEM
     */
    public InputSource resolveEntity(String publicID, String systemID)
            throws IOException, SAXException {
        resolveEntityCalled = true;
        if (entityResolver!=null) {
            return entityResolver.resolveEntity(publicID,systemID);
        } else {
            try {
                String resource =
                        systemID.substring(systemID.lastIndexOf('/') + 1);
                ClassLoader classloader =
                        Thread.currentThread().getContextClassLoader();
                InputStream in = classloader.getResourceAsStream(resource);
                return new InputSource(in);
            } catch (Throwable t) {
                // Try and recover through normal processing
                return null;
            }
        }
    }
}
