1 /* DomNode.java -- 2 Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc. 3 4 This file is part of GNU Classpath. 5 6 GNU Classpath is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 GNU Classpath is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with GNU Classpath; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19 02110-1301 USA. 20 21 Linking this library statically or dynamically with other modules is 22 making a combined work based on this library. Thus, the terms and 23 conditions of the GNU General Public License cover the whole 24 combination. 25 26 As a special exception, the copyright holders of this library give you 27 permission to link this library with independent modules to produce an 28 executable, regardless of the license terms of these independent 29 modules, and to copy and distribute the resulting executable under 30 terms of your choice, provided that you also meet, for each linked 31 independent module, the terms and conditions of the license of that 32 module. An independent module is a module which is not derived from 33 or based on this library. If you modify this library, you may extend 34 this exception to your version of the library, but you are not 35 obligated to do so. If you do not wish to do so, delete this 36 exception statement from your version. */ 37 38 package gnu.xml.dom; 39 40 import gnu.java.lang.CPStringBuilder; 41 42 import java.util.HashMap; 43 import java.util.HashSet; 44 import java.util.Iterator; 45 import java.util.Map; 46 47 import org.w3c.dom.Document; 48 import org.w3c.dom.DOMException; 49 import org.w3c.dom.DOMImplementation; 50 import org.w3c.dom.NamedNodeMap; 51 import org.w3c.dom.Node; 52 import org.w3c.dom.NodeList; 53 import org.w3c.dom.Text; 54 import org.w3c.dom.UserDataHandler; 55 import org.w3c.dom.events.DocumentEvent; 56 import org.w3c.dom.events.Event; 57 import org.w3c.dom.events.EventException; 58 import org.w3c.dom.events.EventListener; 59 import org.w3c.dom.events.EventTarget; 60 import org.w3c.dom.events.MutationEvent; 61 import org.w3c.dom.traversal.NodeFilter; 62 import org.w3c.dom.traversal.NodeIterator; 63 64 /** 65 * <p> "Node", "EventTarget", and "DocumentEvent" implementation. 66 * This provides most of the core DOM functionality; only more 67 * specialized features are provided by subclasses. Those subclasses may 68 * have some particular constraints they must implement, by overriding 69 * methods defined here. Such constraints are noted here in the method 70 * documentation. </p> 71 * 72 * <p> Note that you can create events with type names prefixed with "USER-", 73 * and pass them through this DOM. This lets you use the DOM event scheme 74 * for application specific purposes, although you must use a predefined event 75 * structure (such as MutationEvent) to pass data along with those events. 76 * Test for existence of this feature with the "USER-Events" DOM feature 77 * name.</p> 78 * 79 * <p> Other kinds of events you can send include the "html" events, 80 * like "load", "unload", "abort", "error", and "blur"; and the mutation 81 * events. If this DOM has been compiled with mutation event support 82 * enabled, it will send mutation events when you change parts of the 83 * tree; otherwise you may create and send such events yourself, but 84 * they won't be generated by the DOM itself. </p> 85 * 86 * <p> Note that there is a namespace-aware name comparison method, 87 * <em>nameAndTypeEquals</em>, which compares the names (and types) of 88 * two nodes in conformance with the "Namespaces in XML" specification. 89 * While mostly intended for use with elements and attributes, this should 90 * also be helpful for ProcessingInstruction nodes and some others which 91 * do not have namespace URIs. 92 * 93 * @author David Brownell 94 * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> 95 */ 96 public abstract class DomNode 97 implements Node, NodeList, EventTarget, DocumentEvent, Cloneable, Comparable 98 { 99 100 // package private 101 //final static String xmlNamespace = "http://www.w3.org/XML/1998/namespace"; 102 //final static String xmlnsURI = "http://www.w3.org/2000/xmlns/"; 103 104 // tunable 105 // NKIDS_* affects arrays of children (which grow) 106 // (currently) fixed size: 107 // ANCESTORS_* is for event capture/bubbling, # ancestors 108 // NOTIFICATIONS_* is for per-node event delivery, # events 109 private static final int NKIDS_DELTA = 8; 110 private static final int ANCESTORS_INIT = 20; 111 private static final int NOTIFICATIONS_INIT = 10; 112 113 // tunable: enable mutation events or not? Enabling it costs about 114 // 10-15% in DOM construction time, last time it was measured. 115 116 // package private !!! 117 static final boolean reportMutations = true; 118 119 // locking protocol changeable only within this class 120 private static final Object lockNode = new Object(); 121 122 // NON-FINAL class data 123 124 // Optimize event dispatch by not allocating memory each time 125 private static boolean dispatchDataLock; 126 private static DomNode[] ancestors = new DomNode[ANCESTORS_INIT]; 127 private static ListenerRecord[] notificationSet 128 = new ListenerRecord[NOTIFICATIONS_INIT]; 129 130 // Ditto for the (most common) event object itself! 131 private static boolean eventDataLock; 132 private static DomEvent.DomMutationEvent mutationEvent 133 = new DomEvent.DomMutationEvent(null); 134 135 // 136 // PER-INSTANCE DATA 137 // 138 139 DomDocument owner; 140 DomNode parent; // parent node; 141 DomNode previous; // previous sibling node 142 DomNode next; // next sibling node 143 DomNode first; // first child node 144 DomNode last; // last child node 145 int index; // index of this node in its parent's children 146 int depth; // depth of the node in the document 147 int length; // number of children 148 final short nodeType; 149 150 // Bleech ... "package private" so a builder can populate entity refs. 151 // writable during construction. DOM spec is nasty. 152 boolean readonly; 153 154 // event registrations 155 private HashSet listeners; 156 private int nListeners; 157 158 // DOM Level 3 userData dictionary. 159 private HashMap userData; 160 private HashMap userDataHandlers; 161 162 // 163 // Some of the methods here are declared 'final' because 164 // knowledge about their implementation is built into this 165 // class -- for both integrity and performance. 166 // 167 168 /** 169 * Reduces space utilization for this node. 170 */ compact()171 public void compact() 172 { 173 } 174 175 /** 176 * Constructs a node and associates it with its owner. Only 177 * Document and DocumentType nodes may be created with no owner, 178 * and DocumentType nodes get an owner as soon as they are 179 * associated with a document. 180 */ DomNode(short nodeType, DomDocument owner)181 protected DomNode(short nodeType, DomDocument owner) 182 { 183 this.nodeType = nodeType; 184 185 if (owner == null) 186 { 187 // DOM calls never go down this path 188 if (nodeType != DOCUMENT_NODE && nodeType != DOCUMENT_TYPE_NODE) 189 { 190 throw new IllegalArgumentException ("no owner!"); 191 } 192 } 193 this.owner = owner; 194 this.listeners = new HashSet(); 195 } 196 197 198 /** 199 * <b>DOM L1</b> 200 * Returns null; Element subclasses must override this method. 201 */ getAttributes()202 public NamedNodeMap getAttributes() 203 { 204 return null; 205 } 206 207 /** 208 * <b>DOM L2></b> 209 * Returns true iff this is an element node with attributes. 210 */ hasAttributes()211 public boolean hasAttributes() 212 { 213 return false; 214 } 215 216 /** 217 * <b>DOM L1</b> 218 * Returns a list, possibly empty, of the children of this node. 219 * In this implementation, to conserve memory, nodes are the same 220 * as their list of children. This can have ramifications for 221 * subclasses, which may need to provide their own getLength method 222 * for reasons unrelated to the NodeList method of the same name. 223 */ getChildNodes()224 public NodeList getChildNodes() 225 { 226 return this; 227 } 228 229 /** 230 * <b>DOM L1</b> 231 * Returns the first child of this node, or null if there are none. 232 */ getFirstChild()233 public Node getFirstChild() 234 { 235 return first; 236 } 237 238 /** 239 * <b>DOM L1</b> 240 * Returns the last child of this node, or null if there are none. 241 */ getLastChild()242 public Node getLastChild() 243 { 244 return last; 245 } 246 247 /** 248 * <b>DOM L1</b> 249 * Returns true if this node has children. 250 */ hasChildNodes()251 public boolean hasChildNodes() 252 { 253 return length != 0; 254 } 255 256 257 /** 258 * Exposes the internal "readonly" flag. In DOM, children of 259 * entities and entity references are readonly, as are the 260 * objects associated with DocumentType objets. 261 */ isReadonly()262 public final boolean isReadonly() 263 { 264 return readonly; 265 } 266 267 /** 268 * Sets the internal "readonly" flag so this subtree can't be changed. 269 * Subclasses need to override this method for any associated content 270 * that's not a child node, such as an element's attributes or the 271 * (few) declarations associated with a DocumentType. 272 */ makeReadonly()273 public void makeReadonly() 274 { 275 readonly = true; 276 for (DomNode child = first; child != null; child = child.next) 277 { 278 child.makeReadonly(); 279 } 280 } 281 282 /** 283 * Used to adopt a node to a new document. 284 */ setOwner(DomDocument doc)285 void setOwner(DomDocument doc) 286 { 287 this.owner = doc; 288 for (DomNode ctx = first; ctx != null; ctx = ctx.next) 289 { 290 ctx.setOwner(doc); 291 } 292 } 293 294 // just checks the node for inclusion -- may be called many 295 // times (docfrag) before anything is allowed to change checkMisc(DomNode child)296 private void checkMisc(DomNode child) 297 { 298 if (readonly && !owner.building) 299 { 300 throw new DomDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, 301 null, this, 0); 302 } 303 for (DomNode ctx = this; ctx != null; ctx = ctx.parent) 304 { 305 if (child == ctx) 306 { 307 throw new DomDOMException(DOMException.HIERARCHY_REQUEST_ERR, 308 "can't make ancestor into a child", 309 this, 0); 310 } 311 } 312 313 DomDocument owner = (nodeType == DOCUMENT_NODE) ? (DomDocument) this : 314 this.owner; 315 DomDocument childOwner = child.owner; 316 short childNodeType = child.nodeType; 317 318 if (childOwner != owner) 319 { 320 // new in DOM L2, this case -- patch it up later, in reparent() 321 if (!(childNodeType == DOCUMENT_TYPE_NODE && childOwner == null)) 322 { 323 throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR, 324 null, child, 0); 325 } 326 } 327 328 // enforce various structural constraints 329 switch (nodeType) 330 { 331 case DOCUMENT_NODE: 332 switch (childNodeType) 333 { 334 case ELEMENT_NODE: 335 case PROCESSING_INSTRUCTION_NODE: 336 case COMMENT_NODE: 337 case DOCUMENT_TYPE_NODE: 338 return; 339 } 340 break; 341 342 case ATTRIBUTE_NODE: 343 switch (childNodeType) 344 { 345 case TEXT_NODE: 346 case ENTITY_REFERENCE_NODE: 347 return; 348 } 349 break; 350 351 case DOCUMENT_FRAGMENT_NODE: 352 case ENTITY_REFERENCE_NODE: 353 case ELEMENT_NODE: 354 case ENTITY_NODE: 355 switch (childNodeType) 356 { 357 case ELEMENT_NODE: 358 case TEXT_NODE: 359 case COMMENT_NODE: 360 case PROCESSING_INSTRUCTION_NODE: 361 case CDATA_SECTION_NODE: 362 case ENTITY_REFERENCE_NODE: 363 return; 364 } 365 break; 366 case DOCUMENT_TYPE_NODE: 367 if (!owner.building) 368 break; 369 switch (childNodeType) 370 { 371 case COMMENT_NODE: 372 case PROCESSING_INSTRUCTION_NODE: 373 return; 374 } 375 break; 376 } 377 if (owner.checkingWellformedness) 378 { 379 throw new DomDOMException(DOMException.HIERARCHY_REQUEST_ERR, 380 "can't append " + 381 nodeTypeToString(childNodeType) + 382 " to node of type " + 383 nodeTypeToString(nodeType), 384 this, 0); 385 } 386 } 387 388 // Here's hoping a good optimizer will detect the case when the 389 // next several methods are never called, and won't allocate 390 // object code space of any kind. (Case: not reporting any 391 // mutation events. We can also remove some static variables 392 // listed above.) 393 insertionEvent(DomEvent.DomMutationEvent event, DomNode target)394 private void insertionEvent(DomEvent.DomMutationEvent event, 395 DomNode target) 396 { 397 if (owner == null || owner.building) 398 { 399 return; 400 } 401 boolean doFree = false; 402 403 if (event == null) 404 { 405 event = getMutationEvent(); 406 } 407 if (event != null) 408 { 409 doFree = true; 410 } 411 else 412 { 413 event = new DomEvent.DomMutationEvent(null); 414 } 415 event.initMutationEvent("DOMNodeInserted", 416 true /* bubbles */, false /* nocancel */, 417 this /* related */, null, null, null, (short) 0); 418 target.dispatchEvent(event); 419 420 // XXX should really visit every descendant of 'target' 421 // and sent a DOMNodeInsertedIntoDocument event to it... 422 // bleech, there's no way to keep that acceptably fast. 423 424 if (doFree) 425 { 426 event.target = null; 427 event.relatedNode = null; 428 event.currentNode = null; 429 eventDataLock = false; 430 } // else we created work for the GC 431 } 432 removalEvent(DomEvent.DomMutationEvent event, DomNode target)433 private void removalEvent(DomEvent.DomMutationEvent event, 434 DomNode target) 435 { 436 if (owner == null || owner.building) 437 { 438 return; 439 } 440 boolean doFree = false; 441 442 if (event == null) 443 { 444 event = getMutationEvent(); 445 } 446 if (event != null) 447 { 448 doFree = true; 449 } 450 else 451 { 452 event = new DomEvent.DomMutationEvent(null); 453 } 454 event.initMutationEvent("DOMNodeRemoved", 455 true /* bubbles */, false /* nocancel */, 456 this /* related */, null, null, null, (short) 0); 457 target.dispatchEvent(event); 458 459 // XXX should really visit every descendant of 'target' 460 // and sent a DOMNodeRemovedFromDocument event to it... 461 // bleech, there's no way to keep that acceptably fast. 462 463 event.target = null; 464 event.relatedNode = null; 465 event.currentNode = null; 466 if (doFree) 467 { 468 eventDataLock = false; 469 } 470 // else we created more work for the GC 471 } 472 473 // 474 // Avoid creating lots of memory management work, by using a simple 475 // allocation strategy for the mutation event objects that get used 476 // at least once per tree modification. We can't use stack allocation, 477 // so we do the next simplest thing -- more or less, static allocation. 478 // Concurrent notifications should be rare, anyway. 479 // 480 // Returns the preallocated object, which needs to be carefully freed, 481 // or null to indicate the caller needs to allocate their own. 482 // getMutationEvent()483 static private DomEvent.DomMutationEvent getMutationEvent() 484 { 485 synchronized (lockNode) 486 { 487 if (eventDataLock) 488 { 489 return null; 490 } 491 eventDataLock = true; 492 return mutationEvent; 493 } 494 } 495 496 // NOTE: this is manually inlined in the insertion 497 // and removal event methods above; change in sync. freeMutationEvent()498 static private void freeMutationEvent() 499 { 500 // clear fields to enable GC 501 mutationEvent.clear(); 502 eventDataLock = false; 503 } 504 setDepth(int depth)505 void setDepth(int depth) 506 { 507 this.depth = depth; 508 for (DomNode ctx = first; ctx != null; ctx = ctx.next) 509 { 510 ctx.setDepth(depth + 1); 511 } 512 } 513 514 /** 515 * <b>DOM L1</b> 516 * Appends the specified node to this node's list of children. 517 * Document subclasses must override this to enforce the restrictions 518 * that there be only one element and document type child. 519 * 520 * <p> Causes a DOMNodeInserted mutation event to be reported. 521 * Will first cause a DOMNodeRemoved event to be reported if the 522 * parameter already has a parent. If the new child is a document 523 * fragment node, both events will be reported for each child of 524 * the fragment; the order in which children are removed and 525 * inserted is implementation-specific. 526 * 527 * <p> If this DOM has been compiled without mutation event support, 528 * these events will not be reported. 529 */ appendChild(Node newChild)530 public Node appendChild(Node newChild) 531 { 532 try 533 { 534 DomNode child = (DomNode) newChild; 535 536 if (child.nodeType == DOCUMENT_FRAGMENT_NODE) 537 { 538 // Append all nodes in the fragment to this node 539 for (DomNode ctx = child.first; ctx != null; ctx = ctx.next) 540 { 541 checkMisc(ctx); 542 } 543 for (DomNode ctx = child.first; ctx != null; ) 544 { 545 DomNode ctxNext = ctx.next; 546 appendChild(ctx); 547 ctx = ctxNext; 548 } 549 } 550 else 551 { 552 checkMisc(child); 553 if (child.parent != null) 554 { 555 child.parent.removeChild(child); 556 } 557 child.parent = this; 558 child.index = length++; 559 child.setDepth(depth + 1); 560 child.next = null; 561 if (last == null) 562 { 563 first = child; 564 child.previous = null; 565 } 566 else 567 { 568 last.next = child; 569 child.previous = last; 570 } 571 last = child; 572 573 if (reportMutations) 574 { 575 insertionEvent(null, child); 576 } 577 } 578 579 return child; 580 } 581 catch (ClassCastException e) 582 { 583 throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR, 584 null, newChild, 0); 585 } 586 } 587 588 /** 589 * <b>DOM L1</b> 590 * Inserts the specified node in this node's list of children. 591 * Document subclasses must override this to enforce the restrictions 592 * that there be only one element and document type child. 593 * 594 * <p> Causes a DOMNodeInserted mutation event to be reported. Will 595 * first cause a DOMNodeRemoved event to be reported if the newChild 596 * parameter already has a parent. If the new child is a document 597 * fragment node, both events will be reported for each child of 598 * the fragment; the order in which children are removed and inserted 599 * is implementation-specific. 600 * 601 * <p> If this DOM has been compiled without mutation event support, 602 * these events will not be reported. 603 */ insertBefore(Node newChild, Node refChild)604 public Node insertBefore(Node newChild, Node refChild) 605 { 606 if (refChild == null) 607 { 608 return appendChild(newChild); 609 } 610 611 try 612 { 613 DomNode child = (DomNode) newChild; 614 DomNode ref = (DomNode) refChild; 615 616 if (child.nodeType == DOCUMENT_FRAGMENT_NODE) 617 { 618 // Append all nodes in the fragment to this node 619 for (DomNode ctx = child.first; ctx != null; ctx = ctx.next) 620 { 621 checkMisc(ctx); 622 } 623 for (DomNode ctx = child.first; ctx != null; ) 624 { 625 DomNode ctxNext = ctx.next; 626 insertBefore(ctx, ref); 627 ctx = ctxNext; 628 } 629 } 630 else 631 { 632 checkMisc(child); 633 if (ref == null || ref.parent != this) 634 { 635 throw new DomDOMException(DOMException.NOT_FOUND_ERR, 636 null, ref, 0); 637 } 638 if (ref == child) 639 { 640 throw new DomDOMException(DOMException.HIERARCHY_REQUEST_ERR, 641 "can't insert node before itself", 642 ref, 0); 643 } 644 645 if (child.parent != null) 646 { 647 child.parent.removeChild(child); 648 } 649 child.parent = this; 650 int i = ref.index; 651 child.setDepth(depth + 1); 652 child.next = ref; 653 if (ref.previous != null) 654 { 655 ref.previous.next = child; 656 } 657 child.previous = ref.previous; 658 ref.previous = child; 659 if (first == ref) 660 { 661 first = child; 662 } 663 // index renumbering 664 for (DomNode ctx = child; ctx != null; ctx = ctx.next) 665 { 666 ctx.index = i++; 667 } 668 669 if (reportMutations) 670 { 671 insertionEvent(null, child); 672 } 673 length++; 674 } 675 676 return child; 677 } 678 catch (ClassCastException e) 679 { 680 throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR, 681 null, newChild, 0); 682 } 683 } 684 685 /** 686 * <b>DOM L1</b> 687 * Replaces the specified node in this node's list of children. 688 * Document subclasses must override this to test the restrictions 689 * that there be only one element and document type child. 690 * 691 * <p> Causes DOMNodeRemoved and DOMNodeInserted mutation event to be 692 * reported. Will cause another DOMNodeRemoved event to be reported if 693 * the newChild parameter already has a parent. These events may be 694 * delivered in any order, except that the event reporting removal 695 * from such an existing parent will always be delivered before the 696 * event reporting its re-insertion as a child of some other node. 697 * The order in which children are removed and inserted is implementation 698 * specific. 699 * 700 * <p> If your application needs to depend on the in which those removal 701 * and insertion events are delivered, don't use this API. Instead, 702 * invoke the removeChild and insertBefore methods directly, to guarantee 703 * a specific delivery order. Similarly, don't use document fragments, 704 * Otherwise your application code may not work on a DOM which implements 705 * this method differently. 706 * 707 * <p> If this DOM has been compiled without mutation event support, 708 * these events will not be reported. 709 */ replaceChild(Node newChild, Node refChild)710 public Node replaceChild(Node newChild, Node refChild) 711 { 712 try 713 { 714 DomNode child = (DomNode) newChild; 715 DomNode ref = (DomNode) refChild; 716 717 DomEvent.DomMutationEvent event = getMutationEvent(); 718 boolean doFree = (event != null); 719 720 if (child.nodeType == DOCUMENT_FRAGMENT_NODE) 721 { 722 // Append all nodes in the fragment to this node 723 for (DomNode ctx = child.first; ctx != null; ctx = ctx.next) 724 { 725 checkMisc(ctx); 726 } 727 if (ref == null || ref.parent != this) 728 { 729 throw new DomDOMException(DOMException.NOT_FOUND_ERR, 730 null, ref, 0); 731 } 732 733 if (reportMutations) 734 { 735 removalEvent(event, ref); 736 } 737 length--; 738 length += child.length; 739 740 if (child.length == 0) 741 { 742 // Removal 743 if (ref.previous != null) 744 { 745 ref.previous.next = ref.next; 746 } 747 if (ref.next != null) 748 { 749 ref.next.previous = ref.previous; 750 } 751 if (first == ref) 752 { 753 first = ref.next; 754 } 755 if (last == ref) 756 { 757 last = ref.previous; 758 } 759 } 760 else 761 { 762 int i = ref.index; 763 for (DomNode ctx = child.first; ctx != null; ctx = ctx.next) 764 { 765 // Insertion 766 ctx.parent = this; 767 ctx.index = i++; 768 ctx.setDepth(ref.depth); 769 if (ctx == child.first) 770 { 771 ctx.previous = ref.previous; 772 } 773 if (ctx == child.last) 774 { 775 ctx.next = ref.next; 776 } 777 } 778 if (first == ref) 779 { 780 first = child.first; 781 } 782 if (last == ref) 783 { 784 last = child.last; 785 } 786 } 787 } 788 else 789 { 790 checkMisc(child); 791 if (ref == null || ref.parent != this) 792 { 793 throw new DomDOMException(DOMException.NOT_FOUND_ERR, 794 null, ref, 0); 795 } 796 797 if (reportMutations) 798 { 799 removalEvent(event, ref); 800 } 801 802 if (child.parent != null) 803 { 804 child.parent.removeChild(child); 805 } 806 child.parent = this; 807 child.index = ref.index; 808 child.setDepth(ref.depth); 809 if (ref.previous != null) 810 { 811 ref.previous.next = child; 812 } 813 child.previous = ref.previous; 814 if (ref.next != null) 815 { 816 ref.next.previous = child; 817 } 818 child.next = ref.next; 819 if (first == ref) 820 { 821 first = child; 822 } 823 if (last == ref) 824 { 825 last = child; 826 } 827 828 if (reportMutations) 829 { 830 insertionEvent(event, child); 831 } 832 if (doFree) 833 { 834 freeMutationEvent(); 835 } 836 } 837 ref.parent = null; 838 ref.index = 0; 839 ref.setDepth(0); 840 ref.previous = null; 841 ref.next = null; 842 843 return ref; 844 } 845 catch (ClassCastException e) 846 { 847 throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR, 848 null, newChild, 0); 849 } 850 } 851 852 /** 853 * <b>DOM L1</b> 854 * Removes the specified child from this node's list of children, 855 * or else reports an exception. 856 * 857 * <p> Causes a DOMNodeRemoved mutation event to be reported. 858 * 859 * <p> If this DOM has been compiled without mutation event support, 860 * these events will not be reported. 861 */ removeChild(Node refChild)862 public Node removeChild(Node refChild) 863 { 864 try 865 { 866 DomNode ref = (DomNode) refChild; 867 868 if (ref == null || ref.parent != this) 869 { 870 throw new DomDOMException(DOMException.NOT_FOUND_ERR, 871 null, ref, 0); 872 } 873 if (readonly && !owner.building) 874 { 875 throw new DomDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, 876 null, this, 0); 877 } 878 879 for (DomNode child = first; child != null; child = child.next) 880 { 881 if (child == ref) 882 { 883 if (reportMutations) 884 { 885 removalEvent(null, child); 886 } 887 888 length--; 889 if (ref.previous != null) 890 { 891 ref.previous.next = ref.next; 892 } 893 if (ref.next != null) 894 { 895 ref.next.previous = ref.previous; 896 } 897 if (first == ref) 898 { 899 first = ref.next; 900 } 901 if (last == ref) 902 { 903 last = ref.previous; 904 } 905 // renumber indices 906 int i = 0; 907 for (DomNode ctx = first; ctx != null; ctx = ctx.next) 908 { 909 ctx.index = i++; 910 } 911 ref.parent = null; 912 ref.setDepth(0); 913 ref.index = 0; 914 ref.previous = null; 915 ref.next = null; 916 917 return ref; 918 } 919 } 920 throw new DomDOMException(DOMException.NOT_FOUND_ERR, 921 "that's no child of mine", refChild, 0); 922 } 923 catch (ClassCastException e) 924 { 925 throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR, 926 null, refChild, 0); 927 } 928 } 929 930 /** 931 * <b>DOM L1 (NodeList)</b> 932 * Returns the item with the specified index in this NodeList, 933 * else null. 934 */ item(int index)935 public Node item(int index) 936 { 937 DomNode child = first; 938 int count = 0; 939 while (child != null && count < index) 940 { 941 child = child.next; 942 count++; 943 } 944 return child; 945 } 946 947 /** 948 * <b>DOM L1 (NodeList)</b> 949 * Returns the number of elements in this NodeList. 950 * (Note that many interfaces have a "Length" property, not just 951 * NodeList, and if a node subtype must implement one of those, 952 * it will also need to override getChildNodes.) 953 */ getLength()954 public int getLength() 955 { 956 return length; 957 } 958 959 /** 960 * Minimize extra space consumed by this node to hold children and event 961 * listeners. 962 */ trimToSize()963 public void trimToSize() 964 { 965 } 966 967 /** 968 * <b>DOM L1</b> 969 * Returns the previous sibling, if one is known. 970 */ getNextSibling()971 public Node getNextSibling() 972 { 973 return next; 974 } 975 976 /** 977 * <b>DOM L1</b> 978 * Returns the previous sibling, if one is known. 979 */ getPreviousSibling()980 public Node getPreviousSibling() 981 { 982 return previous; 983 } 984 985 /** 986 * <b>DOM L1</b> 987 * Returns the parent node, if one is known. 988 */ getParentNode()989 public Node getParentNode() 990 { 991 return parent; 992 } 993 994 /** 995 * <b>DOM L2</b> 996 * Consults the DOM implementation to determine if the requested 997 * feature is supported. DocumentType subclasses must override 998 * this method, and associate themselves directly with the 999 * DOMImplementation node used. (This method relies on being able 1000 * to access the DOMImplementation from the owner document, but 1001 * DocumentType nodes can be created without an owner.) 1002 */ isSupported(String feature, String version)1003 public boolean isSupported(String feature, String version) 1004 { 1005 Document doc = owner; 1006 DOMImplementation impl = null; 1007 1008 if (doc == null && nodeType == DOCUMENT_NODE) 1009 { 1010 doc = (Document) this; 1011 } 1012 1013 if (doc == null) 1014 { 1015 // possible for DocumentType 1016 throw new IllegalStateException ("unbound ownerDocument"); 1017 } 1018 1019 impl = doc.getImplementation(); 1020 return impl.hasFeature(feature, version); 1021 } 1022 1023 /** 1024 * <b>DOM L1 (modified in L2)</b> 1025 * Returns the owner document. This is only null for Document nodes, 1026 * and (new in L2) for DocumentType nodes which have not yet been 1027 * associated with the rest of their document. 1028 */ getOwnerDocument()1029 final public Document getOwnerDocument() 1030 { 1031 return owner; 1032 } 1033 1034 /** 1035 * <b>DOM L1</b> 1036 * Does nothing; this must be overridden (along with the 1037 * getNodeValue method) for nodes with a non-null defined value. 1038 */ setNodeValue(String value)1039 public void setNodeValue(String value) 1040 { 1041 } 1042 1043 /** 1044 * <b>DOM L1</b> 1045 * Returns null; this must be overridden for nodes types with 1046 * a defined value, along with the setNodeValue method. 1047 */ getNodeValue()1048 public String getNodeValue() 1049 { 1050 return null; 1051 } 1052 1053 /** This forces GCJ compatibility. 1054 * Without this method GCJ is unable to compile to byte code. 1055 */ getNodeType()1056 public final short getNodeType() 1057 { 1058 return nodeType; 1059 } 1060 1061 /** This forces GCJ compatibility. 1062 * Without this method GCJ seems unable to natively compile GNUJAXP. 1063 */ getNodeName()1064 public abstract String getNodeName(); 1065 1066 /** 1067 * <b>DOM L2</b> 1068 * Does nothing; this must be overridden (along with the 1069 * getPrefix method) for element and attribute nodes. 1070 */ setPrefix(String prefix)1071 public void setPrefix(String prefix) 1072 { 1073 } 1074 1075 /** 1076 * <b>DOM L2</b> 1077 * Returns null; this must be overridden for element and 1078 * attribute nodes. 1079 */ getPrefix()1080 public String getPrefix() 1081 { 1082 return null; 1083 } 1084 1085 /** 1086 * <b>DOM L2</b> 1087 * Returns null; this must be overridden for element and 1088 * attribute nodes. 1089 */ getNamespaceURI()1090 public String getNamespaceURI() 1091 { 1092 return null; 1093 } 1094 1095 /** 1096 * <b>DOM L2</b> 1097 * Returns the node name; this must be overridden for element and 1098 * attribute nodes. 1099 */ getLocalName()1100 public String getLocalName() 1101 { 1102 return null; 1103 } 1104 1105 /** 1106 * <b>DOM L1</b> 1107 * Returns a clone of this node which optionally includes cloned 1108 * versions of child nodes. Clones are always mutable, except for 1109 * entity reference nodes. 1110 */ cloneNode(boolean deep)1111 public Node cloneNode(boolean deep) 1112 { 1113 if (deep) 1114 { 1115 return cloneNodeDeepInternal(true, null); 1116 } 1117 1118 DomNode node = (DomNode) clone(); 1119 if (nodeType == ENTITY_REFERENCE_NODE) 1120 { 1121 node.makeReadonly(); 1122 } 1123 notifyUserDataHandlers(UserDataHandler.NODE_CLONED, this, node); 1124 return node; 1125 } 1126 1127 /** 1128 * Returns a deep clone of this node. 1129 */ cloneNodeDeepInternal(boolean root, DomDocument doc)1130 private DomNode cloneNodeDeepInternal(boolean root, DomDocument doc) 1131 { 1132 DomNode node = (DomNode) clone(); 1133 boolean building = false; // Never used unless root is true 1134 if (root) 1135 { 1136 doc = (nodeType == DOCUMENT_NODE) ? (DomDocument) node : node.owner; 1137 building = doc.building; 1138 doc.building = true; // Permit certain structural rules 1139 } 1140 node.owner = doc; 1141 for (DomNode ctx = first; ctx != null; ctx = ctx.next) 1142 { 1143 DomNode newChild = ctx.cloneNodeDeepInternal(false, doc); 1144 node.appendChild(newChild); 1145 } 1146 if (nodeType == ENTITY_REFERENCE_NODE) 1147 { 1148 node.makeReadonly(); 1149 } 1150 if (root) 1151 { 1152 doc.building = building; 1153 } 1154 notifyUserDataHandlers(UserDataHandler.NODE_CLONED, this, node); 1155 return node; 1156 } 1157 notifyUserDataHandlers(short op, Node src, Node dst)1158 void notifyUserDataHandlers(short op, Node src, Node dst) 1159 { 1160 if (userDataHandlers != null) 1161 { 1162 for (Iterator i = userDataHandlers.entrySet().iterator(); i.hasNext(); ) 1163 { 1164 Map.Entry entry = (Map.Entry) i.next(); 1165 String key = (String) entry.getKey(); 1166 UserDataHandler handler = (UserDataHandler) entry.getValue(); 1167 Object data = userData.get(key); 1168 handler.handle(op, key, data, src, dst); 1169 } 1170 } 1171 } 1172 1173 /** 1174 * Clones this node; roughly equivalent to cloneNode(false). 1175 * Element subclasses must provide a new implementation which 1176 * invokes this method to handle the basics, and then arranges 1177 * to clone any element attributes directly. Attribute subclasses 1178 * must make similar arrangements, ensuring that existing ties to 1179 * elements are broken by cloning. 1180 */ clone()1181 public Object clone() 1182 { 1183 try 1184 { 1185 DomNode node = (DomNode) super.clone(); 1186 1187 node.parent = null; 1188 node.depth = 0; 1189 node.index = 0; 1190 node.length = 0; 1191 node.first = null; 1192 node.last = null; 1193 node.previous = null; 1194 node.next = null; 1195 1196 node.readonly = false; 1197 node.listeners = new HashSet(); 1198 node.nListeners = 0; 1199 return node; 1200 1201 } 1202 catch (CloneNotSupportedException x) 1203 { 1204 throw new Error("clone didn't work"); 1205 } 1206 } 1207 1208 // the elements-by-tagname stuff is needed for both 1209 // elements and documents ... this is in lieu of a 1210 // common base class between Node and NodeNS. 1211 1212 /** 1213 * <b>DOM L1</b> 1214 * Creates a NodeList giving array-style access to elements with 1215 * the specified name. Access is fastest if indices change by 1216 * small values, and the DOM is not modified. 1217 */ getElementsByTagName(String tag)1218 public NodeList getElementsByTagName(String tag) 1219 { 1220 return new ShadowList(null, tag); 1221 } 1222 1223 /** 1224 * <b>DOM L2</b> 1225 * Creates a NodeList giving array-style access to elements with 1226 * the specified namespace and local name. Access is fastest if 1227 * indices change by small values, and the DOM is not modified. 1228 */ getElementsByTagNameNS(String namespace, String local)1229 public NodeList getElementsByTagNameNS(String namespace, String local) 1230 { 1231 return new ShadowList(namespace, local); 1232 } 1233 1234 1235 // 1236 // This shadow class is GC-able even when the live list it shadows 1237 // can't be, because of event registration hookups. Its finalizer 1238 // makes that live list become GC-able. 1239 // 1240 final class ShadowList 1241 implements NodeList 1242 { 1243 1244 private LiveNodeList liveList; 1245 ShadowList(String ns, String local)1246 ShadowList(String ns, String local) 1247 { 1248 liveList = new LiveNodeList(ns, local); 1249 } 1250 finalize()1251 public void finalize() 1252 { 1253 liveList.detach(); 1254 liveList = null; 1255 } 1256 item(int index)1257 public Node item(int index) 1258 { 1259 return liveList.item(index); 1260 } 1261 getLength()1262 public int getLength() 1263 { 1264 return liveList.getLength(); 1265 } 1266 } 1267 1268 final class LiveNodeList 1269 implements NodeList, EventListener, NodeFilter 1270 { 1271 1272 private final boolean matchAnyURI; 1273 private final boolean matchAnyName; 1274 private final String elementURI; 1275 private final String elementName; 1276 1277 private DomIterator current; 1278 private int lastIndex; 1279 LiveNodeList(String uri, String name)1280 LiveNodeList(String uri, String name) 1281 { 1282 elementURI = uri; 1283 elementName = name; 1284 matchAnyURI = "*".equals(uri); 1285 matchAnyName = "*".equals(name); 1286 1287 DomNode.this.addEventListener("DOMNodeInserted", this, true); 1288 DomNode.this.addEventListener("DOMNodeRemoved", this, true); 1289 } 1290 detach()1291 void detach() 1292 { 1293 if (current != null) 1294 current.detach(); 1295 current = null; 1296 1297 DomNode.this.removeEventListener("DOMNodeInserted", this, true); 1298 DomNode.this.removeEventListener("DOMNodeRemoved", this, true); 1299 } 1300 acceptNode(Node element)1301 public short acceptNode(Node element) 1302 { 1303 if (element == DomNode.this) 1304 { 1305 return FILTER_SKIP; 1306 } 1307 1308 // use namespace-aware matching ... 1309 if (elementURI != null) 1310 { 1311 if (!(matchAnyURI 1312 || elementURI.equals(element.getNamespaceURI()))) 1313 { 1314 return FILTER_SKIP; 1315 } 1316 if (!(matchAnyName 1317 || elementName.equals(element.getLocalName()))) 1318 { 1319 return FILTER_SKIP; 1320 } 1321 1322 // ... or qName-based kind. 1323 } 1324 else 1325 { 1326 if (!(matchAnyName 1327 || elementName.equals(element.getNodeName()))) 1328 { 1329 return FILTER_SKIP; 1330 } 1331 } 1332 return FILTER_ACCEPT; 1333 } 1334 createIterator()1335 private DomIterator createIterator() 1336 { 1337 return new DomIterator(DomNode.this, 1338 NodeFilter.SHOW_ELEMENT, 1339 this, /* filter */ 1340 true /* expand entity refs */ 1341 ); 1342 } 1343 handleEvent(Event e)1344 public void handleEvent(Event e) 1345 { 1346 MutationEvent mutation = (MutationEvent) e; 1347 Node related = mutation.getRelatedNode(); 1348 1349 // XXX if it's got children ... check all kids too, they 1350 // will invalidate our saved index 1351 1352 if (related.getNodeType() != Node.ELEMENT_NODE || 1353 related.getNodeName() != elementName || 1354 related.getNamespaceURI() != elementURI) 1355 { 1356 return; 1357 } 1358 1359 if (current != null) 1360 current.detach(); 1361 current = null; 1362 } 1363 item(int index)1364 public Node item(int index) 1365 { 1366 if (current == null) 1367 { 1368 current = createIterator(); 1369 lastIndex = -1; 1370 } 1371 1372 // last node or before? go backwards 1373 if (index <= lastIndex) { 1374 while (index != lastIndex) { 1375 current.previousNode (); 1376 lastIndex--; 1377 } 1378 Node ret = current.previousNode (); 1379 current.detach(); 1380 current = null; 1381 return ret; 1382 } 1383 1384 // somewhere after last node 1385 while (++lastIndex != index) 1386 current.nextNode (); 1387 1388 Node ret = current.nextNode (); 1389 current.detach(); 1390 current = null; 1391 return ret; 1392 } 1393 getLength()1394 public int getLength() 1395 { 1396 int retval = 0; 1397 NodeIterator iter = createIterator(); 1398 1399 while (iter.nextNode() != null) 1400 { 1401 retval++; 1402 } 1403 iter.detach(); 1404 return retval; 1405 } 1406 1407 } 1408 1409 // 1410 // EventTarget support 1411 // 1412 static final class ListenerRecord 1413 { 1414 1415 String type; 1416 EventListener listener; 1417 boolean useCapture; 1418 1419 // XXX use JDK 1.2 java.lang.ref.WeakReference to listener, 1420 // and we can both get rid of "shadow" classes and remove 1421 // the need for applications to apply similar trix ... but 1422 // JDK 1.2 support isn't generally available yet 1423 ListenerRecord(String type, EventListener listener, boolean useCapture)1424 ListenerRecord(String type, EventListener listener, boolean useCapture) 1425 { 1426 this.type = type.intern(); 1427 this.listener = listener; 1428 this.useCapture = useCapture; 1429 } 1430 equals(Object o)1431 public boolean equals(Object o) 1432 { 1433 ListenerRecord rec = (ListenerRecord)o; 1434 return listener == rec.listener 1435 && useCapture == rec.useCapture 1436 && type == rec.type; 1437 } 1438 hashCode()1439 public int hashCode() 1440 { 1441 return listener.hashCode() ^ type.hashCode(); 1442 } 1443 } 1444 1445 /** 1446 * <b>DOM L2 (Events)</b> 1447 * Returns an instance of the specified type of event object. 1448 * Understands about DOM Mutation, HTML, and UI events. 1449 * 1450 * <p>If the name of the event type begins with "USER-", then an object 1451 * implementing the "Event" class will be returned; this provides a 1452 * limited facility for application-defined events to use the DOM event 1453 * infrastructure. Alternatively, use one of the standard DOM event 1454 * classes and initialize it using use such a "USER-" event type name; 1455 * or defin, instantiate, and initialize an application-specific subclass 1456 * of DomEvent and pass that to dispatchEvent(). 1457 * 1458 * @param eventType Identifies the particular DOM feature module 1459 * defining the type of event, such as "MutationEvents". 1460 * <em>The event "name" is a different kind of "type".</em> 1461 */ createEvent(String eventType)1462 public Event createEvent(String eventType) 1463 { 1464 eventType = eventType.toLowerCase(); 1465 1466 if ("mutationevents".equals(eventType)) 1467 { 1468 return new DomEvent.DomMutationEvent(null); 1469 } 1470 1471 if ("htmlevents".equals(eventType) 1472 || "events".equals(eventType) 1473 || "user-events".equals(eventType)) 1474 { 1475 return new DomEvent(null); 1476 } 1477 1478 if ("uievents".equals(eventType)) 1479 { 1480 return new DomEvent.DomUIEvent(null); 1481 } 1482 1483 // mouse events 1484 1485 throw new DomDOMException(DOMException.NOT_SUPPORTED_ERR, 1486 eventType, null, 0); 1487 } 1488 1489 /** 1490 * <b>DOM L2 (Events)</b> 1491 * Registers an event listener's interest in a class of events. 1492 */ addEventListener(String type, EventListener listener, boolean useCapture)1493 public final void addEventListener(String type, 1494 EventListener listener, 1495 boolean useCapture) 1496 { 1497 // prune duplicates 1498 ListenerRecord record; 1499 1500 record = new ListenerRecord(type, listener, useCapture); 1501 listeners.add(record); 1502 nListeners = listeners.size(); 1503 } 1504 1505 // XXX this exception should be discarded from DOM 1506 1507 // this class can be instantiated, unlike the one in the spec 1508 static final class DomEventException 1509 extends EventException 1510 { 1511 DomEventException()1512 DomEventException() 1513 { 1514 super(UNSPECIFIED_EVENT_TYPE_ERR, "unspecified event type"); 1515 } 1516 1517 } 1518 1519 /** 1520 * <b>DOM L2 (Events)</b> 1521 * Delivers an event to all relevant listeners, returning true if the 1522 * caller should perform their default action. Note that the event 1523 * must have been provided by the createEvent() method on this 1524 * class, else it can't be dispatched. 1525 * 1526 * @see #createEvent 1527 * 1528 * @exception NullPointerException When a null event is passed. 1529 * @exception ClassCastException When the event wasn't provided by 1530 * the createEvent method, or otherwise isn't a DomEvent. 1531 * @exception EventException If the event type wasn't specified 1532 */ dispatchEvent(Event event)1533 public final boolean dispatchEvent(Event event) 1534 throws EventException 1535 { 1536 DomEvent e = (DomEvent) event; 1537 DomNode[] ancestors = null; 1538 int ancestorMax = 0; 1539 boolean haveDispatchDataLock = false; 1540 1541 if (e.type == null) 1542 { 1543 throw new DomEventException(); 1544 } 1545 1546 e.doDefault = true; 1547 e.target = this; 1548 1549 // 1550 // Typical case: one nonrecursive dispatchEvent call at a time 1551 // for this class. If that's our case, we can avoid allocating 1552 // garbage, which is overall a big win. Even with advanced GCs 1553 // that deal well with short-lived garbage, and wayfast allocators, 1554 // it still helps. 1555 // 1556 // Remember -- EVERY mutation goes though here at least once. 1557 // 1558 // When populating a DOM tree, trying to send mutation events is 1559 // the primary cost; this dominates the critical path. 1560 // 1561 try 1562 { 1563 DomNode current; 1564 int index; 1565 boolean haveAncestorRegistrations = false; 1566 ListenerRecord[] notificationSet; 1567 int ancestorLen; 1568 1569 synchronized (lockNode) 1570 { 1571 if (!dispatchDataLock) 1572 { 1573 haveDispatchDataLock = dispatchDataLock = true; 1574 notificationSet = DomNode.notificationSet; 1575 ancestors = DomNode.ancestors; 1576 } 1577 else 1578 { 1579 notificationSet = new ListenerRecord[NOTIFICATIONS_INIT]; 1580 ancestors = new DomNode[ANCESTORS_INIT]; 1581 } 1582 ancestorLen = ancestors.length; 1583 } 1584 1585 // Climb to the top of this subtree and handle capture, letting 1586 // each node (from the top down) capture until one stops it or 1587 // until we get to this one. 1588 current = (parent == null) ? this : parent; 1589 if (current.depth >= ANCESTORS_INIT) 1590 { 1591 DomNode[] newants = new DomNode[current.depth + 1]; 1592 System.arraycopy(ancestors, 0, newants, 0, ancestors.length); 1593 ancestors = newants; 1594 ancestorLen = ancestors.length; 1595 } 1596 for (index = 0; index < ancestorLen; index++) 1597 { 1598 if (current == null || current.depth == 0) 1599 break; 1600 1601 if (current.nListeners != 0) 1602 { 1603 haveAncestorRegistrations = true; 1604 } 1605 ancestors [index] = current; 1606 current = current.parent; 1607 } 1608 if (current.depth > 0) 1609 { 1610 throw new RuntimeException("dispatchEvent capture stack size"); 1611 } 1612 1613 ancestorMax = index; 1614 e.stop = false; 1615 1616 if (haveAncestorRegistrations) 1617 { 1618 e.eventPhase = Event.CAPTURING_PHASE; 1619 while (!e.stop && index-- > 0) 1620 { 1621 current = ancestors [index]; 1622 if (current.nListeners != 0) 1623 { 1624 notifyNode(e, current, true, notificationSet); 1625 } 1626 } 1627 } 1628 1629 // Always deliver events to the target node (this) 1630 // unless stopPropagation was called. If we saw 1631 // no registrations yet (typical!), we never will. 1632 if (!e.stop && nListeners != 0) 1633 { 1634 e.eventPhase = Event.AT_TARGET; 1635 notifyNode (e, this, false, notificationSet); 1636 } 1637 else if (!haveAncestorRegistrations) 1638 { 1639 e.stop = true; 1640 } 1641 1642 // If the event bubbles and propagation wasn't halted, 1643 // walk back up the ancestor list. Stop bubbling when 1644 // any bubbled event handler stops it. 1645 1646 if (!e.stop && e.bubbles) 1647 { 1648 e.eventPhase = Event.BUBBLING_PHASE; 1649 for (index = 0; 1650 !e.stop 1651 && index < ancestorMax 1652 && (current = ancestors[index]) != null; 1653 index++) 1654 { 1655 if (current.nListeners != 0) 1656 { 1657 notifyNode(e, current, false, notificationSet); 1658 } 1659 } 1660 } 1661 e.eventPhase = 0; 1662 1663 // Caller chooses whether to perform the default 1664 // action based on return from this method. 1665 return e.doDefault; 1666 1667 } 1668 finally 1669 { 1670 if (haveDispatchDataLock) 1671 { 1672 // synchronize to force write ordering 1673 synchronized (lockNode) 1674 { 1675 // null out refs to ensure they'll be GC'd 1676 for (int i = 0; i < ancestorMax; i++) 1677 { 1678 ancestors [i] = null; 1679 } 1680 // notificationSet handled by notifyNode 1681 1682 dispatchDataLock = false; 1683 } 1684 } 1685 } 1686 } 1687 notifyNode(DomEvent e, DomNode current, boolean capture, ListenerRecord[] notificationSet)1688 private void notifyNode(DomEvent e, 1689 DomNode current, 1690 boolean capture, 1691 ListenerRecord[] notificationSet) 1692 { 1693 int count = 0; 1694 Iterator iter; 1695 1696 iter = current.listeners.iterator(); 1697 1698 // do any of this set of listeners get notified? 1699 while (iter.hasNext()) 1700 { 1701 ListenerRecord rec = (ListenerRecord)iter.next(); 1702 1703 if (rec.useCapture != capture) 1704 { 1705 continue; 1706 } 1707 if (!e.type.equals (rec.type)) 1708 { 1709 continue; 1710 } 1711 if (count >= notificationSet.length) 1712 { 1713 // very simple growth algorithm 1714 int len = Math.max(notificationSet.length, 1); 1715 ListenerRecord[] tmp = new ListenerRecord[len * 2]; 1716 System.arraycopy(notificationSet, 0, tmp, 0, 1717 notificationSet.length); 1718 notificationSet = tmp; 1719 } 1720 notificationSet[count++] = rec; 1721 } 1722 iter = null; 1723 1724 // Notify just those listeners 1725 e.currentNode = current; 1726 for (int i = 0; i < count; i++) 1727 { 1728 try 1729 { 1730 iter = current.listeners.iterator(); 1731 // Late in the DOM CR process (3rd or 4th CR?) the 1732 // removeEventListener spec became asymmetric with respect 1733 // to addEventListener ... effect is now immediate. 1734 while (iter.hasNext()) 1735 { 1736 ListenerRecord rec = (ListenerRecord)iter.next(); 1737 1738 if (rec.equals(notificationSet[i])) 1739 { 1740 notificationSet[i].listener.handleEvent(e); 1741 break; 1742 } 1743 } 1744 iter = null; 1745 } 1746 catch (Exception x) 1747 { 1748 // ignore all exceptions 1749 } 1750 notificationSet[i] = null; // free for GC 1751 } 1752 } 1753 1754 /** 1755 * <b>DOM L2 (Events)</b> 1756 * Unregisters an event listener. 1757 */ removeEventListener(String type, EventListener listener, boolean useCapture)1758 public final void removeEventListener(String type, 1759 EventListener listener, 1760 boolean useCapture) 1761 { 1762 listeners.remove(new ListenerRecord(type, listener, useCapture)); 1763 nListeners = listeners.size(); 1764 // no exceptions reported 1765 } 1766 1767 /** 1768 * <b>DOM L1 (relocated in DOM L2)</b> 1769 * In this node and all contained nodes (including attributes if 1770 * relevant) merge adjacent text nodes. This is done while ignoring 1771 * text which happens to use CDATA delimiters). 1772 */ normalize()1773 public final void normalize() 1774 { 1775 // Suspend readonly status 1776 boolean saved = readonly; 1777 readonly = false; 1778 for (DomNode ctx = first; ctx != null; ctx = ctx.next) 1779 { 1780 boolean saved2 = ctx.readonly; 1781 ctx.readonly = false; 1782 switch (ctx.nodeType) 1783 { 1784 case TEXT_NODE: 1785 case CDATA_SECTION_NODE: 1786 while (ctx.next != null && 1787 (ctx.next.nodeType == TEXT_NODE || 1788 ctx.next.nodeType == CDATA_SECTION_NODE)) 1789 { 1790 Text text = (Text) ctx; 1791 text.appendData(ctx.next.getNodeValue()); 1792 removeChild(ctx.next); 1793 } 1794 break; 1795 case ELEMENT_NODE: 1796 NamedNodeMap attrs = ctx.getAttributes(); 1797 int len = attrs.getLength(); 1798 for (int i = 0; i < len; i++) 1799 { 1800 DomNode attr = (DomNode) attrs.item(i); 1801 boolean saved3 = attr.readonly; 1802 attr.readonly = false; 1803 attr.normalize(); 1804 attr.readonly = saved3; 1805 } 1806 // Fall through 1807 case DOCUMENT_NODE: 1808 case DOCUMENT_FRAGMENT_NODE: 1809 case ATTRIBUTE_NODE: 1810 case ENTITY_REFERENCE_NODE: 1811 ctx.normalize(); 1812 break; 1813 } 1814 ctx.readonly = saved2; 1815 } 1816 readonly = saved; 1817 } 1818 1819 /** 1820 * Returns true iff node types match, and either (a) both nodes have no 1821 * namespace and their getNodeName() values are the same, or (b) both 1822 * nodes have the same getNamespaceURI() and same getLocalName() values. 1823 * 1824 * <p>Note that notion of a "Per-Element-Type" attribute name scope, as 1825 * found in a non-normative appendix of the XML Namespaces specification, 1826 * is not supported here. Your application must implement that notion, 1827 * typically by not bothering to check nameAndTypeEquals for attributes 1828 * without namespace URIs unless you already know their elements are 1829 * nameAndTypeEquals. 1830 */ nameAndTypeEquals(Node other)1831 public boolean nameAndTypeEquals(Node other) 1832 { 1833 if (other == this) 1834 { 1835 return true; 1836 } 1837 // node types must match 1838 if (nodeType != other.getNodeType()) 1839 { 1840 return false; 1841 } 1842 1843 // if both have namespaces, do a "full" comparision 1844 // this is a "global" partition 1845 String ns1 = this.getNamespaceURI(); 1846 String ns2 = other.getNamespaceURI(); 1847 1848 if (ns1 != null && ns2 != null) 1849 { 1850 return ns1.equals(ns2) && 1851 equal(getLocalName(), other.getLocalName()); 1852 } 1853 1854 // if neither has a namespace, this is a "no-namespace" name. 1855 if (ns1 == null && ns2 == null) 1856 { 1857 if (!getNodeName().equals(other.getNodeName())) 1858 { 1859 return false; 1860 } 1861 // can test the non-normative "per-element-type" scope here. 1862 // if this is an attribute node and both nodes have been bound 1863 // to elements (!!), then return the nameAndTypeEquals() 1864 // comparison of those elements. 1865 return true; 1866 } 1867 1868 // otherwise they're unequal: one scoped, one not. 1869 return false; 1870 } 1871 1872 // DOM Level 3 methods 1873 getBaseURI()1874 public String getBaseURI() 1875 { 1876 return (parent != null) ? parent.getBaseURI() : null; 1877 } 1878 compareDocumentPosition(Node other)1879 public short compareDocumentPosition(Node other) 1880 throws DOMException 1881 { 1882 return (short) compareTo(other); 1883 } 1884 1885 /** 1886 * DOM nodes have a natural ordering: document order. 1887 */ compareTo(Object other)1888 public final int compareTo(Object other) 1889 { 1890 if (other instanceof DomNode) 1891 { 1892 DomNode n1 = this; 1893 DomNode n2 = (DomNode) other; 1894 if (n1.owner != n2.owner) 1895 { 1896 return 0; 1897 } 1898 int d1 = n1.depth, d2 = n2.depth; 1899 int delta = d1 - d2; 1900 while (d1 > d2) 1901 { 1902 n1 = n1.parent; 1903 d1--; 1904 } 1905 while (d2 > d1) 1906 { 1907 n2 = n2.parent; 1908 d2--; 1909 } 1910 int c = compareTo2(n1, n2); 1911 return (c != 0) ? c : delta; 1912 } 1913 return 0; 1914 } 1915 1916 /** 1917 * Compare two nodes at the same depth. 1918 */ compareTo2(DomNode n1, DomNode n2)1919 final int compareTo2(DomNode n1, DomNode n2) 1920 { 1921 if (n1 == n2 || n1.depth == 0 || n2.depth == 0) 1922 { 1923 return 0; 1924 } 1925 int c = compareTo2(n1.parent, n2.parent); 1926 return (c != 0) ? c : n1.index - n2.index; 1927 } 1928 getTextContent()1929 public final String getTextContent() 1930 throws DOMException 1931 { 1932 return getTextContent(true); 1933 } 1934 getTextContent(boolean topLevel)1935 final String getTextContent(boolean topLevel) 1936 throws DOMException 1937 { 1938 switch (nodeType) 1939 { 1940 case ELEMENT_NODE: 1941 case ENTITY_NODE: 1942 case ENTITY_REFERENCE_NODE: 1943 case DOCUMENT_FRAGMENT_NODE: 1944 CPStringBuilder buffer = new CPStringBuilder(); 1945 for (DomNode ctx = first; ctx != null; ctx = ctx.next) 1946 { 1947 String textContent = ctx.getTextContent(false); 1948 if (textContent != null) 1949 { 1950 buffer.append(textContent); 1951 } 1952 } 1953 return buffer.toString(); 1954 case TEXT_NODE: 1955 case CDATA_SECTION_NODE: 1956 if (((Text) this).isElementContentWhitespace()) 1957 { 1958 return ""; 1959 } 1960 return getNodeValue(); 1961 case ATTRIBUTE_NODE: 1962 return getNodeValue(); 1963 case COMMENT_NODE: 1964 case PROCESSING_INSTRUCTION_NODE: 1965 return topLevel ? getNodeValue() : ""; 1966 default: 1967 return null; 1968 } 1969 } 1970 setTextContent(String textContent)1971 public void setTextContent(String textContent) 1972 throws DOMException 1973 { 1974 switch (nodeType) 1975 { 1976 case ELEMENT_NODE: 1977 case ATTRIBUTE_NODE: 1978 case ENTITY_NODE: 1979 case ENTITY_REFERENCE_NODE: 1980 case DOCUMENT_FRAGMENT_NODE: 1981 for (DomNode ctx = first; ctx != null; ) 1982 { 1983 DomNode n = ctx.next; 1984 removeChild(ctx); 1985 ctx = n; 1986 } 1987 if (textContent != null) 1988 { 1989 Text text = owner.createTextNode(textContent); 1990 appendChild(text); 1991 } 1992 break; 1993 case TEXT_NODE: 1994 case CDATA_SECTION_NODE: 1995 case COMMENT_NODE: 1996 case PROCESSING_INSTRUCTION_NODE: 1997 setNodeValue(textContent); 1998 break; 1999 } 2000 } 2001 isSameNode(Node other)2002 public boolean isSameNode(Node other) 2003 { 2004 return this == other; 2005 } 2006 lookupPrefix(String namespaceURI)2007 public String lookupPrefix(String namespaceURI) 2008 { 2009 return (parent == null || parent == owner) ? null : 2010 parent.lookupPrefix(namespaceURI); 2011 } 2012 isDefaultNamespace(String namespaceURI)2013 public boolean isDefaultNamespace(String namespaceURI) 2014 { 2015 return (parent == null || parent == owner) ? false : 2016 parent.isDefaultNamespace(namespaceURI); 2017 } 2018 lookupNamespaceURI(String prefix)2019 public String lookupNamespaceURI(String prefix) 2020 { 2021 return (parent == null || parent == owner) ? null : 2022 parent.lookupNamespaceURI(prefix); 2023 } 2024 isEqualNode(Node arg)2025 public boolean isEqualNode(Node arg) 2026 { 2027 if (this == arg) 2028 return true; 2029 if (arg == null) 2030 return false; 2031 if (nodeType != arg.getNodeType()) 2032 return false; 2033 switch (nodeType) 2034 { 2035 case ELEMENT_NODE: 2036 case ATTRIBUTE_NODE: 2037 if (!equal(getLocalName(), arg.getLocalName()) || 2038 !equal(getNamespaceURI(), arg.getNamespaceURI())) 2039 return false; 2040 break; 2041 case PROCESSING_INSTRUCTION_NODE: 2042 if (!equal(getNodeName(), arg.getNodeName()) || 2043 !equal(getNodeValue(), arg.getNodeValue())) 2044 return false; 2045 break; 2046 case COMMENT_NODE: 2047 case TEXT_NODE: 2048 case CDATA_SECTION_NODE: 2049 if (!equal(getNodeValue(), arg.getNodeValue())) 2050 return false; 2051 break; 2052 } 2053 // Children 2054 Node argCtx = arg.getFirstChild(); 2055 getFirstChild(); // because of DomAttr lazy children 2056 DomNode ctx = first; 2057 for (; ctx != null && argCtx != null; ctx = ctx.next) 2058 { 2059 if (nodeType == DOCUMENT_NODE) 2060 { 2061 // Ignore whitespace outside document element 2062 while (ctx != null && ctx.nodeType == TEXT_NODE) 2063 ctx = ctx.next; 2064 while (argCtx != null && ctx.getNodeType() == TEXT_NODE) 2065 argCtx = argCtx.getNextSibling(); 2066 if (ctx == null && argCtx != null) 2067 return false; 2068 else if (argCtx == null && ctx != null) 2069 return false; 2070 } 2071 if (!ctx.isEqualNode(argCtx)) 2072 return false; 2073 argCtx = argCtx.getNextSibling(); 2074 } 2075 if (ctx != null || argCtx != null) 2076 return false; 2077 2078 // TODO DocumentType 2079 return true; 2080 } 2081 equal(String arg1, String arg2)2082 boolean equal(String arg1, String arg2) 2083 { 2084 return ((arg1 == null && arg2 == null) || 2085 (arg1 != null && arg1.equals(arg2))); 2086 } 2087 getFeature(String feature, String version)2088 public Object getFeature(String feature, String version) 2089 { 2090 DOMImplementation impl = (nodeType == DOCUMENT_NODE) ? 2091 ((Document) this).getImplementation() : owner.getImplementation(); 2092 if (impl.hasFeature(feature, version)) 2093 { 2094 return this; 2095 } 2096 return null; 2097 } 2098 setUserData(String key, Object data, UserDataHandler handler)2099 public Object setUserData(String key, Object data, UserDataHandler handler) 2100 { 2101 if (userData == null) 2102 { 2103 userData = new HashMap(); 2104 } 2105 if (handler != null) 2106 { 2107 if (userDataHandlers == null) 2108 { 2109 userDataHandlers = new HashMap(); 2110 } 2111 userDataHandlers.put(key, handler); 2112 } 2113 return userData.put(key, data); 2114 } 2115 getUserData(String key)2116 public Object getUserData(String key) 2117 { 2118 if (userData == null) 2119 { 2120 return null; 2121 } 2122 return userData.get(key); 2123 } 2124 toString()2125 public String toString() 2126 { 2127 String nodeName = getNodeName(); 2128 String nodeValue = getNodeValue(); 2129 CPStringBuilder buf = new CPStringBuilder(getClass().getName()); 2130 buf.append('['); 2131 if (nodeName != null) 2132 { 2133 buf.append(nodeName); 2134 } 2135 if (nodeValue != null) 2136 { 2137 if (nodeName != null) 2138 { 2139 buf.append('='); 2140 } 2141 buf.append('\''); 2142 buf.append(encode(nodeValue)); 2143 buf.append('\''); 2144 } 2145 buf.append(']'); 2146 return buf.toString(); 2147 } 2148 encode(String value)2149 String encode(String value) 2150 { 2151 CPStringBuilder buf = null; 2152 int len = value.length(); 2153 for (int i = 0; i < len; i++) 2154 { 2155 char c = value.charAt(i); 2156 if (c == '\n') 2157 { 2158 if (buf == null) 2159 { 2160 buf = new CPStringBuilder(value.substring(0, i)); 2161 } 2162 buf.append("\\n"); 2163 } 2164 else if (c == '\r') 2165 { 2166 if (buf == null) 2167 { 2168 buf = new CPStringBuilder(value.substring(0, i)); 2169 } 2170 buf.append("\\r"); 2171 } 2172 else if (buf != null) 2173 { 2174 buf.append(c); 2175 } 2176 } 2177 return (buf != null) ? buf.toString() : value; 2178 } 2179 nodeTypeToString(short nodeType)2180 String nodeTypeToString(short nodeType) 2181 { 2182 switch (nodeType) 2183 { 2184 case ELEMENT_NODE: 2185 return "ELEMENT_NODE"; 2186 case ATTRIBUTE_NODE: 2187 return "ATTRIBUTE_NODE"; 2188 case TEXT_NODE: 2189 return "TEXT_NODE"; 2190 case CDATA_SECTION_NODE: 2191 return "CDATA_SECTION_NODE"; 2192 case DOCUMENT_NODE: 2193 return "DOCUMENT_NODE"; 2194 case DOCUMENT_TYPE_NODE: 2195 return "DOCUMENT_TYPE_NODE"; 2196 case COMMENT_NODE: 2197 return "COMMENT_NODE"; 2198 case PROCESSING_INSTRUCTION_NODE: 2199 return "PROCESSING_INSTRUCTION_NODE"; 2200 case DOCUMENT_FRAGMENT_NODE: 2201 return "DOCUMENT_FRAGMENT_NODE"; 2202 case ENTITY_NODE: 2203 return "ENTITY_NODE"; 2204 case ENTITY_REFERENCE_NODE: 2205 return "ENTITY_REFERENCE_NODE"; 2206 case NOTATION_NODE: 2207 return "NOTATION_NODE"; 2208 default: 2209 return "UNKNOWN"; 2210 } 2211 } 2212 list(java.io.PrintStream out, int indent)2213 public void list(java.io.PrintStream out, int indent) 2214 { 2215 for (int i = 0; i < indent; i++) 2216 out.print(" "); 2217 out.println(toString()); 2218 for (DomNode ctx = first; ctx != null; ctx = ctx.next) 2219 ctx.list(out, indent + 1); 2220 } 2221 2222 } 2223