All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.xerces.dom.DocumentImpl Maven / Gradle / Ivy

There is a newer version: 0.4.3
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.xerces.dom;

import java.io.Serializable;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

import org.apache.xerces.dom.events.EventImpl;
import org.apache.xerces.dom.events.MouseEventImpl;
import org.apache.xerces.dom.events.MutationEventImpl;
import org.apache.xerces.dom.events.UIEventImpl;
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.UserDataHandler;
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.
 * 

* 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. *

* 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. *

* Note: 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 * @version $Id: DocumentImpl.java 890667 2009-12-15 06:41:08Z mrglavas $ * @since PR-DOM-Level-1-19980818. */ public class DocumentImpl extends CoreDocumentImpl implements DocumentTraversal, DocumentEvent, DocumentRange { // // Constants // /** Serialization version. */ static final long serialVersionUID = 515687835542616694L; // // Data // /** Node Iterators */ protected transient List iterators; /** Reference queue for cleared Node Iterator references */ protected transient ReferenceQueue iteratorReferenceQueue; /** Ranges */ protected transient List ranges; /** Reference queue for cleared Range references */ protected transient ReferenceQueue rangeReferenceQueue; /** 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 LinkedList(); iteratorReferenceQueue = new ReferenceQueue(); } removeStaleIteratorReferences(); iterators.add(new WeakReference(iterator, iteratorReferenceQueue)); 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.

* * This function is called from the NodeIterator#detach(). */ void removeNodeIterator(NodeIterator nodeIterator) { if (nodeIterator == null) return; if (iterators == null) return; removeStaleIteratorReferences(); Iterator i = iterators.iterator(); while (i.hasNext()) { Object iterator = ((Reference) i.next()).get(); if (iterator == nodeIterator) { i.remove(); return; } // Remove stale reference from the list. else if (iterator == null) { i.remove(); } } } /** * Remove stale iterator references from the iterator list. */ private void removeStaleIteratorReferences() { removeStaleReferences(iteratorReferenceQueue, iterators); } /** * Remove stale references from the given list. */ private void removeStaleReferences(ReferenceQueue queue, List list) { Reference ref = queue.poll(); int count = 0; while (ref != null) { ++count; ref = queue.poll(); } if (count > 0) { final Iterator i = list.iterator(); while (i.hasNext()) { Object o = ((Reference) i.next()).get(); if (o == null) { i.remove(); if (--count <= 0) { return; } } } } } // // DocumentRange methods // /** */ public Range createRange() { if (ranges == null) { ranges = new LinkedList(); rangeReferenceQueue = new ReferenceQueue(); } Range range = new RangeImpl(this); removeStaleRangeReferences(); ranges.add(new WeakReference(range, rangeReferenceQueue)); 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; removeStaleRangeReferences(); Iterator i = ranges.iterator(); while (i.hasNext()) { Object otherRange = ((Reference) i.next()).get(); if (otherRange == range) { i.remove(); return; } // Remove stale reference from the list. else if (otherRange == null) { i.remove(); } } } /** * A method to be called when some text was changed in a text node, * so that live objects can be notified. */ void replacedText(CharacterDataImpl node) { // notify ranges if (ranges != null) { notifyRangesReplacedText(node); } } private void notifyRangesReplacedText(CharacterDataImpl node) { removeStaleRangeReferences(); final Iterator i = ranges.iterator(); while (i.hasNext()) { RangeImpl range = (RangeImpl) ((Reference) i.next()).get(); if (range != null) { range.receiveReplacedText(node); } // Remove stale reference from the list. else { i.remove(); } } } /** * A method to be called when some text was deleted from a text node, * so that live objects can be notified. */ void deletedText(CharacterDataImpl node, int offset, int count) { // notify ranges if (ranges != null) { notifyRangesDeletedText(node, offset, count); } } private void notifyRangesDeletedText(CharacterDataImpl node, int offset, int count) { removeStaleRangeReferences(); final Iterator i = ranges.iterator(); while (i.hasNext()) { RangeImpl range = (RangeImpl) ((Reference) i.next()).get(); if (range != null) { range.receiveDeletedText(node, offset, count); } // Remove stale reference from the list. else { i.remove(); } } } /** * A method to be called when some text was inserted into a text node, * so that live objects can be notified. */ void insertedText(CharacterDataImpl node, int offset, int count) { // notify ranges if (ranges != null) { notifyRangesInsertedText(node, offset, count); } } private void notifyRangesInsertedText(CharacterDataImpl node, int offset, int count) { removeStaleRangeReferences(); final Iterator i = ranges.iterator(); while (i.hasNext()) { RangeImpl range = (RangeImpl) ((Reference) i.next()).get(); if (range != null) { range.receiveInsertedText(node, offset, count); } // Remove stale reference from the list. else { i.remove(); } } } /** * 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) { notifyRangesSplitData(node, newNode, offset); } } private void notifyRangesSplitData(Node node, Node newNode, int offset) { removeStaleRangeReferences(); final Iterator i = ranges.iterator(); while (i.hasNext()) { RangeImpl range = (RangeImpl) ((Reference) i.next()).get(); if (range != null) { range.receiveSplitData(node, newNode, offset); } // Remove stale reference from the list. else { i.remove(); } } } /** * Remove stale range references from the range list. */ private void removeStaleRangeReferences() { removeStaleReferences(rangeReferenceQueue, ranges); } // // DocumentEvent methods // /** * Introduced in DOM Level 2. Optional.

* 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(); } else if (type.equalsIgnoreCase("MutationEvents") || "MutationEvent".equals(type)) { return new MutationEventImpl(); } else if (type.equalsIgnoreCase("UIEvents") || "UIEvent".equals(type)) { return new UIEventImpl(); } else if (type.equalsIgnoreCase("MouseEvents") || "MouseEvent".equals(type)) { return new MouseEventImpl(); } 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. *

