--- /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