/*
 * $Id: EventProducer.java,v 1.1.1.1 2002/09/30 15:08:52 smartine Exp $
 * Copyright (C) 1999-2000 David Brownell
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package xml.pipeline;

import java.io.IOException;

import org.xml.sax.*;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.ext.*;


// $Id: EventProducer.java,v 1.1.1.1 2002/09/30 15:08:52 smartine Exp $

/**
 * This class describes a kind of "producer" end of a SAX event pipeline,
 * which sends all the event callbacks for one "document" to a "consumer".
 * Producers are first set up with one input document; they parse it, and
 * send its events to their consumer.  Unless a specific parser is provided,
 * parsing is done with the system default XML parser.  That parser normally
 * does not validate its inputs.
 *
 * <p> Consumers may be arbitrarily complex pipeline networks, branching and
 * joining as required by the application module of which they are a part.
 * Producers may later be given additional input documents to parse, which
 * they'll do without rebinding handlers.  (That is, all setup overhead is
 * avoided starting with the second use of that pipeline.)
 *
 * <p>In that way, simple "one shot" processing pipelines are a natural
 * lead-in to more complex ones accepting multiple documents, as part of
 * work flow or other XML processing systems.
 *
 * <p> Three chunks of SAX2 configuration are here coupled to the parser
 * setup:  error handler, diagnostic locale, and entity resolution.
 * Everything relating to the document itself is sent to a consumer.
 * Consumers automatically have access to the parser's error handler,
 * promoting consistency in error reporting and handling mechanisms.
 *
 * <p> Note that event pipelines can be used directly, without needing
 * any kind of EventProducer.  The producer abstraction is primarily used
 * to group XML "Document" level abstractions into chunks which can be
 * processed together.  When working with finer grained abstractions, such
 * as application components which will be combined into a "document",
 * application modules may find it preferable to work directly with the
 * event consumption end of a pipeline.
 *
 * @author David Brownell
 * @version $Date: 2002/09/30 15:08:52 $
 */
public class EventProducer
{
    static final String FEATURE = "http://xml.org/sax/features/";
    static final String HANDLER = "http://xml.org/sax/properties/";

    //
    // These are the names of the SAX2 handlers we'll try to pass to the
    // parser.  For the moment they're just the extensions defined by SAX2;
    // others can be defined by anyone at any time.  The list should be
    // in some administerable resource.
    //
    private static final String	handlerIds [] = {
	HANDLER + "declaration-handler",
	HANDLER + "lexical-handler"
    };


    private XMLReader		parser;
    private InputSource		input;

    /**
     * Uses the specified URI to build an event producer from the system
     * default SAX parser, in its default configuration, and that URI.
     */
    public EventProducer (String uri)
    throws SAXException
    {
	this (new InputSource (uri));
    }

    /**
     * Builds an event producer from the system default SAX
     * parser, in its default configuration, and the specified
     * input source.
     */
    public EventProducer (InputSource i)
    throws SAXException
    {
	try {
	    parser = XMLReaderFactory.createXMLReader ();
	} catch (Exception e) {
	    // would prefer to have the factory be
	    // fully responsible for not failing
	    parser = XMLReaderFactory.createXMLReader (
		"xml.aelfred2.SAXDriver"
		);
	    
	    // NOTE:  no SAX1 parser fallback for now.
	}
	input = i;
    }

    /**
     * Builds an event producer from the specified parser, in its current
     * configuration, and the specified input source.  The handlers will
     * generally be changed from the configuration provided here, if the
     * event consumer provides a value for that handler.
     *
     * @param p The parser to use; this should be configured
     *	already with an EntityResolver, ErrorHandler, and Locale.
     */
    public EventProducer (XMLReader p, InputSource i)
    {
	parser = p;
	input = i;
    }

    final public void setFeature (String id, boolean value)
    throws SAXException
    {
	if (parser == null)
	    throw new SAXException ("no parser!");
	parser.setFeature (id, value);
    }