* 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.

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.length() == 0 || 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.

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.length() == 0 || 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.

* Distribution engine for DOM Level 2 Events. *

* Event propagation runs as follows: *

    *
  1. 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. *
  2. 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. *
  3. CAPTURING_PHASE: Ancestors are scanned, root to target, for * Capturing listeners. If found, they are invoked (see below). *
  4. AT_TARGET: * Event is dispatched to NON-CAPTURING listeners on the * target node. Note that capturing listeners on this node are _not_ * invoked. *
  5. BUBBLING_PHASE: Ancestors are scanned, target to root, for * non-capturing listeners. *
  6. 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. *
*

* 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. *

* 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. *

* 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 preventDefault() * 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.length() == 0) { 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. ArrayList pv = new ArrayList(10); Node p = node; Node n = p.getParentNode(); while (n != null) { pv.add(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.get(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.get(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. *

* 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 savedEnclosingAttr. * @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. *

* 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 */ 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) { mutationEventsModifiedCharacterData(node, oldvalue, value, replace); } } private void mutationEventsModifiedCharacterData(NodeImpl node, String oldvalue, String value, boolean replace) { 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) { mutationEventsInsertedNode(node, newInternal, replace); } // notify the range of insertions if (ranges != null) { notifyRangesInsertedNode(newInternal); } } private void mutationEventsInsertedNode(NodeImpl node, NodeImpl newInternal, boolean replace) { // 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); } } private void notifyRangesInsertedNode(NodeImpl newInternal) { removeStaleRangeReferences(); final Iterator i = ranges.iterator(); while (i.hasNext()) { RangeImpl range = (RangeImpl) ((Reference) i.next()).get(); if (range != null) { range.insertedNodeFromDOM(newInternal); } // Remove stale reference from the list. else { i.remove(); } } } /** * 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) { notifyIteratorsRemovingNode(oldChild); } // notify ranges if (ranges != null) { notifyRangesRemovingNode(oldChild); } // mutation events if (mutationEvents) { mutationEventsRemovingNode(node, oldChild, replace); } } private void notifyIteratorsRemovingNode(NodeImpl oldChild) { removeStaleIteratorReferences(); final Iterator i = iterators.iterator(); while (i.hasNext()) { NodeIteratorImpl iterator = (NodeIteratorImpl) ((Reference) i.next()).get(); if (iterator != null) { iterator.removeNode(oldChild); } // Remove stale reference from the list. else { i.remove(); } } } private void notifyRangesRemovingNode(NodeImpl oldChild) { removeStaleRangeReferences(); final Iterator i = ranges.iterator(); while (i.hasNext()) { RangeImpl range = (RangeImpl) ((Reference) i.next()).get(); if (range != null) { range.removeNode(oldChild); } // Remove stale reference from the list. else { i.remove(); } } } private void mutationEventsRemovingNode(NodeImpl node, NodeImpl oldChild, boolean replace) { // 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) { mutationEventsRemovedAttrNode(attr, oldOwner, name); } } private void mutationEventsRemovedAttrNode(AttrImpl attr, NodeImpl oldOwner, String name) { // 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





© 2015 - 2024 Weber Informatics LLC | Privacy Policy