1 /*
   2  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
   3  */
   4 /*
   5  * Licensed to the Apache Software Foundation (ASF) under one or more
   6  * contributor license agreements.  See the NOTICE file distributed with
   7  * this work for additional information regarding copyright ownership.
   8  * The ASF licenses this file to You under the Apache License, Version 2.0
   9  * (the "License"); you may not use this file except in compliance with
  10  * the License.  You may obtain a copy of the License at
  11  *
  12  *      http://www.apache.org/licenses/LICENSE-2.0
  13  *
  14  * Unless required by applicable law or agreed to in writing, software
  15  * distributed under the License is distributed on an "AS IS" BASIS,
  16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17  * See the License for the specific language governing permissions and
  18  * limitations under the License.
  19  */
  20 
  21 package com.sun.org.apache.xerces.internal.dom;
  22 
  23 import com.sun.org.apache.xerces.internal.dom.events.EventImpl;
  24 import com.sun.org.apache.xerces.internal.dom.events.MutationEventImpl;
  25 import java.io.IOException;
  26 import java.io.ObjectInputStream;
  27 import java.io.ObjectOutputStream;
  28 import java.io.ObjectStreamField;
  29 import java.io.Serializable;
  30 import java.util.ArrayList;
  31 import java.util.HashMap;
  32 import java.util.Hashtable;
  33 import java.util.List;
  34 import java.util.Map;
  35 import java.util.Vector;
  36 import org.w3c.dom.Attr;
  37 import org.w3c.dom.DOMException;
  38 import org.w3c.dom.DOMImplementation;
  39 import org.w3c.dom.DocumentType;
  40 import org.w3c.dom.Element;
  41 import org.w3c.dom.NamedNodeMap;
  42 import org.w3c.dom.Node;
  43 import org.w3c.dom.UserDataHandler;
  44 import org.w3c.dom.events.DocumentEvent;
  45 import org.w3c.dom.events.Event;
  46 import org.w3c.dom.events.EventException;
  47 import org.w3c.dom.events.EventListener;
  48 import org.w3c.dom.events.MutationEvent;
  49 import org.w3c.dom.ranges.DocumentRange;
  50 import org.w3c.dom.ranges.Range;
  51 import org.w3c.dom.traversal.DocumentTraversal;
  52 import org.w3c.dom.traversal.NodeFilter;
  53 import org.w3c.dom.traversal.NodeIterator;
  54 import org.w3c.dom.traversal.TreeWalker;
  55 
  56 
  57 /**
  58  * The Document interface represents the entire HTML or XML document.
  59  * Conceptually, it is the root of the document tree, and provides the
  60  * primary access to the document's data.
  61  * <P>
  62  * Since elements, text nodes, comments, processing instructions,
  63  * etc. cannot exist outside the context of a Document, the Document
  64  * interface also contains the factory methods needed to create these
  65  * objects. The Node objects created have a ownerDocument attribute
  66  * which associates them with the Document within whose context they
  67  * were created.
  68  * <p>
  69  * The DocumentImpl class also implements the DOM Level 2 DocumentTraversal
  70  * interface. This interface is comprised of factory methods needed to
  71  * create NodeIterators and TreeWalkers. The process of creating NodeIterator
  72  * objects also adds these references to this document.
  73  * After finishing with an iterator it is important to remove the object
  74  * using the remove methods in this implementation. This allows the release of
  75  * the references from the iterator objects to the DOM Nodes.
  76  * <p>
  77  * <b>Note:</b> When any node in the document is serialized, the
  78  * entire document is serialized along with it.
  79  *
  80  * @xerces.internal
  81  *
  82  * @author Arnaud  Le Hors, IBM
  83  * @author Joe Kesselman, IBM
  84  * @author Andy Clark, IBM
  85  * @author Ralf Pfeiffer, IBM
  86  * @since  PR-DOM-Level-1-19980818.
  87  */
  88 public class DocumentImpl
  89     extends CoreDocumentImpl
  90     implements DocumentTraversal, DocumentEvent, DocumentRange {
  91 
  92     //
  93     // Constants
  94     //
  95 
  96     /** Serialization version. */
  97     static final long serialVersionUID = 515687835542616694L;
  98 
  99     //
 100     // Data
 101     //
 102 
 103     /** Iterators */
 104     // REVISIT: Should this be transient? -Ac
 105     protected List<NodeIterator> iterators;
 106 
 107      /** Ranges */
 108     // REVISIT: Should this be transient? -Ac
 109     protected List<Range> ranges;
 110 
 111     /** Table for event listeners registered to this document nodes. */
 112     protected Map<NodeImpl, List<LEntry>> eventListeners;
 113 
 114     /** Bypass mutation events firing. */
 115     protected boolean mutationEvents = false;
 116 
 117 
 118     /**
 119      * @serialField iterators Vector Node iterators
 120      * @serialField ranges Vector ranges
 121      * @serialField eventListeners Hashtable Event listeners
 122      * @serialField mutationEvents boolean Bypass mutation events firing
 123      */
 124     private static final ObjectStreamField[] serialPersistentFields =
 125         new ObjectStreamField[] {
 126             new ObjectStreamField("iterators", Vector.class),
 127             new ObjectStreamField("ranges", Vector.class),
 128             new ObjectStreamField("eventListeners", Hashtable.class),
 129             new ObjectStreamField("mutationEvents", boolean.class),
 130         };
 131 
 132     //
 133     // Constructors
 134     //
 135 
 136     /**
 137      * NON-DOM: Actually creating a Document is outside the DOM's spec,
 138      * since it has to operate in terms of a particular implementation.
 139      */
 140     public DocumentImpl() {
 141         super();
 142     }
 143 
 144     /** Constructor. */
 145     public DocumentImpl(boolean grammarAccess) {
 146         super(grammarAccess);
 147     }
 148 
 149     /**
 150      * For DOM2 support.
 151      * The createDocument factory method is in DOMImplementation.
 152      */
 153     public DocumentImpl(DocumentType doctype)
 154     {
 155         super(doctype);
 156     }
 157 
 158     /** For DOM2 support. */
 159     public DocumentImpl(DocumentType doctype, boolean grammarAccess) {
 160         super(doctype, grammarAccess);
 161     }
 162 
 163     //
 164     // Node methods
 165     //
 166 
 167     /**
 168      * Deep-clone a document, including fixing ownerDoc for the cloned
 169      * children. Note that this requires bypassing the WRONG_DOCUMENT_ERR
 170      * protection. I've chosen to implement it by calling importNode
 171      * which is DOM Level 2.
 172      *
 173      * @return org.w3c.dom.Node
 174      * @param deep boolean, iff true replicate children
 175      */
 176     public Node cloneNode(boolean deep) {
 177 
 178         DocumentImpl newdoc = new DocumentImpl();
 179         callUserDataHandlers(this, newdoc, UserDataHandler.NODE_CLONED);
 180         cloneNode(newdoc, deep);
 181 
 182         // experimental
 183         newdoc.mutationEvents = mutationEvents;
 184 
 185         return newdoc;
 186 
 187     } // cloneNode(boolean):Node
 188 
 189     /**
 190      * Retrieve information describing the abilities of this particular
 191      * DOM implementation. Intended to support applications that may be
 192      * using DOMs retrieved from several different sources, potentially
 193      * with different underlying representations.
 194      */
 195     public DOMImplementation getImplementation() {
 196         // Currently implemented as a singleton, since it's hardcoded
 197         // information anyway.
 198         return DOMImplementationImpl.getDOMImplementation();
 199     }
 200 
 201     //
 202     // DocumentTraversal methods
 203     //
 204 
 205     /**
 206      * NON-DOM extension:
 207      * Create and return a NodeIterator. The NodeIterator is
 208      * added to a list of NodeIterators so that it can be
 209      * removed to free up the DOM Nodes it references.
 210      *
 211      * @param root The root of the iterator.
 212      * @param whatToShow The whatToShow mask.
 213      * @param filter The NodeFilter installed. Null means no filter.
 214      */
 215     public NodeIterator createNodeIterator(Node root,
 216                                            short whatToShow,
 217                                            NodeFilter filter)
 218     {
 219         return createNodeIterator(root, whatToShow, filter, true);
 220     }
 221 
 222     /**
 223      * Create and return a NodeIterator. The NodeIterator is
 224      * added to a list of NodeIterators so that it can be
 225      * removed to free up the DOM Nodes it references.
 226      *
 227      * @param root The root of the iterator.
 228      * @param whatToShow The whatToShow mask.
 229      * @param filter The NodeFilter installed. Null means no filter.
 230      * @param entityReferenceExpansion true to expand the contents of
 231      *                                 EntityReference nodes
 232      * @since WD-DOM-Level-2-19990923
 233      */
 234     public NodeIterator createNodeIterator(Node root,
 235                                            int whatToShow,
 236                                            NodeFilter filter,
 237                                            boolean entityReferenceExpansion)
 238     {
 239 
 240         if (root == null) {
 241                   String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_SUPPORTED_ERR", null);
 242                   throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg);
 243         }
 244 
 245         NodeIterator iterator = new NodeIteratorImpl(this,
 246                                                      root,
 247                                                      whatToShow,
 248                                                      filter,
 249                                                      entityReferenceExpansion);
 250         if (iterators == null) {
 251             iterators = new ArrayList<>();
 252         }
 253 
 254         iterators.add(iterator);
 255 
 256         return iterator;
 257     }
 258 
 259     /**
 260      * NON-DOM extension:
 261      * Create and return a TreeWalker.
 262      *
 263      * @param root The root of the iterator.
 264      * @param whatToShow The whatToShow mask.
 265      * @param filter The NodeFilter installed. Null means no filter.
 266      */
 267     public TreeWalker createTreeWalker(Node root,
 268                                        short whatToShow,
 269                                        NodeFilter filter)
 270     {
 271         return createTreeWalker(root, whatToShow, filter, true);
 272     }
 273     /**
 274      * Create and return a TreeWalker.
 275      *
 276      * @param root The root of the iterator.
 277      * @param whatToShow The whatToShow mask.
 278      * @param filter The NodeFilter installed. Null means no filter.
 279      * @param entityReferenceExpansion true to expand the contents of
 280      *                                 EntityReference nodes
 281      * @since WD-DOM-Level-2-19990923
 282      */
 283     public TreeWalker createTreeWalker(Node root,
 284                                        int whatToShow,
 285                                        NodeFilter filter,
 286                                        boolean entityReferenceExpansion)
 287     {
 288         if (root == null) {
 289             String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_SUPPORTED_ERR", null);
 290             throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg);
 291         }
 292         return new TreeWalkerImpl(root, whatToShow, filter,
 293                                   entityReferenceExpansion);
 294     }
 295 
 296     //
 297     // Not DOM Level 2. Support DocumentTraversal methods.
 298     //
 299 
 300     /** This is not called by the developer client. The
 301      *  developer client uses the detach() function on the
 302      *  NodeIterator itself. <p>
 303      *
 304      *  This function is called from the NodeIterator#detach().
 305      */
 306      void removeNodeIterator(NodeIterator nodeIterator) {
 307 
 308         if (nodeIterator == null) return;
 309         if (iterators == null) return;
 310 
 311         iterators.remove(nodeIterator);
 312     }
 313 
 314     //
 315     // DocumentRange methods
 316     //
 317     /**
 318      */
 319     public Range createRange() {
 320 
 321         if (ranges == null) {
 322             ranges = new ArrayList<>();
 323         }
 324 
 325         Range range = new RangeImpl(this);
 326         ranges.add(range);
 327 
 328         return range;
 329 
 330     }
 331 
 332     /** Not a client function. Called by Range.detach(),
 333      *  so a Range can remove itself from the list of
 334      *  Ranges.
 335      */
 336     void removeRange(Range range) {
 337 
 338         if (range == null) return;
 339         if (ranges == null) return;
 340 
 341         ranges.remove(range);
 342     }
 343 
 344     /**
 345      * A method to be called when some text was changed in a text node,
 346      * so that live objects can be notified.
 347      */
 348     void replacedText(NodeImpl node) {
 349         // notify ranges
 350         if (ranges != null) {
 351             int size = ranges.size();
 352             for (int i = 0; i != size; i++) {
 353                 ((RangeImpl)ranges.get(i)).receiveReplacedText(node);
 354             }
 355         }
 356     }
 357 
 358     /**
 359      * A method to be called when some text was deleted from a text node,
 360      * so that live objects can be notified.
 361      */
 362     void deletedText(NodeImpl node, int offset, int count) {
 363         // notify ranges
 364         if (ranges != null) {
 365             int size = ranges.size();
 366             for (int i = 0; i != size; i++) {
 367                 ((RangeImpl)ranges.get(i)).receiveDeletedText(node,
 368                                                                 offset, count);
 369             }
 370         }
 371     }
 372 
 373     /**
 374      * A method to be called when some text was inserted into a text node,
 375      * so that live objects can be notified.
 376      */
 377     void insertedText(NodeImpl node, int offset, int count) {
 378         // notify ranges
 379         if (ranges != null) {
 380             int size = ranges.size();
 381             for (int i = 0; i != size; i++) {
 382                 ((RangeImpl)ranges.get(i)).receiveInsertedText(node,
 383                                                                 offset, count);
 384             }
 385         }
 386     }
 387 
 388     /**
 389      * A method to be called when a text node has been split,
 390      * so that live objects can be notified.
 391      */
 392     void splitData(Node node, Node newNode, int offset) {
 393         // notify ranges
 394         if (ranges != null) {
 395             int size = ranges.size();
 396             for (int i = 0; i != size; i++) {
 397                 ((RangeImpl)ranges.get(i)).receiveSplitData(node,
 398                                                               newNode, offset);
 399             }
 400         }
 401     }
 402 
 403     //
 404     // DocumentEvent methods
 405     //
 406 
 407     /**
 408      * Introduced in DOM Level 2. Optional. <p>
 409      * Create and return Event objects.
 410      *
 411      * @param type The eventType parameter specifies the type of Event
 412      * interface to be created.  If the Event interface specified is supported
 413      * by the implementation this method will return a new Event of the
 414      * interface type requested. If the Event is to be dispatched via the
 415      * dispatchEvent method the appropriate event init method must be called
 416      * after creation in order to initialize the Event's values.  As an
 417      * example, a user wishing to synthesize some kind of Event would call
 418      * createEvent with the parameter "Events". The initEvent method could then
 419      * be called on the newly created Event to set the specific type of Event
 420      * to be dispatched and set its context information.
 421      * @return Newly created Event
 422      * @exception DOMException NOT_SUPPORTED_ERR: Raised if the implementation
 423      * does not support the type of Event interface requested
 424      * @since WD-DOM-Level-2-19990923
 425      */
 426     public Event createEvent(String type)
 427         throws DOMException {
 428             if (type.equalsIgnoreCase("Events") || "Event".equals(type))
 429                 return new EventImpl();
 430             if (type.equalsIgnoreCase("MutationEvents") ||
 431                 "MutationEvent".equals(type))
 432                 return new MutationEventImpl();
 433             else {
 434             String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_SUPPORTED_ERR", null);
 435                 throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg);
 436         }
 437         }
 438 
 439     /**
 440      * Sets whether the DOM implementation generates mutation events
 441      * upon operations.
 442      */
 443     void setMutationEvents(boolean set) {
 444         mutationEvents = set;
 445     }
 446 
 447     /**
 448      * Returns true if the DOM implementation generates mutation events.
 449      */
 450     boolean getMutationEvents() {
 451         return mutationEvents;
 452     }
 453 
 454     /**
 455      * Store event listener registered on a given node
 456      * This is another place where we could use weak references! Indeed, the
 457      * node here won't be GC'ed as long as some listener is registered on it,
 458      * since the eventsListeners table will have a reference to the node.
 459      */
 460     private void setEventListeners(NodeImpl n, List<LEntry> listeners) {
 461         if (eventListeners == null) {
 462             eventListeners = new HashMap<>();
 463         }
 464         if (listeners == null) {
 465             eventListeners.remove(n);
 466             if (eventListeners.isEmpty()) {
 467                 // stop firing events when there isn't any listener
 468                 mutationEvents = false;
 469             }
 470         } else {
 471             eventListeners.put(n, listeners);
 472             // turn mutation events on
 473             mutationEvents = true;
 474         }
 475     }
 476 
 477     /**
 478      * Retreive event listener registered on a given node
 479      */
 480     private List<LEntry> getEventListeners(NodeImpl n) {
 481         if (eventListeners == null) {
 482             return null;
 483         }
 484         return eventListeners.get(n);
 485     }
 486 
 487     //
 488     // EventTarget support (public and internal)
 489     //
 490 
 491     //
 492     // Constants
 493     //
 494 
 495     /*
 496      * NON-DOM INTERNAL: Class LEntry is just a struct used to represent
 497      * event listeners registered with this node. Copies of this object
 498      * are hung from the nodeListeners Vector.
 499      * <p>
 500      * I considered using two vectors -- one for capture,
 501      * one for bubble -- but decided that since the list of listeners
 502      * is probably short in most cases, it might not be worth spending
 503      * the space. ***** REVISIT WHEN WE HAVE MORE EXPERIENCE.
 504      */
 505     class LEntry implements Serializable {
 506 
 507         private static final long serialVersionUID = -8426757059492421631L;
 508         String type;
 509         EventListener listener;
 510         boolean useCapture;
 511 
 512         /** NON-DOM INTERNAL: Constructor for Listener list Entry
 513          * @param type Event name (NOT event group!) to listen for.
 514          * @param listener Who gets called when event is dispatched
 515          * @param useCaptue True iff listener is registered on
 516          *  capturing phase rather than at-target or bubbling
 517          */
 518         LEntry(String type, EventListener listener, boolean useCapture)
 519         {
 520             this.type = type;
 521             this.listener = listener;
 522             this.useCapture = useCapture;
 523         }
 524 
 525     } // LEntry
 526 
 527     /**
 528      * Introduced in DOM Level 2. <p> Register an event listener with this
 529      * Node. A listener may be independently registered as both Capturing and
 530      * Bubbling, but may only be registered once per role; redundant
 531      * registrations are ignored.
 532      * @param node node to add listener to
 533      * @param type Event name (NOT event group!) to listen for.
 534      * @param listener Who gets called when event is dispatched
 535      * @param useCapture True iff listener is registered on
 536      *  capturing phase rather than at-target or bubbling
 537      */
 538     @Override
 539     protected void addEventListener(NodeImpl node, String type,
 540                                     EventListener listener, boolean useCapture)
 541     {
 542         // We can't dispatch to blank type-name, and of course we need
 543         // a listener to dispatch to
 544         if (type == null || type.equals("") || listener == null)
 545             return;
 546 
 547         // Each listener may be registered only once per type per phase.
 548         // Simplest way to code that is to zap the previous entry, if any.
 549         removeEventListener(node, type, listener, useCapture);
 550 
 551         List<LEntry> nodeListeners = getEventListeners(node);
 552         if(nodeListeners == null) {
 553             nodeListeners = new ArrayList<>();
 554             setEventListeners(node, nodeListeners);
 555         }
 556         nodeListeners.add(new LEntry(type, listener, useCapture));
 557 
 558         // Record active listener
 559         LCount lc = LCount.lookup(type);
 560         if (useCapture) {
 561             ++lc.captures;
 562             ++lc.total;
 563         }
 564         else {
 565             ++lc.bubbles;
 566             ++lc.total;
 567         }
 568 
 569     } // addEventListener(NodeImpl,String,EventListener,boolean) :void
 570 
 571     /**
 572      * Introduced in DOM Level 2. <p> Deregister an event listener previously
 573      * registered with this Node.  A listener must be independently removed
 574      * from the Capturing and Bubbling roles. Redundant removals (of listeners
 575      * not currently registered for this role) are ignored.
 576      * @param node node to remove listener from
 577      * @param type Event name (NOT event group!) to listen for.
 578      * @param listener Who gets called when event is dispatched
 579      * @param useCapture True iff listener is registered on
 580      *  capturing phase rather than at-target or bubbling
 581      */
 582     @Override
 583     protected void removeEventListener(NodeImpl node, String type,
 584                                        EventListener listener,
 585                                        boolean useCapture)
 586     {
 587         // If this couldn't be a valid listener registration, ignore request
 588         if (type == null || type.equals("") || listener == null)
 589             return;
 590         List<LEntry> nodeListeners = getEventListeners(node);
 591         if (nodeListeners == null)
 592             return;
 593 
 594         // Note that addListener has previously ensured that
 595         // each listener may be registered only once per type per phase.
 596         // count-down is OK for deletions!
 597         for (int i = nodeListeners.size() - 1; i >= 0; --i) {
 598             LEntry le = nodeListeners.get(i);
 599             if (le.useCapture == useCapture && le.listener == listener &&
 600                 le.type.equals(type)) {
 601                 nodeListeners.remove(i);
 602                 // Storage management: Discard empty listener lists
 603                 if (nodeListeners.isEmpty())
 604                     setEventListeners(node, null);
 605 
 606                 // Remove active listener
 607                 LCount lc = LCount.lookup(type);
 608                 if (useCapture) {
 609                     --lc.captures;
 610                     --lc.total;
 611                 }
 612                 else {
 613                     --lc.bubbles;
 614                     --lc.total;
 615                 }
 616 
 617                 break;  // Found it; no need to loop farther.
 618             }
 619         }
 620     } // removeEventListener(NodeImpl,String,EventListener,boolean) :void
 621 
 622     @Override
 623     protected void copyEventListeners(NodeImpl src, NodeImpl tgt) {
 624         List<LEntry> nodeListeners = getEventListeners(src);
 625         if (nodeListeners == null) {
 626             return;
 627         }
 628         setEventListeners(tgt, new ArrayList<>(nodeListeners));
 629     }
 630 
 631     /**
 632      * Introduced in DOM Level 2. <p>
 633      * Distribution engine for DOM Level 2 Events.
 634      * <p>
 635      * Event propagation runs as follows:
 636      * <ol>
 637      * <li>Event is dispatched to a particular target node, which invokes
 638      *   this code. Note that the event's stopPropagation flag is
 639      *   cleared when dispatch begins; thereafter, if it has
 640      *   been set before processing of a node commences, we instead
 641      *   immediately advance to the DEFAULT phase.
 642      * <li>The node's ancestors are established as destinations for events.
 643      *   For capture and bubble purposes, node ancestry is determined at
 644      *   the time dispatch starts. If an event handler alters the document
 645      *   tree, that does not change which nodes will be informed of the event.
 646      * <li>CAPTURING_PHASE: Ancestors are scanned, root to target, for
 647      *   Capturing listeners. If found, they are invoked (see below).
 648      * <li>AT_TARGET:
 649      *   Event is dispatched to NON-CAPTURING listeners on the
 650      *   target node. Note that capturing listeners on this node are _not_
 651      *   invoked.
 652      * <li>BUBBLING_PHASE: Ancestors are scanned, target to root, for
 653      *   non-capturing listeners.
 654      * <li>Default processing: Some DOMs have default behaviors bound to
 655      *   specific nodes. If this DOM does, and if the event's preventDefault
 656      *   flag has not been set, we now return to the target node and process
 657      *   its default handler for this event, if any.
 658      * </ol>
 659      * <p>
 660      * Note that registration of handlers during processing of an event does
 661      * not take effect during this phase of this event; they will not be called
 662      * until the next time this node is visited by dispatchEvent. On the other
 663      * hand, removals take effect immediately.
 664      * <p>
 665      * If an event handler itself causes events to be dispatched, they are
 666      * processed synchronously, before processing resumes
 667      * on the event which triggered them. Please be aware that this may
 668      * result in events arriving at listeners "out of order" relative
 669      * to the actual sequence of requests.
 670      * <p>
 671      * Note that our implementation resets the event's stop/prevent flags
 672      * when dispatch begins.
 673      * I believe the DOM's intent is that event objects be redispatchable,
 674      * though it isn't stated in those terms.
 675      * @param node node to dispatch to
 676      * @param event the event object to be dispatched to
 677      *              registered EventListeners
 678      * @return true if the event's <code>preventDefault()</code>
 679      *              method was invoked by an EventListener; otherwise false.
 680     */
 681     @Override
 682     protected boolean dispatchEvent(NodeImpl node, Event event) {
 683         if (event == null) return false;
 684 
 685         // Can't use anyone else's implementation, since there's no public
 686         // API for setting the event's processing-state fields.
 687         EventImpl evt = (EventImpl)event;
 688 
 689         // VALIDATE -- must have been initialized at least once, must have
 690         // a non-null non-blank name.
 691         if(!evt.initialized || evt.type == null || evt.type.equals("")) {
 692             String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "UNSPECIFIED_EVENT_TYPE_ERR", null);
 693             throw new EventException(EventException.UNSPECIFIED_EVENT_TYPE_ERR, msg);
 694         }
 695 
 696         // If nobody is listening for this event, discard immediately
 697         LCount lc = LCount.lookup(evt.getType());
 698         if (lc.total == 0)
 699             return evt.preventDefault;
 700 
 701         // INITIALIZE THE EVENT'S DISPATCH STATUS
 702         // (Note that Event objects are reusable in our implementation;
 703         // that doesn't seem to be explicitly guaranteed in the DOM, but
 704         // I believe it is the intent.)
 705         evt.target = node;
 706         evt.stopPropagation = false;
 707         evt.preventDefault = false;
 708 
 709         // Capture pre-event parentage chain, not including target;
 710         // use pre-event-dispatch ancestors even if event handlers mutate
 711         // document and change the target's context.
 712         // Note that this is parents ONLY; events do not
 713         // cross the Attr/Element "blood/brain barrier".
 714         // DOMAttrModified. which looks like an exception,
 715         // is issued to the Element rather than the Attr
 716         // and causes a _second_ DOMSubtreeModified in the Element's
 717         // tree.
 718         List<Node> pv = new ArrayList<>(10);
 719         Node p = node;
 720         Node n = p.getParentNode();
 721         while (n != null) {
 722             pv.add(n);
 723             p = n;
 724             n = n.getParentNode();
 725         }
 726 
 727         // CAPTURING_PHASE:
 728         if (lc.captures > 0) {
 729             evt.eventPhase = Event.CAPTURING_PHASE;
 730             // Ancestors are scanned, root to target, for
 731             // Capturing listeners.
 732             for (int j = pv.size() - 1; j >= 0; --j) {
 733                 if (evt.stopPropagation)
 734                     break;  // Someone set the flag. Phase ends.
 735 
 736                 // Handle all capturing listeners on this node
 737                 NodeImpl nn = (NodeImpl) pv.get(j);
 738                 evt.currentTarget = nn;
 739                 List<LEntry> nodeListeners = getEventListeners(nn);
 740                 if (nodeListeners != null) {
 741                     List<LEntry> nl = (List)((ArrayList)nodeListeners).clone();
 742                     // call listeners in the order in which they got registered
 743                     int nlsize = nl.size();
 744                     for (int i = 0; i < nlsize; i++) {
 745                         LEntry le = nl.get(i);
 746                         if (le.useCapture && le.type.equals(evt.type) &&
 747                             nodeListeners.contains(le)) {
 748                             try {
 749                                 le.listener.handleEvent(evt);
 750                             }
 751                             catch (Exception e) {
 752                                 // All exceptions are ignored.
 753                             }
 754                         }
 755                     }
 756                 }
 757             }
 758         }
 759 
 760 
 761         // Both AT_TARGET and BUBBLE use non-capturing listeners.
 762         if (lc.bubbles > 0) {
 763             // AT_TARGET PHASE: Event is dispatched to NON-CAPTURING listeners
 764             // on the target node. Note that capturing listeners on the target
 765             // node are _not_ invoked, even during the capture phase.
 766             evt.eventPhase = Event.AT_TARGET;
 767             evt.currentTarget = node;
 768             List<LEntry> nodeListeners = getEventListeners(node);
 769             if (!evt.stopPropagation && nodeListeners != null) {
 770                 List<LEntry> nl = (List)((ArrayList)nodeListeners).clone();
 771                 // call listeners in the order in which they got registered
 772                 int nlsize = nl.size();
 773                 for (int i = 0; i < nlsize; i++) {
 774                     LEntry le = (LEntry) nl.get(i);
 775                     if (!le.useCapture && le.type.equals(evt.type) &&
 776                         nodeListeners.contains(le)) {
 777                         try {
 778                             le.listener.handleEvent(evt);
 779                         }
 780                         catch (Exception e) {
 781                             // All exceptions are ignored.
 782                         }
 783                     }
 784                 }
 785             }
 786             // BUBBLING_PHASE: Ancestors are scanned, target to root, for
 787             // non-capturing listeners. If the event's preventBubbling flag
 788             // has been set before processing of a node commences, we
 789             // instead immediately advance to the default phase.
 790             // Note that not all events bubble.
 791             if (evt.bubbles) {
 792                 evt.eventPhase = Event.BUBBLING_PHASE;
 793                 int pvsize = pv.size();
 794                 for (int j = 0; j < pvsize; j++) {
 795                     if (evt.stopPropagation)
 796                         break;  // Someone set the flag. Phase ends.
 797 
 798                     // Handle all bubbling listeners on this node
 799                     NodeImpl nn = (NodeImpl) pv.get(j);
 800                     evt.currentTarget = nn;
 801                     nodeListeners = getEventListeners(nn);
 802                     if (nodeListeners != null) {
 803                         List<LEntry> nl = (List)((ArrayList)nodeListeners).clone();
 804                         // call listeners in the order in which they got
 805                         // registered
 806                         int nlsize = nl.size();
 807                         for (int i = 0; i < nlsize; i++) {
 808                             LEntry le = nl.get(i);
 809                             if (!le.useCapture && le.type.equals(evt.type) &&
 810                                 nodeListeners.contains(le)) {
 811                                 try {
 812                                     le.listener.handleEvent(evt);
 813                                 }
 814                                 catch (Exception e) {
 815                                     // All exceptions are ignored.
 816                                 }
 817                             }
 818                         }
 819                     }
 820                 }
 821             }
 822         }
 823 
 824         // DEFAULT PHASE: Some DOMs have default behaviors bound to specific
 825         // nodes. If this DOM does, and if the event's preventDefault flag has
 826         // not been set, we now return to the target node and process its
 827         // default handler for this event, if any.
 828         // No specific phase value defined, since this is DOM-internal
 829         if (lc.defaults > 0 && (!evt.cancelable || !evt.preventDefault)) {
 830             // evt.eventPhase = Event.DEFAULT_PHASE;
 831             // evt.currentTarget = node;
 832             // DO_DEFAULT_OPERATION
 833         }
 834 
 835         return evt.preventDefault;
 836     } // dispatchEvent(NodeImpl,Event) :boolean
 837 
 838     /**
 839      * NON-DOM INTERNAL: DOMNodeInsertedIntoDocument and ...RemovedFrom...
 840      * are dispatched to an entire subtree. This is the distribution code
 841      * therefor. They DO NOT bubble, thanks be, but may be captured.
 842      * <p>
 843      * Similar to code in dispatchingEventToSubtree however this method
 844      * is only used on the target node and does not start a dispatching chain
 845      * on the sibling of the target node as this is not part of the subtree
 846      * ***** At the moment I'm being sloppy and using the normal
 847      * capture dispatcher on every node. This could be optimized hugely
 848      * by writing a capture engine that tracks our position in the tree to
 849      * update the capture chain without repeated chases up to root.
 850      * @param n target node (that was directly inserted or removed)
 851      * @param e event to be sent to that node and its subtree
 852      */
 853     protected void dispatchEventToSubtree(Node n, Event e) {
 854 
 855         ((NodeImpl) n).dispatchEvent(e);
 856         if (n.getNodeType() == Node.ELEMENT_NODE) {
 857             NamedNodeMap a = n.getAttributes();
 858             for (int i = a.getLength() - 1; i >= 0; --i)
 859                 dispatchingEventToSubtree(a.item(i), e);
 860         }
 861         dispatchingEventToSubtree(n.getFirstChild(), e);
 862 
 863     } // dispatchEventToSubtree(NodeImpl,Node,Event) :void
 864 
 865 
 866     /**
 867      * Dispatches event to the target node's descendents recursively
 868      *
 869      * @param n node to dispatch to
 870      * @param e event to be sent to that node and its subtree
 871      */
 872     protected void dispatchingEventToSubtree(Node n, Event e) {
 873         if (n==null)
 874                 return;
 875 
 876         // ***** Recursive implementation. This is excessively expensive,
 877         // and should be replaced in conjunction with optimization
 878         // mentioned above.
 879         ((NodeImpl) n).dispatchEvent(e);
 880         if (n.getNodeType() == Node.ELEMENT_NODE) {
 881             NamedNodeMap a = n.getAttributes();
 882             for (int i = a.getLength() - 1; i >= 0; --i)
 883                 dispatchingEventToSubtree(a.item(i), e);
 884         }
 885         dispatchingEventToSubtree(n.getFirstChild(), e);
 886         dispatchingEventToSubtree(n.getNextSibling(), e);
 887     }
 888 
 889     /**
 890      * NON-DOM INTERNAL: Return object for getEnclosingAttr. Carries
 891      * (two values, the Attr node affected (if any) and its previous
 892      * string value. Simple struct, no methods.
 893      */
 894     class EnclosingAttr implements Serializable {
 895         private static final long serialVersionUID = 5208387723391647216L;
 896         AttrImpl node;
 897         String oldvalue;
 898     }
 899 
 900     EnclosingAttr savedEnclosingAttr;
 901 
 902     /**
 903      * NON-DOM INTERNAL: Convenience wrapper for calling
 904      * dispatchAggregateEvents when the context was established
 905      * by <code>savedEnclosingAttr</code>.
 906      * @param node node to dispatch to
 907      * @param ea description of Attr affected by current operation
 908      */
 909     protected void dispatchAggregateEvents(NodeImpl node, EnclosingAttr ea) {
 910         if (ea != null)
 911             dispatchAggregateEvents(node, ea.node, ea.oldvalue,
 912                                     MutationEvent.MODIFICATION);
 913         else
 914             dispatchAggregateEvents(node, null, null, (short) 0);
 915 
 916     } // dispatchAggregateEvents(NodeImpl,EnclosingAttr) :void
 917 
 918     /**
 919      * NON-DOM INTERNAL: Generate the "aggregated" post-mutation events
 920      * DOMAttrModified and DOMSubtreeModified.
 921      * Both of these should be issued only once for each user-requested
 922      * mutation operation, even if that involves multiple changes to
 923      * the DOM.
 924      * For example, if a DOM operation makes multiple changes to a single
 925      * Attr before returning, it would be nice to generate only one
 926      * DOMAttrModified, and multiple changes over larger scope but within
 927      * a recognizable single subtree might want to generate only one
 928      * DOMSubtreeModified, sent to their lowest common ancestor.
 929      * <p>
 930      * To manage this, use the "internal" versions of insert and remove
 931      * with MUTATION_LOCAL, then make an explicit call to this routine
 932      * at the higher level. Some examples now exist in our code.
 933      *
 934      * @param node The node to dispatch to
 935      * @param enclosingAttr The Attr node (if any) whose value has been changed
 936      * as a result of the DOM operation. Null if none such.
 937      * @param oldValue The String value previously held by the
 938      * enclosingAttr. Ignored if none such.
 939      * @param change Type of modification to the attr. See
 940      * MutationEvent.attrChange
 941      */
 942     protected void dispatchAggregateEvents(NodeImpl node,
 943                                            AttrImpl enclosingAttr,
 944                                            String oldvalue, short change) {
 945         // We have to send DOMAttrModified.
 946         NodeImpl owner = null;
 947         if (enclosingAttr != null) {
 948             LCount lc = LCount.lookup(MutationEventImpl.DOM_ATTR_MODIFIED);
 949             owner = (NodeImpl) enclosingAttr.getOwnerElement();
 950             if (lc.total > 0) {
 951                 if (owner != null) {
 952                     MutationEventImpl me =  new MutationEventImpl();
 953                     me.initMutationEvent(MutationEventImpl.DOM_ATTR_MODIFIED,
 954                                          true, false, enclosingAttr,
 955                                          oldvalue,
 956                                          enclosingAttr.getNodeValue(),
 957                                          enclosingAttr.getNodeName(),
 958                                          change);
 959                     owner.dispatchEvent(me);
 960                 }
 961             }
 962         }
 963         // DOMSubtreeModified gets sent to the lowest common root of a
 964         // set of changes.
 965         // "This event is dispatched after all other events caused by the
 966         // mutation have been fired."
 967         LCount lc = LCount.lookup(MutationEventImpl.DOM_SUBTREE_MODIFIED);
 968         if (lc.total > 0) {
 969             MutationEvent me =  new MutationEventImpl();
 970             me.initMutationEvent(MutationEventImpl.DOM_SUBTREE_MODIFIED,
 971                                  true, false, null, null,
 972                                  null, null, (short) 0);
 973 
 974             // If we're within an Attr, DStM gets sent to the Attr
 975             // and to its owningElement. Otherwise we dispatch it
 976             // locally.
 977             if (enclosingAttr != null) {
 978                 dispatchEvent(enclosingAttr, me);
 979                 if (owner != null)
 980                     dispatchEvent(owner, me);
 981             }
 982             else
 983                 dispatchEvent(node, me);
 984         }
 985     } // dispatchAggregateEvents(NodeImpl, AttrImpl,String) :void
 986 
 987     /**
 988      * NON-DOM INTERNAL: Pre-mutation context check, in
 989      * preparation for later generating DOMAttrModified events.
 990      * Determines whether this node is within an Attr
 991      * @param node node to get enclosing attribute for
 992      * @return either a description of that Attr, or null if none such.
 993      */
 994     protected void saveEnclosingAttr(NodeImpl node) {
 995         savedEnclosingAttr = null;
 996         // MUTATION PREPROCESSING AND PRE-EVENTS:
 997         // If we're within the scope of an Attr and DOMAttrModified
 998         // was requested, we need to preserve its previous value for
 999         // that event.
1000         LCount lc = LCount.lookup(MutationEventImpl.DOM_ATTR_MODIFIED);
1001         if (lc.total > 0) {
1002             NodeImpl eventAncestor = node;
1003             while (true) {
1004                 if (eventAncestor == null)
1005                     return;
1006                 int type = eventAncestor.getNodeType();
1007                 if (type == Node.ATTRIBUTE_NODE) {
1008                     EnclosingAttr retval = new EnclosingAttr();
1009                     retval.node = (AttrImpl) eventAncestor;
1010                     retval.oldvalue = retval.node.getNodeValue();
1011                     savedEnclosingAttr = retval;
1012                     return;
1013                 }
1014                 else if (type == Node.ENTITY_REFERENCE_NODE)
1015                     eventAncestor = eventAncestor.parentNode();
1016                 else if (type == Node.TEXT_NODE)
1017                     eventAncestor = eventAncestor.parentNode();
1018                 else
1019                     return;
1020                 // Any other parent means we're not in an Attr
1021             }
1022         }
1023     } // saveEnclosingAttr(NodeImpl) :void
1024 
1025     /**
1026      * A method to be called when a character data node has been modified
1027      */
1028     void modifyingCharacterData(NodeImpl node, boolean replace) {
1029         if (mutationEvents) {
1030                 if (!replace) {
1031                         saveEnclosingAttr(node);
1032                 }
1033         }
1034     }
1035 
1036     /**
1037      * A method to be called when a character data node has been modified
1038      */
1039     void modifiedCharacterData(NodeImpl node, String oldvalue, String value, boolean replace) {
1040         if (mutationEvents) {
1041                 if (!replace) {
1042                         // MUTATION POST-EVENTS:
1043                         LCount lc =
1044                                 LCount.lookup(MutationEventImpl.DOM_CHARACTER_DATA_MODIFIED);
1045                         if (lc.total > 0) {
1046                                 MutationEvent me = new MutationEventImpl();
1047                                 me.initMutationEvent(
1048                                         MutationEventImpl.DOM_CHARACTER_DATA_MODIFIED,
1049                                         true, false, null,
1050                                                                                 oldvalue, value, null, (short) 0);
1051                                 dispatchEvent(node, me);
1052                         }
1053 
1054                         // Subroutine: Transmit DOMAttrModified and DOMSubtreeModified,
1055                         // if required. (Common to most kinds of mutation)
1056                         dispatchAggregateEvents(node, savedEnclosingAttr);
1057                 } // End mutation postprocessing
1058         }
1059     }
1060 
1061     /**
1062      * A method to be called when a character data node has been replaced
1063      */
1064     void replacedCharacterData(NodeImpl node, String oldvalue, String value) {
1065         //now that we have finished replacing data, we need to perform the same actions
1066         //that are required after a character data node has been modified
1067         //send the value of false for replace parameter so that mutation
1068         //events if appropriate will be initiated
1069         modifiedCharacterData(node, oldvalue, value, false);
1070     }
1071 
1072 
1073 
1074     /**
1075      * A method to be called when a node is about to be inserted in the tree.
1076      */
1077     void insertingNode(NodeImpl node, boolean replace) {
1078         if (mutationEvents) {
1079             if (!replace) {
1080                 saveEnclosingAttr(node);
1081             }
1082         }
1083     }
1084 
1085     /**
1086      * A method to be called when a node has been inserted in the tree.
1087      */
1088     void insertedNode(NodeImpl node, NodeImpl newInternal, boolean replace) {
1089         if (mutationEvents) {
1090             // MUTATION POST-EVENTS:
1091             // "Local" events (non-aggregated)
1092             // New child is told it was inserted, and where
1093             LCount lc = LCount.lookup(MutationEventImpl.DOM_NODE_INSERTED);
1094             if (lc.total > 0) {
1095                 MutationEventImpl me = new MutationEventImpl();
1096                 me.initMutationEvent(MutationEventImpl.DOM_NODE_INSERTED,
1097                                      true, false, node,
1098                                      null, null, null, (short) 0);
1099                 dispatchEvent(newInternal, me);
1100             }
1101 
1102             // If within the Document, tell the subtree it's been added
1103             // to the Doc.
1104             lc = LCount.lookup(
1105                             MutationEventImpl.DOM_NODE_INSERTED_INTO_DOCUMENT);
1106             if (lc.total > 0) {
1107                 NodeImpl eventAncestor = node;
1108                 if (savedEnclosingAttr != null)
1109                     eventAncestor = (NodeImpl)
1110                         savedEnclosingAttr.node.getOwnerElement();
1111                 if (eventAncestor != null) { // Might have been orphan Attr
1112                     NodeImpl p = eventAncestor;
1113                     while (p != null) {
1114                         eventAncestor = p; // Last non-null ancestor
1115                         // In this context, ancestry includes
1116                         // walking back from Attr to Element
1117                         if (p.getNodeType() == ATTRIBUTE_NODE) {
1118                             p = (NodeImpl) ((AttrImpl)p).getOwnerElement();
1119                         }
1120                         else {
1121                             p = p.parentNode();
1122                         }
1123                     }
1124                     if (eventAncestor.getNodeType() == Node.DOCUMENT_NODE){
1125                         MutationEventImpl me = new MutationEventImpl();
1126                         me.initMutationEvent(MutationEventImpl
1127                                              .DOM_NODE_INSERTED_INTO_DOCUMENT,
1128                                              false,false,null,null,
1129                                              null,null,(short)0);
1130                         dispatchEventToSubtree(newInternal, me);
1131                     }
1132                 }
1133             }
1134             if (!replace) {
1135                 // Subroutine: Transmit DOMAttrModified and DOMSubtreeModified
1136                 // (Common to most kinds of mutation)
1137                 dispatchAggregateEvents(node, savedEnclosingAttr);
1138             }
1139         }
1140 
1141         // notify the range of insertions
1142         if (ranges != null) {
1143             int size = ranges.size();
1144             for (int i = 0; i != size; i++) {
1145                 ((RangeImpl)ranges.get(i)).insertedNodeFromDOM(newInternal);
1146             }
1147         }
1148     }
1149 
1150     /**
1151      * A method to be called when a node is about to be removed from the tree.
1152      */
1153     void removingNode(NodeImpl node, NodeImpl oldChild, boolean replace) {
1154 
1155         // notify iterators
1156         if (iterators != null) {
1157             int size = iterators.size();
1158             for (int i = 0; i != size; i++) {
1159                ((NodeIteratorImpl)iterators.get(i)).removeNode(oldChild);
1160             }
1161         }
1162 
1163         // notify ranges
1164         if (ranges != null) {
1165             int size = ranges.size();
1166             for (int i = 0; i != size; i++) {
1167                 ((RangeImpl)ranges.get(i)).removeNode(oldChild);
1168             }
1169         }
1170 
1171         // mutation events
1172         if (mutationEvents) {
1173             // MUTATION PREPROCESSING AND PRE-EVENTS:
1174             // If we're within the scope of an Attr and DOMAttrModified
1175             // was requested, we need to preserve its previous value for
1176             // that event.
1177             if (!replace) {
1178                 saveEnclosingAttr(node);
1179             }
1180             // Child is told that it is about to be removed
1181             LCount lc = LCount.lookup(MutationEventImpl.DOM_NODE_REMOVED);
1182             if (lc.total > 0) {
1183                 MutationEventImpl me= new MutationEventImpl();
1184                 me.initMutationEvent(MutationEventImpl.DOM_NODE_REMOVED,
1185                                      true, false, node, null,
1186                                      null, null, (short) 0);
1187                 dispatchEvent(oldChild, me);
1188             }
1189 
1190             // If within Document, child's subtree is informed that it's
1191             // losing that status
1192             lc = LCount.lookup(
1193                              MutationEventImpl.DOM_NODE_REMOVED_FROM_DOCUMENT);
1194             if (lc.total > 0) {
1195                 NodeImpl eventAncestor = this;
1196                 if(savedEnclosingAttr != null)
1197                     eventAncestor = (NodeImpl)
1198                         savedEnclosingAttr.node.getOwnerElement();
1199                 if (eventAncestor != null) { // Might have been orphan Attr
1200                     for (NodeImpl p = eventAncestor.parentNode();
1201                          p != null; p = p.parentNode()) {
1202                         eventAncestor = p; // Last non-null ancestor
1203                     }
1204                     if (eventAncestor.getNodeType() == Node.DOCUMENT_NODE){
1205                         MutationEventImpl me = new MutationEventImpl();
1206                         me.initMutationEvent(
1207                               MutationEventImpl.DOM_NODE_REMOVED_FROM_DOCUMENT,
1208                                              false, false, null,
1209                                              null, null, null, (short) 0);
1210                         dispatchEventToSubtree(oldChild, me);
1211                     }
1212                 }
1213             }
1214         } // End mutation preprocessing
1215     }
1216 
1217     /**
1218      * A method to be called when a node has been removed from the tree.
1219      */
1220     void removedNode(NodeImpl node, boolean replace) {
1221         if (mutationEvents) {
1222             // MUTATION POST-EVENTS:
1223             // Subroutine: Transmit DOMAttrModified and DOMSubtreeModified,
1224             // if required. (Common to most kinds of mutation)
1225             if (!replace) {
1226                 dispatchAggregateEvents(node, savedEnclosingAttr);
1227             }
1228         } // End mutation postprocessing
1229     }
1230 
1231     /**
1232      * A method to be called when a node is about to be replaced in the tree.
1233      */
1234     void replacingNode(NodeImpl node) {
1235         if (mutationEvents) {
1236             saveEnclosingAttr(node);
1237         }
1238     }
1239 
1240     /**
1241      * A method to be called when character data is about to be replaced in the tree.
1242      */
1243     void replacingData (NodeImpl node) {
1244         if (mutationEvents) {
1245                         saveEnclosingAttr(node);
1246         }
1247     }
1248 
1249     /**
1250      * A method to be called when a node has been replaced in the tree.
1251      */
1252     void replacedNode(NodeImpl node) {
1253         if (mutationEvents) {
1254             dispatchAggregateEvents(node, savedEnclosingAttr);
1255         }
1256     }
1257 
1258     /**
1259      * A method to be called when an attribute value has been modified
1260      */
1261     void modifiedAttrValue(AttrImpl attr, String oldvalue) {
1262         if (mutationEvents) {
1263             // MUTATION POST-EVENTS:
1264             dispatchAggregateEvents(attr, attr, oldvalue,
1265                                     MutationEvent.MODIFICATION);
1266         }
1267     }
1268 
1269     /**
1270      * A method to be called when an attribute node has been set
1271      */
1272     void setAttrNode(AttrImpl attr, AttrImpl previous) {
1273         if (mutationEvents) {
1274             // MUTATION POST-EVENTS:
1275             if (previous == null) {
1276                 dispatchAggregateEvents(attr.ownerNode, attr, null,
1277                                         MutationEvent.ADDITION);
1278             }
1279             else {
1280                 dispatchAggregateEvents(attr.ownerNode, attr,
1281                                         previous.getNodeValue(),
1282                                         MutationEvent.MODIFICATION);
1283             }
1284         }
1285     }
1286 
1287     /**
1288      * A method to be called when an attribute node has been removed
1289      */
1290     void removedAttrNode(AttrImpl attr, NodeImpl oldOwner, String name) {
1291         // We can't use the standard dispatchAggregate, since it assumes
1292         // that the Attr is still attached to an owner. This code is
1293         // similar but dispatches to the previous owner, "element".
1294         if (mutationEvents) {
1295             // If we have to send DOMAttrModified (determined earlier),
1296             // do so.
1297             LCount lc = LCount.lookup(MutationEventImpl.DOM_ATTR_MODIFIED);
1298             if (lc.total > 0) {
1299                 MutationEventImpl me= new MutationEventImpl();
1300                 me.initMutationEvent(MutationEventImpl.DOM_ATTR_MODIFIED,
1301                                      true, false, attr,
1302                                      attr.getNodeValue(), null, name,
1303                                      MutationEvent.REMOVAL);
1304                 dispatchEvent(oldOwner, me);
1305             }
1306 
1307             // We can hand off to process DOMSubtreeModified, though.
1308             // Note that only the Element needs to be informed; the
1309             // Attr's subtree has not been changed by this operation.
1310             dispatchAggregateEvents(oldOwner, null, null, (short) 0);
1311         }
1312     }
1313 
1314 
1315     /**
1316      * A method to be called when an attribute node has been renamed
1317      */
1318     void renamedAttrNode(Attr oldAt, Attr newAt) {
1319         // REVISIT: To be implemented!!!
1320     }
1321 
1322     /**
1323      * A method to be called when an element has been renamed
1324      */
1325     void renamedElement(Element oldEl, Element newEl) {
1326         // REVISIT: To be implemented!!!
1327     }
1328 
1329 
1330     /**
1331      * @serialData Serialized fields. Convert Maps to Hashtables and Lists
1332      * to Vectors for backward compatibility.
1333      */
1334     private void writeObject(ObjectOutputStream out) throws IOException {
1335         // Convert Maps to Hashtables, Lists to Vectors
1336         Vector<NodeIterator> it = (iterators == null)? null : new Vector<>(iterators);
1337         Vector<Range> r = (ranges == null)? null : new Vector<>(ranges);
1338 
1339         Hashtable<NodeImpl, Vector<LEntry>> el = null;
1340         if (eventListeners != null) {
1341             el = new Hashtable<>();
1342             for (Map.Entry<NodeImpl, List<LEntry>> e : eventListeners.entrySet()) {
1343                  el.put(e.getKey(), new Vector<>(e.getValue()));
1344             }
1345         }
1346 
1347         // Write serialized fields
1348         ObjectOutputStream.PutField pf = out.putFields();
1349         pf.put("iterators", it);
1350         pf.put("ranges", r);
1351         pf.put("eventListeners", el);
1352         pf.put("mutationEvents", mutationEvents);
1353         out.writeFields();
1354     }
1355 
1356     @SuppressWarnings("unchecked")
1357     private void readObject(ObjectInputStream in)
1358                         throws IOException, ClassNotFoundException {
1359         // We have to read serialized fields first.
1360         ObjectInputStream.GetField gf = in.readFields();
1361         Vector<NodeIterator> it = (Vector<NodeIterator>)gf.get("iterators", null);
1362         Vector<Range> r = (Vector<Range>)gf.get("ranges", null);
1363         Hashtable<NodeImpl, Vector<LEntry>> el =
1364                 (Hashtable<NodeImpl, Vector<LEntry>>)gf.get("eventListeners", null);
1365 
1366         mutationEvents = gf.get("mutationEvents", false);
1367 
1368         //convert Hashtables back to HashMaps and Vectors to Lists
1369         if (it != null) iterators = new ArrayList<>(it);
1370         if (r != null) ranges = new ArrayList<>(r);
1371         if (el != null) {
1372             eventListeners = new HashMap<>();
1373             for (Map.Entry<NodeImpl, Vector<LEntry>> e : el.entrySet()) {
1374                  eventListeners.put(e.getKey(), new ArrayList<>(e.getValue()));
1375             }
1376         }
1377     }
1378 } // class DocumentImpl