/* EventFilter.java --
   Copyright (C) 1999,2000,2001 Free Software Foundation, Inc.

This file is part of GNU Classpath.

GNU Classpath 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, or (at your option)
any later version.

GNU Classpath 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 GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */

package gnu.xml.pipeline;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

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

/**
 * A customizable event consumer, used to assemble various kinds of filters
 * using SAX handlers and an optional second consumer.  It can be constructed
 * in two ways: <ul>
 *
 *  <li> To serve as a passthrough, sending all events to a second consumer.
 *  The second consumer may be identified through {@link #getNext}.
 *
 *  <li> To serve as a dead end, with all handlers null;
 *  {@link #getNext} returns null.
 *
 * </ul>
 *
 * <p> Additionally, SAX handlers may be assigned, which completely replace
 * the "upstream" view (through {@link EventConsumer}) of handlers, initially
 * null or the "next" consumer provided to the constructor.  To make
 * it easier to build specialized filter classes, this class implements
 * all the standard SAX consumer handlers, and those implementations
 * delegate "downstream" to the consumer accessed by {@link #getNext}.
 *
 * <p> The simplest way to create a custom a filter class is to create a
 * subclass which overrides one or more handler interface methods.  The
 * constructor for that subclass then registers itself as a handler for
 * those interfaces using a call such as <em>setContentHandler(this)</em>,
 * so the "upstream" view of event delivery is modified from the state
 * established in the base class constructor.  That way,
 * the overridden methods intercept those event callbacks
 * as they go "downstream", and
 * all other event callbacks will pass events to any next consumer.
 * Overridden methods may invoke superclass methods (perhaps after modifying
 * parameters) if they wish to delegate such calls.  Such subclasses
 * should use {@link #getErrorHandler} to report errors using the
 * common error reporting mechanism.
 *
 * <p> Another important technique is to construct a filter consisting
 * of only a few specific types of handler.  For example, one could easily
 * prune out lexical events or various declarations by providing handlers
 * which don't pass those events downstream, or by providing null handlers.
 *
 * <hr />
 *
 * <p> This may be viewed as the consumer oriented analogue of the SAX2
 * {@link org.xml.sax.helpers.XMLFilterImpl XMLFilterImpl} class.
 * Key differences include: <ul>
 *
 *      <li> This fully separates consumer and producer roles:  it
 *      does not implement the producer side <em>XMLReader</em> or
 *      <em>EntityResolver</em> interfaces, so it can only be used
 *      in "push" mode (it has no <em>parse()</em> methods).
 *
 *      <li> "Extension" handlers are fully supported, enabling a
 *      richer set of application requirements.
 *      And it implements {@link EventConsumer}, which groups related
 *      consumer methods together, rather than leaving them separated.
 *
 *      <li> The chaining which is visible is "downstream" to the next
 *      consumer, not "upstream" to the preceding producer.
 *      It supports "fan-in", where
 *      a consumer can be fed by several producers.  (For "fan-out",
 *      see the {@link TeeConsumer} class.)
 *
 *      <li> Event chaining is set up differently.  It is intended to
 *      work "upstream" from terminus towards producer, during filter
 *      construction, as described above.
 *      This is part of an early binding model:
 *      events don't need to pass through stages which ignore them.
 *
 *      <li> ErrorHandler support is separated, on the grounds that
 *      pipeline stages need to share the same error handling policy.
 *      For the same reason, error handler setup goes "downstream":
 *      when error handlers get set, they are passed to subsequent
 *      consumers.
 *
 *      </ul>
 *
 * <p> The {@link #chainTo chainTo()} convenience routine supports chaining to
 * an XMLFilterImpl, in its role as a limited functionality event
 * consumer.  Its event producer role ({@link XMLFilter}) is ignored.
 *
 * <hr />
 *
 * <p> The {@link #bind bind()} routine may be used associate event pipelines
 * with any kind of {@link XMLReader} that will produce the events.
 * Such pipelines don't necessarily need to have any members which are
 * implemented using this class.  That routine has some intelligence
 * which supports automatic changes to parser feature flags, letting
 * event piplines become largely independent of the particular feature
 * sets of parsers.
 *
 * @author David Brownell
 */
