Source for gnu.xml.dom.DomNode

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