/*
 * EventFilter.java
 * Copyright (C) 1999,2000,2001 The Free Software Foundation
 * 
 * This file is part of GNU JAXP, a library.
 *
 * GNU JAXP 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.
 * 
 * GNU JAXP 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
 *
 * 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
 * obliged 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;

import gnu.xml.aelfred2.ContentHandler2;

/**
 * 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, ContentHandler2, 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", null);
        } 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(), null);
        } 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 ();
    }

    public void xmlDecl(String version, String encoding, boolean standalone,
                        String inputEncoding)
      throws SAXException
    {
      if (docNext != null && docNext instanceof ContentHandler2)
        {
          ((ContentHandler2) docNext).xmlDecl(version, encoding, standalone,
                                              inputEncoding);
        }
    }

    /** <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);
    }
}