public class EventFilter
    implements EventConsumer, ContentHandler, DTDHandler,
            LexicalHandler, DeclHandler
{
    // SAX handlers
    private ContentHandler              docHandler, docNext;
    private DTDHandler                  dtdHandler, dtdNext;
    private LexicalHandler              lexHandler, lexNext;
    private DeclHandler                 declHandler, declNext;
    // and ideally, one more for the stuff SAX2 doesn't show

    private Locator                     locator;
    private EventConsumer               next;
    private ErrorHandler                errHandler;


    /** SAX2 URI prefix for standard feature flags. */
    public static final String          FEATURE_URI
        = "http://xml.org/sax/features/";
    /** SAX2 URI prefix for standard properties (mostly for handlers). */
    public static final String          PROPERTY_URI
        = "http://xml.org/sax/properties/";

    /** SAX2 property identifier for {@link DeclHandler} events */
    public static final String          DECL_HANDLER
        = PROPERTY_URI + "declaration-handler";
    /** SAX2 property identifier for {@link LexicalHandler} events */
    public static final String          LEXICAL_HANDLER
        = PROPERTY_URI + "lexical-handler";

    //
    // These class objects will be null if the relevant class isn't linked.
    // Small configurations (pJava and some kinds of embedded systems) need
    // to facilitate smaller executables.  So "instanceof" is undesirable
    // when bind() sees if it can remove some stages.
    //
    // SECURITY NOTE:  assuming all these classes are part of the same sealed
    // package, there's no problem saving these in the instance of this class
    // that's associated with "this" class loader.  But that wouldn't be true
    // for classes in another package.
    //
    private static boolean              loaded;
    private static Class                nsClass;
    private static Class                validClass;
    private static Class                wfClass;
    private static Class                xincClass;

    static ClassLoader getClassLoader ()
    {
        Method m = null;

        try {
            m = Thread.class.getMethod("getContextClassLoader");
        } catch (NoSuchMethodException e) {
            // Assume that we are running JDK 1.1, use the current ClassLoader
            return EventFilter.class.getClassLoader();
        }

        try {
            return (ClassLoader) m.invoke(Thread.currentThread());
        } catch (IllegalAccessException e) {
            // assert(false)
            throw new UnknownError(e.getMessage());
        } catch (InvocationTargetException e) {
            // assert(e.getTargetException() instanceof SecurityException)
            throw new UnknownError(e.getMessage());
        }
    }

    static Class loadClass (ClassLoader classLoader, String className)
    {
        try {
            if (classLoader == null)
                return Class.forName(className);
            else
                return classLoader.loadClass(className);
        } catch (Exception e) {
            return null;
        }
    }

    static private void loadClasses ()
    {
        ClassLoader     loader = getClassLoader ();

        nsClass = loadClass (loader, "gnu.xml.pipeline.NSFilter");
        validClass = loadClass (loader, "gnu.xml.pipeline.ValidationConsumer");
        wfClass = loadClass (loader, "gnu.xml.pipeline.WellFormednessFilter");
        xincClass = loadClass (loader, "gnu.xml.pipeline.XIncludeFilter");
        loaded = true;
    }


    /**
     * Binds the standard SAX2 handlers from the specified consumer
     * pipeline to the specified producer.  These handlers include the core
     * {@link ContentHandler} and {@link DTDHandler}, plus the extension
     * {@link DeclHandler} and {@link LexicalHandler}.  Any additional
     * application-specific handlers need to be bound separately.
     * The {@link ErrorHandler} is handled differently:  the producer's
     * error handler is passed through to the consumer pipeline.
     * The producer is told to include namespace prefix information if it
     * can, since many pipeline stages need that Infoset information to
     * work well.
     *
     * <p> At the head of the pipeline, certain standard event filters are
     * recognized and handled specially.  This facilitates construction
     * of processing pipelines that work regardless of the capabilities
     * of the XMLReader implementation in use; for example, it permits
     * validating output of a {@link gnu.xml.util.DomParser}. <ul>
     *
     *  <li> {@link NSFilter} will be removed if the producer can be
     *  told not to discard namespace data, using the "namespace-prefixes"
     *  feature flag.
     *
     *  <li> {@link ValidationConsumer} will be removed if the producer
     *  can be told to validate, using the "validation" feature flag.
     *
     *  <li> {@link WellFormednessFilter} is always removed, on the
     *  grounds that no XMLReader is permitted to producee malformed
     *  event streams and this would just be processing overhead.
     *
     *  <li> {@link XIncludeFilter} stops the special handling, except
     *  that it's told about the "namespace-prefixes" feature of the
     *  event producer so that the event stream is internally consistent.
     *
     *  <li> The first consumer which is not one of those classes stops
     *  such special handling.  This means that if you want to force
     *  one of those filters to be used, you could just precede it with
     *  an instance of {@link EventFilter} configured as a pass-through.
     *  You might need to do that if you are using an {@link NSFilter}
     *  subclass to fix names found in attributes or character data.
     *
     *  </ul>
     *
     * <p> Other than that, this method works with any kind of event consumer,
     * not just event filters.  Note that in all cases, the standard handlers
     * are assigned; any previous handler assignments for the handler will
     * be overridden.
     *
     * @param producer will deliver events to the specified consumer
     * @param consumer pipeline supplying event handlers to be associated
     *  with the producer (may not be null)
     */
    public static void bind (XMLReader producer, EventConsumer consumer)
    {
        Class   klass = null;
        boolean prefixes;

        if (!loaded)
            loadClasses ();

        // DOM building, printing, layered validation, and other
        // things don't work well when prefix info is discarded.
        // Include it by default, whenever possible.
        try {
            producer.setFeature (FEATURE_URI + "namespace-prefixes",
                true);
            prefixes = true;
        } catch (SAXException e) {
            prefixes = false;
        }

        // NOTE:  This loop doesn't use "instanceof", since that
        // would prevent compiling/linking without those classes
        // being present.
        while (consumer != null) {
            klass = consumer.getClass ();

            // we might have already changed this problematic SAX2 default.
            if (nsClass != null && nsClass.isAssignableFrom (klass)) {
                if (!prefixes)
                    break;
                consumer = ((EventFilter)consumer).getNext ();

            // the parser _might_ do DTD validation by default ...
            // if not, maybe we can change this setting.
            } else if (validClass != null
                    && validClass.isAssignableFrom (klass)) {
                try {
                    producer.setFeature (FEATURE_URI + "validation",
                        true);
                    consumer = ((ValidationConsumer)consumer).getNext ();
                } catch (SAXException e) {
                    break;
                }

            // parsers are required not to have such bugs
            } else if (wfClass != null && wfClass.isAssignableFrom (klass)) {
                consumer = ((WellFormednessFilter)consumer).getNext ();

            // stop on the first pipeline stage we can't remove
            } else
                break;

            if (consumer == null)
                klass = null;
        }

        // the actual setting here doesn't matter as much
        // as that producer and consumer agree
        if (xincClass != null && klass != null
                && xincClass.isAssignableFrom (klass))
            ((XIncludeFilter)consumer).setSavingPrefixes (prefixes);

        // Some SAX parsers can't handle null handlers -- bleech
        DefaultHandler2 h = new DefaultHandler2 ();

        if (consumer != null && consumer.getContentHandler () != null)
            producer.setContentHandler (consumer.getContentHandler ());
        else
            producer.setContentHandler (h);
        if (consumer != null && consumer.getDTDHandler () != null)
            producer.setDTDHandler (consumer.getDTDHandler ());
        else
            producer.setDTDHandler (h);

        try {
            Object      dh;

            if (consumer != null)
                dh = consumer.getProperty (DECL_HANDLER);
            else
                dh = null;
            if (dh == null)
                dh = h;
            producer.setProperty (DECL_HANDLER, dh);
        } catch (Exception e) { /* ignore */ }
        try {
            Object      lh;

            if (consumer != null)
                lh = consumer.getProperty (LEXICAL_HANDLER);
            else
                lh = null;
            if (lh == null)
                lh = h;
            producer.setProperty (LEXICAL_HANDLER, lh);
        } catch (Exception e) { /* ignore */ }

        // this binding goes the other way around
        if (producer.getErrorHandler () == null)
            producer.setErrorHandler (h);
        if (consumer != null)
            consumer.setErrorHandler (producer.getErrorHandler ());
    }

    /**
     * Initializes all handlers to null.
     */
        // constructor used by PipelineFactory
    public EventFilter () { }


    /**
     * Handlers that are not otherwise set will default to those from
     * the specified consumer, making it easy to pass events through.
     * If the consumer is null, all handlers are initialzed to null.
     */
        // constructor used by PipelineFactory
    public EventFilter (EventConsumer consumer)
    {
        if (consumer == null)
            return;

        next = consumer;

        // We delegate through the "xxNext" handlers, and
        // report the "xxHandler" ones on our input side.

        // Normally a subclass would both override handler
        // methods and register itself as the "xxHandler".

        docHandler = docNext = consumer.getContentHandler ();
        dtdHandler = dtdNext = consumer.getDTDHandler ();
        try {
            declHandler = declNext = (DeclHandler)
                    consumer.getProperty (DECL_HANDLER);
        } catch (SAXException e) { /* leave value null */ }
        try {
            lexHandler = lexNext = (LexicalHandler)
                    consumer.getProperty (LEXICAL_HANDLER);
        } catch (SAXException e) { /* leave value null */ }
    }

    /**
     * Treats the XMLFilterImpl as a limited functionality event consumer,
     * by arranging to deliver events to it; this lets such classes be
     * "wrapped" as pipeline stages.
     *
     * <p> <em>Upstream Event Setup:</em>
     * If no handlers have been assigned to this EventFilter, then the
     * handlers from specified XMLFilterImpl are returned from this
     * {@link EventConsumer}: the XMLFilterImpl is just "wrapped".
     * Otherwise the specified handlers will be returned.
     *
     * <p> <em>Downstream Event Setup:</em>
     * Subclasses may chain event delivery to the specified XMLFilterImpl
     * by invoking the appropiate superclass methods,
     * as if their constructor passed a "next" EventConsumer to the
     * constructor for this class.
     * If this EventFilter has an ErrorHandler, it is assigned as
     * the error handler for the XMLFilterImpl, just as would be
     * done for a next stage implementing {@link EventConsumer}.
     *
     * @param next the next downstream component of the pipeline.
     * @exception IllegalStateException if the "next" consumer has
     *  already been set through the constructor.
     */
    public void chainTo (XMLFilterImpl next)
    {
        if (this.next != null)
            throw new IllegalStateException ();

        docNext = next.getContentHandler ();
        if (docHandler == null)
            docHandler = docNext;
        dtdNext = next.getDTDHandler ();
        if (dtdHandler == null)
            dtdHandler = dtdNext;

        try {
            declNext = (DeclHandler) next.getProperty (DECL_HANDLER);
            if (declHandler == null)
                declHandler = declNext;
        } catch (SAXException e) { /* leave value null */ }
        try {
            lexNext = (LexicalHandler) next.getProperty (LEXICAL_HANDLER);
            if (lexHandler == null)
                lexHandler = lexNext;
        } catch (SAXException e) { /* leave value null */ }

        if (errHandler != null)
            next.setErrorHandler (errHandler);
    }

    /**
     * Records the error handler that should be used by this stage, and
     * passes it "downstream" to any subsequent stage.
     */
    final public void setErrorHandler (ErrorHandler handler)
    {
        errHandler = handler;
        if (next != null)
            next.setErrorHandler (handler);
    }

    /**
     * Returns the error handler assigned this filter stage, or null
     * if no such assigment has been made.
     */
    final public ErrorHandler getErrorHandler ()
    {
        return errHandler;
    }


    /**
     * Returns the next event consumer in sequence; or null if there
     * is no such handler.
     */
    final public EventConsumer getNext ()
        { return next; }


    /**
     * Assigns the content handler to use; a null handler indicates
     * that these events will not be forwarded.
     * This overrides the previous settting for this handler, which was
     * probably pointed to the next consumer by the base class constructor.
     */
    final public void setContentHandler (ContentHandler h)
    {
        docHandler = h;
    }

    /** Returns the content handler being used. */
    final public ContentHandler getContentHandler ()
    {
        return docHandler;
    }

    /**
     * Assigns the DTD handler to use; a null handler indicates
     * that these events will not be forwarded.
     * This overrides the previous settting for this handler, which was
     * probably pointed to the next consumer by the base class constructor.
     */
    final public void setDTDHandler (DTDHandler h)
        { dtdHandler = h; }

    /** Returns the dtd handler being used. */
    final public DTDHandler getDTDHandler ()
    {
        return dtdHandler;
    }

    /**
     * Stores the property, normally a handler; a null handler indicates
     * that these events will not be forwarded.
     * This overrides the previous handler settting, which was probably
     * pointed to the next consumer by the base class constructor.
     */
    final public void setProperty (String id, Object o)
    throws SAXNotRecognizedException, SAXNotSupportedException
    {
        try {
            Object      value = getProperty (id);

            if (value == o)
                return;
            if (DECL_HANDLER.equals (id)) {
                declHandler = (DeclHandler) o;
                return;
            }
            if (LEXICAL_HANDLER.equals (id)) {
                lexHandler = (LexicalHandler) o;
                return;
            }
            throw new SAXNotSupportedException (id);

        } catch (ClassCastException e) {
            throw new SAXNotSupportedException (id);
        }
    }

    /** Retrieves a property of unknown intent (usually a handler) */
    final public Object getProperty (String id)
    throws SAXNotRecognizedException
    {
        if (DECL_HANDLER.equals (id))
            return declHandler;
        if (LEXICAL_HANDLER.equals (id))
            return lexHandler;

        throw new SAXNotRecognizedException (id);
    }

    /**
     * Returns any locator provided to the next consumer, if this class
     * (or a subclass) is handling {@link ContentHandler } events.
     */
    public Locator getDocumentLocator ()
        { return locator; }


    // CONTENT HANDLER DELEGATIONS

    /** <b>SAX2:</b> passes this callback to the next consumer, if any */
    public void setDocumentLocator (Locator locator)
    {
        this.locator = locator;
        if (docNext != null)
            docNext.setDocumentLocator (locator);
    }

    /** <b>SAX2:</b> passes this callback to the next consumer, if any */
    public void startDocument () throws SAXException
    {
        if (docNext != null)
            docNext.startDocument ();
    }

    /** <b>SAX2:</b> passes this callback to the next consumer, if any */
    public void skippedEntity (String name) throws SAXException
    {
        if (docNext != null)
            docNext.skippedEntity (name);
    }

    /** <b>SAX2:</b> passes this callback to the next consumer, if any */
    public void processingInstruction (String target, String data)
    throws SAXException
    {
        if (docNext != null)
            docNext.processingInstruction (target, data);
    }

    /** <b>SAX2:</b> passes this callback to the next consumer, if any */
    public void characters (char ch [], int start, int length)
    throws SAXException
    {
        if (docNext != null)
            docNext.characters (ch, start, length);
    }

    /** <b>SAX2:</b> passes this callback to the next consumer, if any */
    public void ignorableWhitespace (char ch [], int start, int length)
    throws SAXException
    {
        if (docNext != null)
            docNext.ignorableWhitespace (ch, start, length);
    }

    /** <b>SAX2:</b> passes this callback to the next consumer, if any */
    public void startPrefixMapping (String prefix, String uri)
    throws SAXException
    {
        if (docNext != null)
            docNext.startPrefixMapping (prefix, uri);
    }

    /** <b>SAX2:</b> passes this callback to the next consumer, if any */
    public void startElement (
        String uri, String localName,
        String qName, Attributes atts
    ) throws SAXException
    {
        if (docNext != null)
            docNext.startElement (uri, localName, qName, atts);
    }

    /** <b>SAX2:</b> passes this callback to the next consumer, if any */
    public void endElement (String uri, String localName, String qName)
    throws SAXException
    {
        if (docNext != null)
            docNext.endElement (uri, localName, qName);
    }

    /** <b>SAX2:</b> passes this callback to the next consumer, if any */
    public void endPrefixMapping (String prefix) throws SAXException
    {
        if (docNext != null)
            docNext.endPrefixMapping (prefix);
    }

    /** <b>SAX2:</b> passes this callback to the next consumer, if any */
    public void endDocument () throws SAXException
    {
        if (docNext != null)
            docNext.endDocument ();
        locator = null;
    }


    // DTD HANDLER DELEGATIONS

    /** <b>SAX1:</b> passes this callback to the next consumer, if any */
    public void unparsedEntityDecl (
        String name,
        String publicId,
        String systemId,
        String notationName
    ) throws SAXException
    {
        if (dtdNext != null)
            dtdNext.unparsedEntityDecl (name, publicId, systemId, notationName);
    }

    /** <b>SAX1:</b> passes this callback to the next consumer, if any */
    public void notationDecl (String name, String publicId, String systemId)
    throws SAXException
    {
        if (dtdNext != null)
            dtdNext.notationDecl (name, publicId, systemId);
    }


    // LEXICAL HANDLER DELEGATIONS

    /** <b>SAX2:</b> passes this callback to the next consumer, if any */
    public void startDTD (String name, String publicId, String systemId)
    throws SAXException
    {
        if (lexNext != null)
            lexNext.startDTD (name, publicId, systemId);
    }

    /** <b>SAX2:</b> passes this callback to the next consumer, if any */
    public void endDTD ()
    throws SAXException
    {
        if (lexNext != null)
            lexNext.endDTD ();
    }

    /** <b>SAX2:</b> passes this callback to the next consumer, if any */
    public void comment (char ch [], int start, int length)
    throws SAXException
    {
        if (lexNext != null)
            lexNext.comment (ch, start, length);
    }

    /** <b>SAX2:</b> passes this callback to the next consumer, if any */
    public void startCDATA ()
    throws SAXException
    {
        if (lexNext != null)
            lexNext.startCDATA ();
    }

    /** <b>SAX2:</b> passes this callback to the next consumer, if any */
    public void endCDATA ()
    throws SAXException
    {
        if (lexNext != null)
            lexNext.endCDATA ();
    }

    /**
     * <b>SAX2:</b> passes this callback to the next consumer, if any.
     */
    public void startEntity (String name)
    throws SAXException
    {
        if (lexNext != null)
            lexNext.startEntity (name);
    }

    /**
     * <b>SAX2:</b> passes this callback to the next consumer, if any.
     */
    public void endEntity (String name)
    throws SAXException
    {
        if (lexNext != null)
            lexNext.endEntity (name);
    }


    // DECLARATION HANDLER DELEGATIONS


    /** <b>SAX2:</b> passes this callback to the next consumer, if any */
    public void elementDecl (String name, String model)
    throws SAXException
    {
        if (declNext != null)
            declNext.elementDecl (name, model);
    }

    /** <b>SAX2:</b> passes this callback to the next consumer, if any */
    public void attributeDecl (String eName, String aName,
            String type, String mode, String value)
    throws SAXException
    {
        if (declNext != null)
            declNext.attributeDecl (eName, aName, type, mode, value);
    }

    /** <b>SAX2:</b> passes this callback to the next consumer, if any */
    public void externalEntityDecl (String name,
        String publicId, String systemId)
    throws SAXException
    {
        if (declNext != null)
            declNext.externalEntityDecl (name, publicId, systemId);
    }

    /** <b>SAX2:</b> passes this callback to the next consumer, if any */
    public void internalEntityDecl (String name, String value)
    throws SAXException
    {
        if (declNext != null)
            declNext.internalEntityDecl (name, value);
    }
}