    final public boolean getFeature (String id)
    throws SAXException
    {
	if (parser == null)
	    throw new SAXException ("no parser!");
	return parser.getFeature (id);
    }


    /**
     * Provides the specified error handler to the parser; it will later
     * be provided to the event consumer that processes parser events.
     */
    final public void setErrorHandler (ErrorHandler h)
    {
	parser.setErrorHandler (h);
    }

    /**
     * Provides the specified entity resolver to the parser.
     */
    final public void setEntityResolver (EntityResolver r)
    {
	parser.setEntityResolver (r);
    }

    /**
     * Configures the event producer to send events to the specified event
     * consumer, and invokes parseDocument() with the initial input source.
     * After this returns, the producer is still configured to use that
     * consumer (as when this method was called -- subsequent changes are
     * ignored) and parseDocument may be invoked to send parsing events
     * for additional XML documents.
     *
     * <p> Certain kinds of event filters are recognized, and can be optimized
     * out by setting appropriate parser options.  {@link NSFilter} is one
     * such filter; if it is at the front of a pipeline, a producer wrapping
     * a SAX parser may be able to remove it and improve overall performance
     * by requesting that parsers not discard such information, rather than
     * attempting a costly (and potentially error prone) reconstruction.
     *
     * @see PipelineFactory
     */
    public void produce (EventConsumer out)
    throws SAXException, IOException
    {
	try {
	    if (parser == null)
		throw new IllegalStateException ("producer can't produce");

	    //
	    // We can optimize out the need for some filters by toggling
	    // parser feature flags.  If we can, do so.  (This can be
	    // disabled by sticking a null filter on, e.g. an EventFilter
	    // will pass things straight through with no runtime costs.)
	    //
	    for (;;) {
		// undo rude "namespace-prefixes=false" behavior
		if (out instanceof NSFilter) {
		    try {
			setFeature (FEATURE + "namespace-prefixes", true);
			out = ((NSFilter) out).getNext ();
		    } catch (Exception e) {
			// we can't do this one
			break;
		    }

		// make sure the event structure is XML-valid
		} else if (out instanceof ValidationConsumer) {
		    try {
			setFeature (FEATURE + "validation", true);
			out = ((ValidationConsumer) out).getNext ();
		    } catch (Exception e) {
			// we can't do this one
			break;
		    }

		// generic pipelines might defend against buggy application
		// producers; SAX parsers are required not to have such bugs
		} else if (out instanceof WellFormednessFilter) {
		    out = ((WellFormednessFilter) out).getNext ();

		// if we don't recognize the filter, we can't optimize it out.
		} else
		    break;
	    }

	    if (out != null) {
		// Some parsers can't deal with null handlers; sigh.
		ContentHandler	ch = out.getContentHandler ();
		DTDHandler	dh = out.getDTDHandler ();

		if (ch != null)
		    parser.setContentHandler (ch);
		if (dh != null)
		    parser.setDTDHandler (dh);

		for (int i = 0; i < handlerIds.length; i++) {
		    try {
			String	name = handlerIds [i];
			Object	property;

			property = out.getProperty (name);
			if (property != null)
			    parser.setProperty (name, property);
		    } catch (Exception e) {
			/* ignore */
		    }
		}

		// pipeline stages should use the same error handler
		out.setErrorHandler (parser.getErrorHandler ());

	    } // else it's likely being parsed just for error reports

	    parseDocument ();
	} finally {
	    input = null;
	}
    }

    /*
     * Not for application use -- parses the saved input.
     * Overridden by DomParser subclass.
     */
    // package private
    void parseDocument ()
    throws SAXException, IOException
    {
	parser.parse (input);
    }


    /**
     * Start firing events to the next stage in the pipeline, returning
     * when the specified inputSource is finished.
     */
    public void parseDocument (InputSource source)
    throws SAXException, IOException
    {
	if (source == null)
	    throw new IllegalStateException ("no input data");

	parser.parse (source);
    }
}
