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