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