jaxp/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/dom/DocumentImpl.java
changeset 25868 686eef1e7a79
parent 25264 040625ce9b72
child 33349 975138b77cff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jaxp/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/dom/DocumentImpl.java	Sun Aug 17 15:51:56 2014 +0100
@@ -0,0 +1,1304 @@
+/*
+ * reserved comment block
+ * DO NOT REMOVE OR ALTER!
+ */
+/*
+ * Copyright 2001,2002,2004,2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.sun.org.apache.xerces.internal.dom;
+
+import java.io.Serializable;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import com.sun.org.apache.xerces.internal.dom.events.EventImpl;
+import com.sun.org.apache.xerces.internal.dom.events.MutationEventImpl;
+import org.w3c.dom.UserDataHandler;
+import org.w3c.dom.Attr;
+import org.w3c.dom.DOMException;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.DocumentType;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.events.DocumentEvent;
+import org.w3c.dom.events.Event;
+import org.w3c.dom.events.EventException;
+import org.w3c.dom.events.EventListener;
+import org.w3c.dom.events.MutationEvent;
+import org.w3c.dom.ranges.DocumentRange;
+import org.w3c.dom.ranges.Range;
+import org.w3c.dom.traversal.DocumentTraversal;
+import org.w3c.dom.traversal.NodeFilter;
+import org.w3c.dom.traversal.NodeIterator;
+import org.w3c.dom.traversal.TreeWalker;
+
+
+/**
+ * The Document interface represents the entire HTML or XML document.
+ * Conceptually, it is the root of the document tree, and provides the
+ * primary access to the document's data.
+ * <P>
+ * Since elements, text nodes, comments, processing instructions,
+ * etc. cannot exist outside the context of a Document, the Document
+ * interface also contains the factory methods needed to create these
+ * objects. The Node objects created have a ownerDocument attribute
+ * which associates them with the Document within whose context they
+ * were created.
+ * <p>
+ * The DocumentImpl class also implements the DOM Level 2 DocumentTraversal
+ * interface. This interface is comprised of factory methods needed to
+ * create NodeIterators and TreeWalkers. The process of creating NodeIterator
+ * objects also adds these references to this document.
+ * After finishing with an iterator it is important to remove the object
+ * using the remove methods in this implementation. This allows the release of
+ * the references from the iterator objects to the DOM Nodes.
+ * <p>
+ * <b>Note:</b> When any node in the document is serialized, the
+ * entire document is serialized along with it.
+ *
+ * @xerces.internal
+ *
+ * @author Arnaud  Le Hors, IBM
+ * @author Joe Kesselman, IBM
+ * @author Andy Clark, IBM
+ * @author Ralf Pfeiffer, IBM
+ * @since  PR-DOM-Level-1-19980818.
+ */
+public class DocumentImpl
+    extends CoreDocumentImpl
+    implements DocumentTraversal, DocumentEvent, DocumentRange {
+
+    //
+    // Constants
+    //
+
+    /** Serialization version. */
+    static final long serialVersionUID = 515687835542616694L;
+
+    //
+    // Data
+    //
+
+    /** Iterators */
+    // REVISIT: Should this be transient? -Ac
+    protected Vector iterators;
+
+     /** Ranges */
+    // REVISIT: Should this be transient? -Ac
+    protected Vector ranges;
+
+    /** Table for event listeners registered to this document nodes. */
+    protected Hashtable eventListeners;
+
+    /** Bypass mutation events firing. */
+    protected boolean mutationEvents = false;
+
+    //
+    // Constructors
+    //
+
+    /**
+     * NON-DOM: Actually creating a Document is outside the DOM's spec,
+     * since it has to operate in terms of a particular implementation.
+     */
+    public DocumentImpl() {
+        super();
+    }
+
+    /** Constructor. */
+    public DocumentImpl(boolean grammarAccess) {
+        super(grammarAccess);
+    }
+
+    /**
+     * For DOM2 support.
+     * The createDocument factory method is in DOMImplementation.
+     */
+    public DocumentImpl(DocumentType doctype)
+    {
+        super(doctype);
+    }
+
+    /** For DOM2 support. */
+    public DocumentImpl(DocumentType doctype, boolean grammarAccess) {
+        super(doctype, grammarAccess);
+    }
+
+    //
+    // Node methods
+    //
+
+    /**
+     * Deep-clone a document, including fixing ownerDoc for the cloned
+     * children. Note that this requires bypassing the WRONG_DOCUMENT_ERR
+     * protection. I've chosen to implement it by calling importNode
+     * which is DOM Level 2.
+     *
+     * @return org.w3c.dom.Node
+     * @param deep boolean, iff true replicate children
+     */
+    public Node cloneNode(boolean deep) {
+
+        DocumentImpl newdoc = new DocumentImpl();
+        callUserDataHandlers(this, newdoc, UserDataHandler.NODE_CLONED);
+        cloneNode(newdoc, deep);
+
+        // experimental
+        newdoc.mutationEvents = mutationEvents;
+
+        return newdoc;
+
+    } // cloneNode(boolean):Node
+
+    /**
+     * Retrieve information describing the abilities of this particular
+     * DOM implementation. Intended to support applications that may be
+     * using DOMs retrieved from several different sources, potentially
+     * with different underlying representations.
+     */
+    public DOMImplementation getImplementation() {
+        // Currently implemented as a singleton, since it's hardcoded
+        // information anyway.
+        return DOMImplementationImpl.getDOMImplementation();
+    }
+
+    //
+    // DocumentTraversal methods
+    //
+
+    /**
+     * NON-DOM extension:
+     * Create and return a NodeIterator. The NodeIterator is
+     * added to a list of NodeIterators so that it can be
+     * removed to free up the DOM Nodes it references.
+     *
+     * @param root The root of the iterator.
+     * @param whatToShow The whatToShow mask.
+     * @param filter The NodeFilter installed. Null means no filter.
+     */
+    public NodeIterator createNodeIterator(Node root,
+                                           short whatToShow,
+                                           NodeFilter filter)
+    {
+        return createNodeIterator(root, whatToShow, filter, true);
+    }
+
+    /**
+     * Create and return a NodeIterator. The NodeIterator is
+     * added to a list of NodeIterators so that it can be
+     * removed to free up the DOM Nodes it references.
+     *
+     * @param root The root of the iterator.
+     * @param whatToShow The whatToShow mask.
+     * @param filter The NodeFilter installed. Null means no filter.
+     * @param entityReferenceExpansion true to expand the contents of
+     *                                 EntityReference nodes
+     * @since WD-DOM-Level-2-19990923
+     */
+    public NodeIterator createNodeIterator(Node root,
+                                           int whatToShow,
+                                           NodeFilter filter,
+                                           boolean entityReferenceExpansion)
+    {
+
+        if (root == null) {
+                  String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_SUPPORTED_ERR", null);
+                  throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg);
+        }
+
+        NodeIterator iterator = new NodeIteratorImpl(this,
+                                                     root,
+                                                     whatToShow,
+                                                     filter,
+                                                     entityReferenceExpansion);
+        if (iterators == null) {
+            iterators = new Vector();
+        }
+
+        iterators.addElement(iterator);
+
+        return iterator;
+    }
+
+    /**
+     * NON-DOM extension:
+     * Create and return a TreeWalker.
+     *
+     * @param root The root of the iterator.
+     * @param whatToShow The whatToShow mask.
+     * @param filter The NodeFilter installed. Null means no filter.
+     */
+    public TreeWalker createTreeWalker(Node root,
+                                       short whatToShow,
+                                       NodeFilter filter)
+    {
+        return createTreeWalker(root, whatToShow, filter, true);
+    }
+    /**
+     * Create and return a TreeWalker.
+     *
+     * @param root The root of the iterator.
+     * @param whatToShow The whatToShow mask.
+     * @param filter The NodeFilter installed. Null means no filter.
+     * @param entityReferenceExpansion true to expand the contents of
+     *                                 EntityReference nodes
+     * @since WD-DOM-Level-2-19990923
+     */
+    public TreeWalker createTreeWalker(Node root,
+                                       int whatToShow,
+                                       NodeFilter filter,
+                                       boolean entityReferenceExpansion)
+    {
+        if (root == null) {
+            String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_SUPPORTED_ERR", null);
+            throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg);
+        }
+        return new TreeWalkerImpl(root, whatToShow, filter,
+                                  entityReferenceExpansion);
+    }
+
+    //
+    // Not DOM Level 2. Support DocumentTraversal methods.
+    //
+
+    /** This is not called by the developer client. The
+     *  developer client uses the detach() function on the
+     *  NodeIterator itself. <p>
+     *
+     *  This function is called from the NodeIterator#detach().
+     */
+     void removeNodeIterator(NodeIterator nodeIterator) {
+
+        if (nodeIterator == null) return;
+        if (iterators == null) return;
+
+        iterators.removeElement(nodeIterator);
+    }
+
+    //
+    // DocumentRange methods
+    //
+    /**
+     */
+    public Range createRange() {
+
+        if (ranges == null) {
+            ranges = new Vector();
+        }
+
+        Range range = new RangeImpl(this);
+
+        ranges.addElement(range);
+
+        return range;
+
+    }
+
+    /** Not a client function. Called by Range.detach(),
+     *  so a Range can remove itself from the list of
+     *  Ranges.
+     */
+    void removeRange(Range range) {
+
+        if (range == null) return;
+        if (ranges == null) return;
+
+        ranges.removeElement(range);
+    }
+
+    /**
+     * A method to be called when some text was changed in a text node,
+     * so that live objects can be notified.
+     */
+    void replacedText(NodeImpl node) {
+        // notify ranges
+        if (ranges != null) {
+            int size = ranges.size();
+            for (int i = 0; i != size; i++) {
+                ((RangeImpl)ranges.elementAt(i)).receiveReplacedText(node);
+            }
+        }
+    }
+
+    /**
+     * A method to be called when some text was deleted from a text node,
+     * so that live objects can be notified.
+     */
+    void deletedText(NodeImpl node, int offset, int count) {
+        // notify ranges
+        if (ranges != null) {
+            int size = ranges.size();
+            for (int i = 0; i != size; i++) {
+                ((RangeImpl)ranges.elementAt(i)).receiveDeletedText(node,
+                                                                offset, count);
+            }
+        }
+    }
+
+    /**
+     * A method to be called when some text was inserted into a text node,
+     * so that live objects can be notified.
+     */
+    void insertedText(NodeImpl node, int offset, int count) {
+        // notify ranges
+        if (ranges != null) {
+            int size = ranges.size();
+            for (int i = 0; i != size; i++) {
+                ((RangeImpl)ranges.elementAt(i)).receiveInsertedText(node,
+                                                                offset, count);
+            }
+        }
+    }
+
+    /**
+     * A method to be called when a text node has been split,
+     * so that live objects can be notified.
+     */
+    void splitData(Node node, Node newNode, int offset) {
+        // notify ranges
+        if (ranges != null) {
+            int size = ranges.size();
+            for (int i = 0; i != size; i++) {
+                ((RangeImpl)ranges.elementAt(i)).receiveSplitData(node,
+                                                              newNode, offset);
+            }
+        }
+    }
+
+    //
+    // DocumentEvent methods
+    //
+
+    /**
+     * Introduced in DOM Level 2. Optional. <p>
+     * Create and return Event objects.
+     *
+     * @param type The eventType parameter specifies the type of Event
+     * interface to be created.  If the Event interface specified is supported
+     * by the implementation this method will return a new Event of the
+     * interface type requested. If the Event is to be dispatched via the
+     * dispatchEvent method the appropriate event init method must be called
+     * after creation in order to initialize the Event's values.  As an
+     * example, a user wishing to synthesize some kind of Event would call
+     * createEvent with the parameter "Events". The initEvent method could then
+     * be called on the newly created Event to set the specific type of Event
+     * to be dispatched and set its context information.
+     * @return Newly created Event
+     * @exception DOMException NOT_SUPPORTED_ERR: Raised if the implementation
+     * does not support the type of Event interface requested
+     * @since WD-DOM-Level-2-19990923
+     */
+    public Event createEvent(String type)
+        throws DOMException {
+            if (type.equalsIgnoreCase("Events") || "Event".equals(type))
+                return new EventImpl();
+            if (type.equalsIgnoreCase("MutationEvents") ||
+                "MutationEvent".equals(type))
+                return new MutationEventImpl();
+            else {
+            String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_SUPPORTED_ERR", null);
+                throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg);
+        }
+        }
+
+    /**
+     * Sets whether the DOM implementation generates mutation events
+     * upon operations.
+     */
+    void setMutationEvents(boolean set) {
+        mutationEvents = set;
+    }
+
+    /**
+     * Returns true if the DOM implementation generates mutation events.
+     */
+    boolean getMutationEvents() {
+        return mutationEvents;
+    }
+
+    /**
+     * Store event listener registered on a given node
+     * This is another place where we could use weak references! Indeed, the
+     * node here won't be GC'ed as long as some listener is registered on it,
+     * since the eventsListeners table will have a reference to the node.
+     */
+    protected void setEventListeners(NodeImpl n, Vector listeners) {
+        if (eventListeners == null) {
+            eventListeners = new Hashtable();
+        }
+        if (listeners == null) {
+            eventListeners.remove(n);
+            if (eventListeners.isEmpty()) {
+                // stop firing events when there isn't any listener
+                mutationEvents = false;
+            }
+        } else {
+            eventListeners.put(n, listeners);
+            // turn mutation events on
+            mutationEvents = true;
+        }
+    }
+
+    /**
+     * Retreive event listener registered on a given node
+     */
+    protected Vector getEventListeners(NodeImpl n) {
+        if (eventListeners == null) {
+            return null;
+        }
+        return (Vector) eventListeners.get(n);
+    }
+
+    //
+    // EventTarget support (public and internal)
+    //
+
+    //
+    // Constants
+    //
+
+    /*
+     * NON-DOM INTERNAL: Class LEntry is just a struct used to represent
+     * event listeners registered with this node. Copies of this object
+     * are hung from the nodeListeners Vector.
+     * <p>
+     * I considered using two vectors -- one for capture,
+     * one for bubble -- but decided that since the list of listeners
+     * is probably short in most cases, it might not be worth spending
+     * the space. ***** REVISIT WHEN WE HAVE MORE EXPERIENCE.
+     */
+    class LEntry implements Serializable {
+
+        private static final long serialVersionUID = -8426757059492421631L;
+        String type;
+        EventListener listener;
+        boolean useCapture;
+
+        /** NON-DOM INTERNAL: Constructor for Listener list Entry
+         * @param type Event name (NOT event group!) to listen for.
+         * @param listener Who gets called when event is dispatched
+         * @param useCaptue True iff listener is registered on
+         *  capturing phase rather than at-target or bubbling
+         */
+        LEntry(String type, EventListener listener, boolean useCapture)
+        {
+            this.type = type;
+            this.listener = listener;
+            this.useCapture = useCapture;
+        }
+
+    } // LEntry
+
+    /**
+     * Introduced in DOM Level 2. <p> Register an event listener with this
+     * Node. A listener may be independently registered as both Capturing and
+     * Bubbling, but may only be registered once per role; redundant
+     * registrations are ignored.
+     * @param node node to add listener to
+     * @param type Event name (NOT event group!) to listen for.
+     * @param listener Who gets called when event is dispatched
+     * @param useCapture True iff listener is registered on
+     *  capturing phase rather than at-target or bubbling
+     */
+    protected void addEventListener(NodeImpl node, String type,
+                                    EventListener listener, boolean useCapture)
+    {
+        // We can't dispatch to blank type-name, and of course we need
+        // a listener to dispatch to
+        if (type == null || type.equals("") || listener == null)
+            return;
+
+        // Each listener may be registered only once per type per phase.
+        // Simplest way to code that is to zap the previous entry, if any.
+        removeEventListener(node, type, listener, useCapture);
+
+        Vector nodeListeners = getEventListeners(node);
+        if(nodeListeners == null) {
+            nodeListeners = new Vector();
+            setEventListeners(node, nodeListeners);
+        }
+        nodeListeners.addElement(new LEntry(type, listener, useCapture));
+
+        // Record active listener
+        LCount lc = LCount.lookup(type);
+        if (useCapture) {
+            ++lc.captures;
+            ++lc.total;
+        }
+        else {
+            ++lc.bubbles;
+            ++lc.total;
+        }
+
+    } // addEventListener(NodeImpl,String,EventListener,boolean) :void
+
+    /**
+     * Introduced in DOM Level 2. <p> Deregister an event listener previously
+     * registered with this Node.  A listener must be independently removed
+     * from the Capturing and Bubbling roles. Redundant removals (of listeners
+     * not currently registered for this role) are ignored.
+     * @param node node to remove listener from
+     * @param type Event name (NOT event group!) to listen for.
+     * @param listener Who gets called when event is dispatched
+     * @param useCapture True iff listener is registered on
+     *  capturing phase rather than at-target or bubbling
+     */
+    protected void removeEventListener(NodeImpl node, String type,
+                                       EventListener listener,
+                                       boolean useCapture)
+    {
+        // If this couldn't be a valid listener registration, ignore request
+        if (type == null || type.equals("") || listener == null)
+            return;
+        Vector nodeListeners = getEventListeners(node);
+        if (nodeListeners == null)
+            return;
+
+        // Note that addListener has previously ensured that
+        // each listener may be registered only once per type per phase.
+        // count-down is OK for deletions!
+        for (int i = nodeListeners.size() - 1; i >= 0; --i) {
+            LEntry le = (LEntry) nodeListeners.elementAt(i);
+            if (le.useCapture == useCapture && le.listener == listener &&
+                le.type.equals(type)) {
+                nodeListeners.removeElementAt(i);
+                // Storage management: Discard empty listener lists
+                if (nodeListeners.size() == 0)
+                    setEventListeners(node, null);
+
+                // Remove active listener
+                LCount lc = LCount.lookup(type);
+                if (useCapture) {
+                    --lc.captures;
+                    --lc.total;
+                }
+                else {
+                    --lc.bubbles;
+                    --lc.total;
+                }
+
+                break;  // Found it; no need to loop farther.
+            }
+        }
+    } // removeEventListener(NodeImpl,String,EventListener,boolean) :void
+
+    protected void copyEventListeners(NodeImpl src, NodeImpl tgt) {
+        Vector nodeListeners = getEventListeners(src);
+        if (nodeListeners == null) {
+            return;
+        }
+        setEventListeners(tgt, (Vector) nodeListeners.clone());
+    }
+
+    /**
+     * Introduced in DOM Level 2. <p>
+     * Distribution engine for DOM Level 2 Events.
+     * <p>
+     * Event propagation runs as follows:
+     * <ol>
+     * <li>Event is dispatched to a particular target node, which invokes
+     *   this code. Note that the event's stopPropagation flag is
+     *   cleared when dispatch begins; thereafter, if it has
+     *   been set before processing of a node commences, we instead
+     *   immediately advance to the DEFAULT phase.
+     * <li>The node's ancestors are established as destinations for events.
+     *   For capture and bubble purposes, node ancestry is determined at
+     *   the time dispatch starts. If an event handler alters the document
+     *   tree, that does not change which nodes will be informed of the event.
+     * <li>CAPTURING_PHASE: Ancestors are scanned, root to target, for
+     *   Capturing listeners. If found, they are invoked (see below).
+     * <li>AT_TARGET:
+     *   Event is dispatched to NON-CAPTURING listeners on the
+     *   target node. Note that capturing listeners on this node are _not_
+     *   invoked.
+     * <li>BUBBLING_PHASE: Ancestors are scanned, target to root, for
+     *   non-capturing listeners.
+     * <li>Default processing: Some DOMs have default behaviors bound to
+     *   specific nodes. If this DOM does, and if the event's preventDefault
+     *   flag has not been set, we now return to the target node and process
+     *   its default handler for this event, if any.
+     * </ol>
+     * <p>
+     * Note that registration of handlers during processing of an event does
+     * not take effect during this phase of this event; they will not be called
+     * until the next time this node is visited by dispatchEvent. On the other
+     * hand, removals take effect immediately.
+     * <p>
+     * If an event handler itself causes events to be dispatched, they are
+     * processed synchronously, before processing resumes
+     * on the event which triggered them. Please be aware that this may
+     * result in events arriving at listeners "out of order" relative
+     * to the actual sequence of requests.
+     * <p>
+     * Note that our implementation resets the event's stop/prevent flags
+     * when dispatch begins.
+     * I believe the DOM's intent is that event objects be redispatchable,
+     * though it isn't stated in those terms.
+     * @param node node to dispatch to
+     * @param event the event object to be dispatched to
+     *              registered EventListeners
+     * @return true if the event's <code>preventDefault()</code>
+     *              method was invoked by an EventListener; otherwise false.
+    */
+    protected boolean dispatchEvent(NodeImpl node, Event event) {
+        if (event == null) return false;
+
+        // Can't use anyone else's implementation, since there's no public
+        // API for setting the event's processing-state fields.
+        EventImpl evt = (EventImpl)event;
+
+        // VALIDATE -- must have been initialized at least once, must have
+        // a non-null non-blank name.
+        if(!evt.initialized || evt.type == null || evt.type.equals("")) {
+            String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "UNSPECIFIED_EVENT_TYPE_ERR", null);
+            throw new EventException(EventException.UNSPECIFIED_EVENT_TYPE_ERR, msg);
+        }
+
+        // If nobody is listening for this event, discard immediately
+        LCount lc = LCount.lookup(evt.getType());
+        if (lc.total == 0)
+            return evt.preventDefault;
+
+        // INITIALIZE THE EVENT'S DISPATCH STATUS
+        // (Note that Event objects are reusable in our implementation;
+        // that doesn't seem to be explicitly guaranteed in the DOM, but
+        // I believe it is the intent.)
+        evt.target = node;
+        evt.stopPropagation = false;
+        evt.preventDefault = false;
+
+        // Capture pre-event parentage chain, not including target;
+        // use pre-event-dispatch ancestors even if event handlers mutate
+        // document and change the target's context.
+        // Note that this is parents ONLY; events do not
+        // cross the Attr/Element "blood/brain barrier".
+        // DOMAttrModified. which looks like an exception,
+        // is issued to the Element rather than the Attr
+        // and causes a _second_ DOMSubtreeModified in the Element's
+        // tree.
+        Vector pv = new Vector(10,10);
+        Node p = node;
+        Node n = p.getParentNode();
+        while (n != null) {
+            pv.addElement(n);
+            p = n;
+            n = n.getParentNode();
+        }
+
+        // CAPTURING_PHASE:
+        if (lc.captures > 0) {
+            evt.eventPhase = Event.CAPTURING_PHASE;
+            // Ancestors are scanned, root to target, for
+            // Capturing listeners.
+            for (int j = pv.size() - 1; j >= 0; --j) {
+                if (evt.stopPropagation)
+                    break;  // Someone set the flag. Phase ends.
+
+                // Handle all capturing listeners on this node
+                NodeImpl nn = (NodeImpl) pv.elementAt(j);
+                evt.currentTarget = nn;
+                Vector nodeListeners = getEventListeners(nn);
+                if (nodeListeners != null) {
+                    Vector nl = (Vector) nodeListeners.clone();
+                    // call listeners in the order in which they got registered
+                    int nlsize = nl.size();
+                    for (int i = 0; i < nlsize; i++) {
+                        LEntry le = (LEntry) nl.elementAt(i);
+                        if (le.useCapture && le.type.equals(evt.type) &&
+                            nodeListeners.contains(le)) {
+                            try {
+                                le.listener.handleEvent(evt);
+                            }
+                            catch (Exception e) {
+                                // All exceptions are ignored.
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+
+        // Both AT_TARGET and BUBBLE use non-capturing listeners.
+        if (lc.bubbles > 0) {
+            // AT_TARGET PHASE: Event is dispatched to NON-CAPTURING listeners
+            // on the target node. Note that capturing listeners on the target
+            // node are _not_ invoked, even during the capture phase.
+            evt.eventPhase = Event.AT_TARGET;
+            evt.currentTarget = node;
+            Vector nodeListeners = getEventListeners(node);
+            if (!evt.stopPropagation && nodeListeners != null) {
+                Vector nl = (Vector) nodeListeners.clone();
+                // call listeners in the order in which they got registered
+                int nlsize = nl.size();
+                for (int i = 0; i < nlsize; i++) {
+                    LEntry le = (LEntry) nl.elementAt(i);
+                    if (!le.useCapture && le.type.equals(evt.type) &&
+                        nodeListeners.contains(le)) {
+                        try {
+                            le.listener.handleEvent(evt);
+                        }
+                        catch (Exception e) {
+                            // All exceptions are ignored.
+                        }
+                    }
+                }
+            }
+            // BUBBLING_PHASE: Ancestors are scanned, target to root, for
+            // non-capturing listeners. If the event's preventBubbling flag
+            // has been set before processing of a node commences, we
+            // instead immediately advance to the default phase.
+            // Note that not all events bubble.
+            if (evt.bubbles) {
+                evt.eventPhase = Event.BUBBLING_PHASE;
+                int pvsize = pv.size();
+                for (int j = 0; j < pvsize; j++) {
+                    if (evt.stopPropagation)
+                        break;  // Someone set the flag. Phase ends.
+
+                    // Handle all bubbling listeners on this node
+                    NodeImpl nn = (NodeImpl) pv.elementAt(j);
+                    evt.currentTarget = nn;
+                    nodeListeners = getEventListeners(nn);
+                    if (nodeListeners != null) {
+                        Vector nl = (Vector) nodeListeners.clone();
+                        // call listeners in the order in which they got
+                        // registered
+                        int nlsize = nl.size();
+                        for (int i = 0; i < nlsize; i++) {
+                            LEntry le = (LEntry) nl.elementAt(i);
+                            if (!le.useCapture && le.type.equals(evt.type) &&
+                                nodeListeners.contains(le)) {
+                                try {
+                                    le.listener.handleEvent(evt);
+                                }
+                                catch (Exception e) {
+                                    // All exceptions are ignored.
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        // DEFAULT PHASE: Some DOMs have default behaviors bound to specific
+        // nodes. If this DOM does, and if the event's preventDefault flag has
+        // not been set, we now return to the target node and process its
+        // default handler for this event, if any.
+        // No specific phase value defined, since this is DOM-internal
+        if (lc.defaults > 0 && (!evt.cancelable || !evt.preventDefault)) {
+            // evt.eventPhase = Event.DEFAULT_PHASE;
+            // evt.currentTarget = node;
+            // DO_DEFAULT_OPERATION
+        }
+
+        return evt.preventDefault;
+    } // dispatchEvent(NodeImpl,Event) :boolean
+
+    /**
+     * NON-DOM INTERNAL: DOMNodeInsertedIntoDocument and ...RemovedFrom...
+     * are dispatched to an entire subtree. This is the distribution code
+     * therefor. They DO NOT bubble, thanks be, but may be captured.
+     * <p>
+     * Similar to code in dispatchingEventToSubtree however this method
+     * is only used on the target node and does not start a dispatching chain
+     * on the sibling of the target node as this is not part of the subtree
+     * ***** At the moment I'm being sloppy and using the normal
+     * capture dispatcher on every node. This could be optimized hugely
+     * by writing a capture engine that tracks our position in the tree to
+     * update the capture chain without repeated chases up to root.
+     * @param n target node (that was directly inserted or removed)
+     * @param e event to be sent to that node and its subtree
+     */
+    protected void dispatchEventToSubtree(Node n, Event e) {
+
+        ((NodeImpl) n).dispatchEvent(e);
+        if (n.getNodeType() == Node.ELEMENT_NODE) {
+            NamedNodeMap a = n.getAttributes();
+            for (int i = a.getLength() - 1; i >= 0; --i)
+                dispatchingEventToSubtree(a.item(i), e);
+        }
+        dispatchingEventToSubtree(n.getFirstChild(), e);
+
+    } // dispatchEventToSubtree(NodeImpl,Node,Event) :void
+
+
+    /**
+     * Dispatches event to the target node's descendents recursively
+     *
+     * @param n node to dispatch to
+     * @param e event to be sent to that node and its subtree
+     */
+    protected void dispatchingEventToSubtree(Node n, Event e) {
+        if (n==null)
+                return;
+
+        // ***** Recursive implementation. This is excessively expensive,
+        // and should be replaced in conjunction with optimization
+        // mentioned above.
+        ((NodeImpl) n).dispatchEvent(e);
+        if (n.getNodeType() == Node.ELEMENT_NODE) {
+            NamedNodeMap a = n.getAttributes();
+            for (int i = a.getLength() - 1; i >= 0; --i)
+                dispatchingEventToSubtree(a.item(i), e);
+        }
+        dispatchingEventToSubtree(n.getFirstChild(), e);
+        dispatchingEventToSubtree(n.getNextSibling(), e);
+    }
+
+    /**
+     * NON-DOM INTERNAL: Return object for getEnclosingAttr. Carries
+     * (two values, the Attr node affected (if any) and its previous
+     * string value. Simple struct, no methods.
+     */
+    class EnclosingAttr implements Serializable {
+        private static final long serialVersionUID = 5208387723391647216L;
+        AttrImpl node;
+        String oldvalue;
+    }
+
+    EnclosingAttr savedEnclosingAttr;
+
+    /**
+     * NON-DOM INTERNAL: Convenience wrapper for calling
+     * dispatchAggregateEvents when the context was established
+     * by <code>savedEnclosingAttr</code>.
+     * @param node node to dispatch to
+     * @param ea description of Attr affected by current operation
+     */
+    protected void dispatchAggregateEvents(NodeImpl node, EnclosingAttr ea) {
+        if (ea != null)
+            dispatchAggregateEvents(node, ea.node, ea.oldvalue,
+                                    MutationEvent.MODIFICATION);
+        else
+            dispatchAggregateEvents(node, null, null, (short) 0);
+
+    } // dispatchAggregateEvents(NodeImpl,EnclosingAttr) :void
+
+    /**
+     * NON-DOM INTERNAL: Generate the "aggregated" post-mutation events
+     * DOMAttrModified and DOMSubtreeModified.
+     * Both of these should be issued only once for each user-requested
+     * mutation operation, even if that involves multiple changes to
+     * the DOM.
+     * For example, if a DOM operation makes multiple changes to a single
+     * Attr before returning, it would be nice to generate only one
+     * DOMAttrModified, and multiple changes over larger scope but within
+     * a recognizable single subtree might want to generate only one
+     * DOMSubtreeModified, sent to their lowest common ancestor.
+     * <p>
+     * To manage this, use the "internal" versions of insert and remove
+     * with MUTATION_LOCAL, then make an explicit call to this routine
+     * at the higher level. Some examples now exist in our code.
+     *
+     * @param node The node to dispatch to
+     * @param enclosingAttr The Attr node (if any) whose value has been changed
+     * as a result of the DOM operation. Null if none such.
+     * @param oldValue The String value previously held by the
+     * enclosingAttr. Ignored if none such.
+     * @param change Type of modification to the attr. See
+     * MutationEvent.attrChange
+     */
+    protected void dispatchAggregateEvents(NodeImpl node,
+                                           AttrImpl enclosingAttr,
+                                           String oldvalue, short change) {
+        // We have to send DOMAttrModified.
+        NodeImpl owner = null;
+        if (enclosingAttr != null) {
+            LCount lc = LCount.lookup(MutationEventImpl.DOM_ATTR_MODIFIED);
+            owner = (NodeImpl) enclosingAttr.getOwnerElement();
+            if (lc.total > 0) {
+                if (owner != null) {
+                    MutationEventImpl me =  new MutationEventImpl();
+                    me.initMutationEvent(MutationEventImpl.DOM_ATTR_MODIFIED,
+                                         true, false, enclosingAttr,
+                                         oldvalue,
+                                         enclosingAttr.getNodeValue(),
+                                         enclosingAttr.getNodeName(),
+                                         change);
+                    owner.dispatchEvent(me);
+                }
+            }
+        }
+        // DOMSubtreeModified gets sent to the lowest common root of a
+        // set of changes.
+        // "This event is dispatched after all other events caused by the
+        // mutation have been fired."
+        LCount lc = LCount.lookup(MutationEventImpl.DOM_SUBTREE_MODIFIED);
+        if (lc.total > 0) {
+            MutationEvent me =  new MutationEventImpl();
+            me.initMutationEvent(MutationEventImpl.DOM_SUBTREE_MODIFIED,
+                                 true, false, null, null,
+                                 null, null, (short) 0);
+
+            // If we're within an Attr, DStM gets sent to the Attr
+            // and to its owningElement. Otherwise we dispatch it
+            // locally.
+            if (enclosingAttr != null) {
+                dispatchEvent(enclosingAttr, me);
+                if (owner != null)
+                    dispatchEvent(owner, me);
+            }
+            else
+                dispatchEvent(node, me);
+        }
+    } // dispatchAggregateEvents(NodeImpl, AttrImpl,String) :void
+
+    /**
+     * NON-DOM INTERNAL: Pre-mutation context check, in
+     * preparation for later generating DOMAttrModified events.
+     * Determines whether this node is within an Attr
+     * @param node node to get enclosing attribute for
+     * @return either a description of that Attr, or null if none such.
+     */
+    protected void saveEnclosingAttr(NodeImpl node) {
+        savedEnclosingAttr = null;
+        // MUTATION PREPROCESSING AND PRE-EVENTS:
+        // If we're within the scope of an Attr and DOMAttrModified
+        // was requested, we need to preserve its previous value for
+        // that event.
+        LCount lc = LCount.lookup(MutationEventImpl.DOM_ATTR_MODIFIED);
+        if (lc.total > 0) {
+            NodeImpl eventAncestor = node;
+            while (true) {
+                if (eventAncestor == null)
+                    return;
+                int type = eventAncestor.getNodeType();
+                if (type == Node.ATTRIBUTE_NODE) {
+                    EnclosingAttr retval = new EnclosingAttr();
+                    retval.node = (AttrImpl) eventAncestor;
+                    retval.oldvalue = retval.node.getNodeValue();
+                    savedEnclosingAttr = retval;
+                    return;
+                }
+                else if (type == Node.ENTITY_REFERENCE_NODE)
+                    eventAncestor = eventAncestor.parentNode();
+                else if (type == Node.TEXT_NODE)
+                    eventAncestor = eventAncestor.parentNode();
+                else
+                    return;
+                // Any other parent means we're not in an Attr
+            }
+        }
+    } // saveEnclosingAttr(NodeImpl) :void
+
+    /**
+     * A method to be called when a character data node has been modified
+     */
+    void modifyingCharacterData(NodeImpl node, boolean replace) {
+        if (mutationEvents) {
+                if (!replace) {
+                        saveEnclosingAttr(node);
+                }
+        }
+    }
+
+    /**
+     * A method to be called when a character data node has been modified
+     */
+    void modifiedCharacterData(NodeImpl node, String oldvalue, String value, boolean replace) {
+        if (mutationEvents) {
+                if (!replace) {
+                        // MUTATION POST-EVENTS:
+                        LCount lc =
+                                LCount.lookup(MutationEventImpl.DOM_CHARACTER_DATA_MODIFIED);
+                        if (lc.total > 0) {
+                                MutationEvent me = new MutationEventImpl();
+                                me.initMutationEvent(
+                                        MutationEventImpl.DOM_CHARACTER_DATA_MODIFIED,
+                                        true, false, null,
+                                                                                oldvalue, value, null, (short) 0);
+                                dispatchEvent(node, me);
+                        }
+
+                        // Subroutine: Transmit DOMAttrModified and DOMSubtreeModified,
+                        // if required. (Common to most kinds of mutation)
+                        dispatchAggregateEvents(node, savedEnclosingAttr);
+                } // End mutation postprocessing
+        }
+    }
+
+    /**
+     * A method to be called when a character data node has been replaced
+     */
+    void replacedCharacterData(NodeImpl node, String oldvalue, String value) {
+        //now that we have finished replacing data, we need to perform the same actions
+        //that are required after a character data node has been modified
+        //send the value of false for replace parameter so that mutation
+        //events if appropriate will be initiated
+        modifiedCharacterData(node, oldvalue, value, false);
+    }
+
+
+
+    /**
+     * A method to be called when a node is about to be inserted in the tree.
+     */
+    void insertingNode(NodeImpl node, boolean replace) {
+        if (mutationEvents) {
+            if (!replace) {
+                saveEnclosingAttr(node);
+            }
+        }
+    }
+
+    /**
+     * A method to be called when a node has been inserted in the tree.
+     */
+    void insertedNode(NodeImpl node, NodeImpl newInternal, boolean replace) {
+        if (mutationEvents) {
+            // MUTATION POST-EVENTS:
+            // "Local" events (non-aggregated)
+            // New child is told it was inserted, and where
+            LCount lc = LCount.lookup(MutationEventImpl.DOM_NODE_INSERTED);
+            if (lc.total > 0) {
+                MutationEventImpl me = new MutationEventImpl();
+                me.initMutationEvent(MutationEventImpl.DOM_NODE_INSERTED,
+                                     true, false, node,
+                                     null, null, null, (short) 0);
+                dispatchEvent(newInternal, me);
+            }
+
+            // If within the Document, tell the subtree it's been added
+            // to the Doc.
+            lc = LCount.lookup(
+                            MutationEventImpl.DOM_NODE_INSERTED_INTO_DOCUMENT);
+            if (lc.total > 0) {
+                NodeImpl eventAncestor = node;
+                if (savedEnclosingAttr != null)
+                    eventAncestor = (NodeImpl)
+                        savedEnclosingAttr.node.getOwnerElement();
+                if (eventAncestor != null) { // Might have been orphan Attr
+                    NodeImpl p = eventAncestor;
+                    while (p != null) {
+                        eventAncestor = p; // Last non-null ancestor
+                        // In this context, ancestry includes
+                        // walking back from Attr to Element
+                        if (p.getNodeType() == ATTRIBUTE_NODE) {
+                            p = (NodeImpl) ((AttrImpl)p).getOwnerElement();
+                        }
+                        else {
+                            p = p.parentNode();
+                        }
+                    }
+                    if (eventAncestor.getNodeType() == Node.DOCUMENT_NODE){
+                        MutationEventImpl me = new MutationEventImpl();
+                        me.initMutationEvent(MutationEventImpl
+                                             .DOM_NODE_INSERTED_INTO_DOCUMENT,
+                                             false,false,null,null,
+                                             null,null,(short)0);
+                        dispatchEventToSubtree(newInternal, me);
+                    }
+                }
+            }
+            if (!replace) {
+                // Subroutine: Transmit DOMAttrModified and DOMSubtreeModified
+                // (Common to most kinds of mutation)
+                dispatchAggregateEvents(node, savedEnclosingAttr);
+            }
+        }
+
+        // notify the range of insertions
+        if (ranges != null) {
+            int size = ranges.size();
+            for (int i = 0; i != size; i++) {
+                ((RangeImpl)ranges.elementAt(i)).insertedNodeFromDOM(newInternal);
+            }
+        }
+    }
+
+    /**
+     * A method to be called when a node is about to be removed from the tree.
+     */
+    void removingNode(NodeImpl node, NodeImpl oldChild, boolean replace) {
+
+        // notify iterators
+        if (iterators != null) {
+            int size = iterators.size();
+            for (int i = 0; i != size; i++) {
+               ((NodeIteratorImpl)iterators.elementAt(i)).removeNode(oldChild);
+            }
+        }
+
+        // notify ranges
+        if (ranges != null) {
+            int size = ranges.size();
+            for (int i = 0; i != size; i++) {
+                ((RangeImpl)ranges.elementAt(i)).removeNode(oldChild);
+            }
+        }
+
+        // mutation events
+        if (mutationEvents) {
+            // MUTATION PREPROCESSING AND PRE-EVENTS:
+            // If we're within the scope of an Attr and DOMAttrModified
+            // was requested, we need to preserve its previous value for
+            // that event.
+            if (!replace) {
+                saveEnclosingAttr(node);
+            }
+            // Child is told that it is about to be removed
+            LCount lc = LCount.lookup(MutationEventImpl.DOM_NODE_REMOVED);
+            if (lc.total > 0) {
+                MutationEventImpl me= new MutationEventImpl();
+                me.initMutationEvent(MutationEventImpl.DOM_NODE_REMOVED,
+                                     true, false, node, null,
+                                     null, null, (short) 0);
+                dispatchEvent(oldChild, me);
+            }
+
+            // If within Document, child's subtree is informed that it's
+            // losing that status
+            lc = LCount.lookup(
+                             MutationEventImpl.DOM_NODE_REMOVED_FROM_DOCUMENT);
+            if (lc.total > 0) {
+                NodeImpl eventAncestor = this;
+                if(savedEnclosingAttr != null)
+                    eventAncestor = (NodeImpl)
+                        savedEnclosingAttr.node.getOwnerElement();
+                if (eventAncestor != null) { // Might have been orphan Attr
+                    for (NodeImpl p = eventAncestor.parentNode();
+                         p != null; p = p.parentNode()) {
+                        eventAncestor = p; // Last non-null ancestor
+                    }
+                    if (eventAncestor.getNodeType() == Node.DOCUMENT_NODE){
+                        MutationEventImpl me = new MutationEventImpl();
+                        me.initMutationEvent(
+                              MutationEventImpl.DOM_NODE_REMOVED_FROM_DOCUMENT,
+                                             false, false, null,
+                                             null, null, null, (short) 0);
+                        dispatchEventToSubtree(oldChild, me);
+                    }
+                }
+            }
+        } // End mutation preprocessing
+    }
+
+    /**
+     * A method to be called when a node has been removed from the tree.
+     */
+    void removedNode(NodeImpl node, boolean replace) {
+        if (mutationEvents) {
+            // MUTATION POST-EVENTS:
+            // Subroutine: Transmit DOMAttrModified and DOMSubtreeModified,
+            // if required. (Common to most kinds of mutation)
+            if (!replace) {
+                dispatchAggregateEvents(node, savedEnclosingAttr);
+            }
+        } // End mutation postprocessing
+    }
+
+    /**
+     * A method to be called when a node is about to be replaced in the tree.
+     */
+    void replacingNode(NodeImpl node) {
+        if (mutationEvents) {
+            saveEnclosingAttr(node);
+        }
+    }
+
+    /**
+     * A method to be called when character data is about to be replaced in the tree.
+     */
+    void replacingData (NodeImpl node) {
+        if (mutationEvents) {
+                        saveEnclosingAttr(node);
+        }
+    }
+
+    /**
+     * A method to be called when a node has been replaced in the tree.
+     */
+    void replacedNode(NodeImpl node) {
+        if (mutationEvents) {
+            dispatchAggregateEvents(node, savedEnclosingAttr);
+        }
+    }
+
+    /**
+     * A method to be called when an attribute value has been modified
+     */
+    void modifiedAttrValue(AttrImpl attr, String oldvalue) {
+        if (mutationEvents) {
+            // MUTATION POST-EVENTS:
+            dispatchAggregateEvents(attr, attr, oldvalue,
+                                    MutationEvent.MODIFICATION);
+        }
+    }
+
+    /**
+     * A method to be called when an attribute node has been set
+     */
+    void setAttrNode(AttrImpl attr, AttrImpl previous) {
+        if (mutationEvents) {
+            // MUTATION POST-EVENTS:
+            if (previous == null) {
+                dispatchAggregateEvents(attr.ownerNode, attr, null,
+                                        MutationEvent.ADDITION);
+            }
+            else {
+                dispatchAggregateEvents(attr.ownerNode, attr,
+                                        previous.getNodeValue(),
+                                        MutationEvent.MODIFICATION);
+            }
+        }
+    }
+
+    /**
+     * A method to be called when an attribute node has been removed
+     */
+    void removedAttrNode(AttrImpl attr, NodeImpl oldOwner, String name) {
+        // We can't use the standard dispatchAggregate, since it assumes
+        // that the Attr is still attached to an owner. This code is
+        // similar but dispatches to the previous owner, "element".
+        if (mutationEvents) {
+            // If we have to send DOMAttrModified (determined earlier),
+            // do so.
+            LCount lc = LCount.lookup(MutationEventImpl.DOM_ATTR_MODIFIED);
+            if (lc.total > 0) {
+                MutationEventImpl me= new MutationEventImpl();
+                me.initMutationEvent(MutationEventImpl.DOM_ATTR_MODIFIED,
+                                     true, false, attr,
+                                     attr.getNodeValue(), null, name,
+                                     MutationEvent.REMOVAL);
+                dispatchEvent(oldOwner, me);
+            }
+
+            // We can hand off to process DOMSubtreeModified, though.
+            // Note that only the Element needs to be informed; the
+            // Attr's subtree has not been changed by this operation.
+            dispatchAggregateEvents(oldOwner, null, null, (short) 0);
+        }
+    }
+
+
+    /**
+     * A method to be called when an attribute node has been renamed
+     */
+    void renamedAttrNode(Attr oldAt, Attr newAt) {
+        // REVISIT: To be implemented!!!
+    }
+
+    /**
+     * A method to be called when an element has been renamed
+     */
+    void renamedElement(Element oldEl, Element newEl) {
+        // REVISIT: To be implemented!!!
+    }
+
+} // class DocumentImpl