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:           }
 672:         
 673:         return child;
 674:       }
 675:     catch (ClassCastException e)
 676:       {
 677:         throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
 678:                                   null, newChild, 0);
 679:       }
 680:   }
 681: 
 682:   /**
 683:    * <b>DOM L1</b>
 684:    * Replaces the specified node in this node's list of children.
 685:    * Document subclasses must override this to test the restrictions
 686:    * that there be only one element and document type child.
 687:    *
 688:    * <p> Causes DOMNodeRemoved and DOMNodeInserted mutation event to be
 689:    * reported.  Will cause another DOMNodeRemoved event to be reported if
 690:    * the newChild parameter already has a parent.  These events may be
 691:    * delivered in any order, except that the event reporting removal
 692:    * from such an existing parent will always be delivered before the
 693:    * event reporting its re-insertion as a child of some other node.
 694:    * The order in which children are removed and inserted is implementation
 695:    * specific.
 696:    *
 697:    * <p> If your application needs to depend on the in which those removal
 698:    * and insertion events are delivered, don't use this API.  Instead,
 699:    * invoke the removeChild and insertBefore methods directly, to guarantee
 700:    * a specific delivery order.  Similarly, don't use document fragments,
 701:    * Otherwise your application code may not work on a DOM which implements
 702:    * this method differently.
 703:    *
 704:    * <p> If this DOM has been compiled without mutation event support,
 705:    * these events will not be reported.
 706:    */
 707:   public Node replaceChild(Node newChild, Node refChild)
 708:   {
 709:     try
 710:       {
 711:         DomNode child = (DomNode) newChild;
 712:         DomNode ref = (DomNode) refChild;
 713:         
 714:         DomEvent.DomMutationEvent event = getMutationEvent();
 715:         boolean doFree = (event != null);
 716:             
 717:         if (child.nodeType == DOCUMENT_FRAGMENT_NODE)
 718:           {
 719:             // Append all nodes in the fragment to this node
 720:             for (DomNode ctx = child.first; ctx != null; ctx = ctx.next)
 721:               {
 722:                 checkMisc(ctx);
 723:               }
 724:             if (ref == null || ref.parent != this)
 725:               {
 726:                 throw new DomDOMException(DOMException.NOT_FOUND_ERR,
 727:                                           null, ref, 0);
 728:               }
 729:             
 730:             if (reportMutations)
 731:               {
 732:                 removalEvent(event, ref);
 733:               }
 734:             length--;
 735:             length += child.length;
 736:             
 737:             if (child.length == 0)
 738:               {
 739:                 // Removal
 740:                 if (ref.previous != null)
 741:                   {
 742:                     ref.previous.next = ref.next;
 743:                   }
 744:                 if (ref.next != null)
 745:                   {
 746:                     ref.next.previous = ref.previous;
 747:                   }
 748:                 if (first == ref)
 749:                   {
 750:                     first = ref.next;
 751:                   }
 752:                 if (last == ref)
 753:                   {
 754:                     last = ref.previous;
 755:                   }
 756:               }
 757:             else
 758:               {
 759:                 int i = ref.index;
 760:                 for (DomNode ctx = child.first; ctx != null; ctx = ctx.next)
 761:                   {
 762:                     // Insertion
 763:                     ctx.parent = this;
 764:                     ctx.index = i++;
 765:                     ctx.setDepth(ref.depth);
 766:                     if (ctx == child.first)
 767:                       {
 768:                         ctx.previous = ref.previous;
 769:                       }
 770:                     if (ctx == child.last)
 771:                       {
 772:                         ctx.next = ref.next;
 773:                       }
 774:                   }
 775:                 if (first == ref)
 776:                   {
 777:                     first = child.first;
 778:                   }
 779:                 if (last == ref)
 780:                   {
 781:                     last = child.last;
 782:                   }
 783:               }
 784:           }
 785:         else
 786:           {
 787:             checkMisc(child);
 788:             if (ref == null || ref.parent != this)
 789:               {
 790:                 throw new DomDOMException(DOMException.NOT_FOUND_ERR,
 791:                                           null, ref, 0);
 792:               }
 793:         
 794:             if (reportMutations)
 795:               {
 796:                 removalEvent(event, ref);
 797:               }
 798:             
 799:             if (child.parent != null)
 800:               {
 801:                 child.parent.removeChild(child);
 802:               }
 803:             child.parent = this;
 804:             child.index = ref.index;
 805:             child.setDepth(ref.depth);
 806:             if (ref.previous != null)
 807:               {
 808:                 ref.previous.next = child;
 809:               }
 810:             child.previous = ref.previous;
 811:             if (ref.next != null)
 812:               {
 813:                 ref.next.previous = child;
 814:               }
 815:             child.next = ref.next;
 816:             if (first == ref)
 817:               {
 818:                 first = child;
 819:               }
 820:             if (last == ref)
 821:               {
 822:                 last = child;
 823:               }
 824: 
 825:             if (reportMutations)
 826:               {
 827:                 insertionEvent(event, child);
 828:               }
 829:             if (doFree)
 830:               {
 831:                 freeMutationEvent();
 832:               }
 833:           }
 834:         ref.parent = null;
 835:         ref.index = 0;
 836:         ref.setDepth(0);
 837:         ref.previous = null;
 838:         ref.next = null;
 839:         
 840:         return ref;
 841:       }
 842:     catch (ClassCastException e)
 843:       {
 844:         throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
 845:                                   null, newChild, 0);
 846:       }
 847:   }
 848: 
 849:   /**
 850:    * <b>DOM L1</b>
 851:    * Removes the specified child from this node's list of children,
 852:    * or else reports an exception.
 853:    *
 854:    * <p> Causes a DOMNodeRemoved mutation event to be reported.
 855:    *
 856:    * <p> If this DOM has been compiled without mutation event support,
 857:    * these events will not be reported.
 858:    */
 859:   public Node removeChild(Node refChild)
 860:   {
 861:     try
 862:       {
 863:         DomNode ref = (DomNode) refChild;
 864: 
 865:         if (ref == null || ref.parent != this)
 866:           {
 867:             throw new DomDOMException(DOMException.NOT_FOUND_ERR,
 868:                                       null, ref, 0);
 869:           }
 870:         if (readonly && !owner.building)
 871:           {
 872:             throw new DomDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
 873:                                       null, this, 0);
 874:           }
 875:         
 876:         for (DomNode child = first; child != null; child = child.next)
 877:           {
 878:             if (child == ref)
 879:               {
 880:                 if (reportMutations)
 881:                   {
 882:                     removalEvent(null, child);
 883:                   }
 884: 
 885:                 length--;
 886:                 if (ref.previous != null)
 887:                   {
 888:                     ref.previous.next = ref.next;
 889:                   }
 890:                 if (ref.next != null)
 891:                   {
 892:                     ref.next.previous = ref.previous;
 893:                   }
 894:                 if (first == ref)
 895:                   {
 896:                     first = ref.next;
 897:                   }
 898:                 if (last == ref)
 899:                   {
 900:                     last = ref.previous;
 901:                   }
 902:                 // renumber indices
 903:                 int i = 0;
 904:                 for (DomNode ctx = first; ctx != null; ctx = ctx.next)
 905:                   {
 906:                     ctx.index = i++;
 907:                   }
 908:                 ref.parent = null;
 909:                 ref.setDepth(0);
 910:                 ref.index = 0;
 911:                 ref.previous = null;
 912:                 ref.next = null;
 913:                 
 914:                 return ref;
 915:               }
 916:           }
 917:         throw new DomDOMException(DOMException.NOT_FOUND_ERR,
 918:                                   "that's no child of mine", refChild, 0);
 919:       }
 920:     catch (ClassCastException e)
 921:       {
 922:         throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
 923:                                   null, refChild, 0);
 924:       }
 925:   }
 926: 
 927:   /**
 928:    * <b>DOM L1 (NodeList)</b>
 929:    * Returns the item with the specified index in this NodeList,
 930:    * else null.
 931:    */
 932:   public Node item(int index)
 933:   {
 934:     DomNode child = first;
 935:     int count = 0;
 936:     while (child != null && count < index)
 937:       {
 938:         child = child.next;
 939:         count++;
 940:       }
 941:     return child;
 942:   }
 943: 
 944:   /**
 945:    * <b>DOM L1 (NodeList)</b>
 946:    * Returns the number of elements in this NodeList.
 947:    * (Note that many interfaces have a "Length" property, not just
 948:    * NodeList, and if a node subtype must implement one of those,
 949:    * it will also need to override getChildNodes.)
 950:    */
 951:   public int getLength()
 952:   {
 953:     return length;
 954:   }
 955: 
 956:   /**
 957:    * Minimize extra space consumed by this node to hold children and event
 958:    * listeners.
 959:    */
 960:   public void trimToSize()
 961:   {
 962:   }
 963: 
 964:   /**
 965:    * <b>DOM L1</b>
 966:    * Returns the previous sibling, if one is known.
 967:    */
 968:   public Node getNextSibling()
 969:   {
 970:     return next;
 971:   }
 972: 
 973:   /**
 974:    * <b>DOM L1</b>
 975:    * Returns the previous sibling, if one is known.
 976:    */
 977:   public Node getPreviousSibling()
 978:   {
 979:     return previous;
 980:   }
 981: 
 982:   /**
 983:    * <b>DOM L1</b>
 984:    * Returns the parent node, if one is known.
 985:    */
 986:   public Node getParentNode()
 987:   {
 988:     return parent;
 989:   }
 990: 
 991:   /**
 992:    * <b>DOM L2</b>
 993:    * Consults the DOM implementation to determine if the requested
 994:    * feature is supported.  DocumentType subclasses must override
 995:    * this method, and associate themselves directly with the
 996:    * DOMImplementation node used.  (This method relies on being able
 997:    * to access the DOMImplementation from the owner document, but
 998:    * DocumentType nodes can be created without an owner.)
 999:    */
1000:   public boolean isSupported(String feature, String version)
1001:   {
1002:     Document        doc = owner;
1003:     DOMImplementation    impl = null;
1004:     
1005:     if (doc == null && nodeType == DOCUMENT_NODE)
1006:       {
1007:         doc = (Document) this;
1008:       }
1009: 
1010:     if (doc == null)
1011:       {
1012:         // possible for DocumentType
1013:         throw new IllegalStateException ("unbound ownerDocument");
1014:       }
1015: 
1016:     impl = doc.getImplementation();
1017:     return impl.hasFeature(feature, version);
1018:   }
1019: 
1020:   /**
1021:    * <b>DOM L1 (modified in L2)</b>
1022:    * Returns the owner document.  This is only null for Document nodes,
1023:    * and (new in L2) for DocumentType nodes which have not yet been
1024:    * associated with the rest of their document.
1025:    */
1026:   final public Document getOwnerDocument()
1027:   {
1028:     return owner;
1029:   }
1030: 
1031:   /**
1032:    * <b>DOM L1</b>
1033:    * Does nothing; this must be overridden (along with the
1034:    * getNodeValue method) for nodes with a non-null defined value.
1035:    */
1036:   public void setNodeValue(String value)
1037:   {
1038:   }
1039: 
1040:   /**
1041:    * <b>DOM L1</b>
1042:    * Returns null; this must be overridden for nodes types with
1043:    * a defined value, along with the setNodeValue method.
1044:    */
1045:   public String getNodeValue()
1046:   {
1047:     return null;
1048:   }
1049: 
1050:   /** This forces GCJ compatibility.
1051:    * Without this method GCJ is unable to compile to byte code.
1052:    */
1053:   public final short getNodeType()
1054:   {
1055:     return nodeType;
1056:   }
1057: 
1058:   /** This forces GCJ compatibility.
1059:    * Without this method GCJ seems unable to natively compile GNUJAXP.
1060:    */
1061:   public abstract String getNodeName();
1062: 
1063:   /**
1064:    * <b>DOM L2</b>
1065:    * Does nothing; this must be overridden (along with the
1066:    * getPrefix method) for element and attribute nodes.
1067:    */
1068:   public void setPrefix(String prefix)
1069:   {
1070:   }
1071: 
1072:   /**
1073:    * <b>DOM L2</b>
1074:    * Returns null; this must be overridden for element and
1075:    * attribute nodes.
1076:    */
1077:   public String getPrefix()
1078:   {
1079:     return null;
1080:   }
1081: 
1082:   /**
1083:    * <b>DOM L2</b>
1084:    * Returns null; this must be overridden for element and
1085:    * attribute nodes.
1086:    */
1087:   public String getNamespaceURI()
1088:   {
1089:     return null;
1090:   }
1091: 
1092:   /**
1093:    * <b>DOM L2</b>
1094:    * Returns the node name; this must be overridden for element and
1095:    * attribute nodes.
1096:    */
1097:   public String getLocalName()
1098:   {
1099:     return null;
1100:   }
1101: 
1102:   /**
1103:    * <b>DOM L1</b>
1104:    * Returns a clone of this node which optionally includes cloned
1105:    * versions of child nodes.  Clones are always mutable, except for
1106:    * entity reference nodes.
1107:    */
1108:   public Node cloneNode(boolean deep)
1109:   {
1110:     DomNode node = (DomNode) clone();
1111:     
1112:     if (deep)
1113:       {
1114:         DomDocument doc = (nodeType == DOCUMENT_NODE) ?
1115:           (DomDocument) node : node.owner;
1116:         boolean building = doc.building;
1117:         doc.building = true; // Permit certain structural rules
1118:         for (DomNode ctx = first; ctx != null; ctx = ctx.next)
1119:           {
1120:             DomNode newChild = (DomNode) ctx.cloneNode(deep);
1121:             newChild.setOwner(doc);
1122:             node.appendChild(newChild);
1123:           }
1124:         doc.building = building;
1125:       }
1126:     if (nodeType == ENTITY_REFERENCE_NODE)
1127:       {
1128:         node.makeReadonly();
1129:       }
1130:     notifyUserDataHandlers(UserDataHandler.NODE_CLONED, this, node);
1131:     return node;
1132:   }
1133: 
1134:   void notifyUserDataHandlers(short op, Node src, Node dst)
1135:   {
1136:     if (userDataHandlers != null)
1137:       {
1138:         for (Iterator i = userDataHandlers.entrySet().iterator(); i.hasNext(); )
1139:           {
1140:             Map.Entry entry = (Map.Entry) i.next();
1141:             String key = (String) entry.getKey();
1142:             UserDataHandler handler = (UserDataHandler) entry.getValue();
1143:             Object data = userData.get(key);
1144:             handler.handle(op, key, data, src, dst);
1145:           }
1146:       }
1147:   }
1148: 
1149:   /**
1150:    * Clones this node; roughly equivalent to cloneNode(false).
1151:    * Element subclasses must provide a new implementation which
1152:    * invokes this method to handle the basics, and then arranges
1153:    * to clone any element attributes directly.  Attribute subclasses
1154:    * must make similar arrangements, ensuring that existing ties to
1155:    * elements are broken by cloning.
1156:    */
1157:   public Object clone()
1158:   {
1159:     try
1160:       {
1161:         DomNode node = (DomNode) super.clone();
1162:         
1163:         node.parent = null;
1164:         node.depth = 0;
1165:         node.index = 0;
1166:         node.length = 0;
1167:         node.first = null;
1168:         node.last = null;
1169:         node.previous = null;
1170:         node.next = null;
1171:         
1172:         node.readonly = false;
1173:         node.listeners = new HashSet();
1174:         node.nListeners = 0;
1175:         return node;
1176: 
1177:       }
1178:     catch (CloneNotSupportedException x)
1179:       {
1180:         throw new Error("clone didn't work");
1181:       }
1182:   }
1183: 
1184:   // the elements-by-tagname stuff is needed for both
1185:   // elements and documents ... this is in lieu of a
1186:   // common base class between Node and NodeNS.
1187: 
1188:   /**
1189:    * <b>DOM L1</b>
1190:    * Creates a NodeList giving array-style access to elements with
1191:    * the specified name.  Access is fastest if indices change by
1192:    * small values, and the DOM is not modified.
1193:    */
1194:   public NodeList getElementsByTagName(String tag)
1195:   {
1196:     return new ShadowList(null, tag);
1197:   }
1198: 
1199:   /**
1200:    * <b>DOM L2</b>
1201:    * Creates a NodeList giving array-style access to elements with
1202:    * the specified namespace and local name.  Access is fastest if
1203:    * indices change by small values, and the DOM is not modified.
1204:    */
1205:   public NodeList getElementsByTagNameNS(String namespace, String local)
1206:   {
1207:     return new ShadowList(namespace, local);
1208:   }
1209: 
1210: 
1211:   //
1212:   // This shadow class is GC-able even when the live list it shadows
1213:   // can't be, because of event registration hookups.  Its finalizer
1214:   // makes that live list become GC-able.
1215:   //
1216:   final class ShadowList
1217:     implements NodeList
1218:   {
1219: 
1220:     private LiveNodeList liveList;
1221:     
1222:     ShadowList(String ns, String local)
1223:     {
1224:       liveList = new LiveNodeList(ns, local);
1225:     }
1226: 
1227:     public void finalize()
1228:     {
1229:       liveList.detach();
1230:       liveList = null;
1231:     }
1232: 
1233:     public Node item(int index)
1234:     {
1235:       return liveList.item(index);
1236:     }
1237: 
1238:     public int getLength()
1239:     {
1240:       return liveList.getLength();
1241:     }
1242:   }
1243: 
1244:   final class LiveNodeList
1245:     implements NodeList, EventListener, NodeFilter
1246:   {
1247:  
1248:     private final boolean matchAnyURI;
1249:     private final boolean matchAnyName; 
1250:     private final String elementURI;
1251:     private final String elementName;
1252:     
1253:     private DomIterator current;
1254:     private int lastIndex;
1255:     
1256:     LiveNodeList(String uri, String name)
1257:     {
1258:       elementURI = uri;
1259:       elementName = name;
1260:       matchAnyURI = "*".equals(uri);
1261:       matchAnyName = "*".equals(name);
1262: 
1263:       DomNode.this.addEventListener("DOMNodeInserted", this, true);
1264:       DomNode.this.addEventListener("DOMNodeRemoved", this, true);
1265:     }
1266: 
1267:     void detach()
1268:     {
1269:       if (current != null)
1270:         current.detach();
1271:       current = null;
1272: 
1273:       DomNode.this.removeEventListener("DOMNodeInserted", this, true);
1274:       DomNode.this.removeEventListener("DOMNodeRemoved", this, true);
1275:     }
1276: 
1277:     public short acceptNode(Node element)
1278:     {
1279:       if (element == DomNode.this)
1280:         {
1281:           return FILTER_SKIP;
1282:         }
1283: 
1284:       // use namespace-aware matching ...
1285:       if (elementURI != null)
1286:         {
1287:           if (!(matchAnyURI
1288:                 || elementURI.equals(element.getNamespaceURI())))
1289:             {
1290:               return FILTER_SKIP;
1291:             }
1292:           if (!(matchAnyName
1293:                 || elementName.equals(element.getLocalName())))
1294:             {
1295:               return FILTER_SKIP;
1296:             }
1297: 
1298:           // ... or qName-based kind.
1299:         }
1300:       else
1301:         {
1302:           if (!(matchAnyName
1303:                 || elementName.equals(element.getNodeName())))
1304:             {
1305:               return FILTER_SKIP;
1306:             }
1307:         }
1308:       return FILTER_ACCEPT;
1309:     }
1310: 
1311:     private DomIterator createIterator()
1312:     {
1313:       return new DomIterator(DomNode.this,
1314:                              NodeFilter.SHOW_ELEMENT,
1315:                              this,    /* filter */
1316:                              true    /* expand entity refs */
1317:                             );
1318:     }
1319: 
1320:     public void handleEvent(Event e)
1321:     {
1322:       MutationEvent    mutation = (MutationEvent) e;
1323:       Node        related = mutation.getRelatedNode();
1324:       
1325:       // XXX if it's got children ... check all kids too, they
1326:       // will invalidate our saved index
1327:       
1328:       if (related.getNodeType() != Node.ELEMENT_NODE ||
1329:           related.getNodeName() != elementName ||
1330:           related.getNamespaceURI() != elementURI)
1331:         {
1332:           return;
1333:         }
1334:       
1335:       if (current != null)
1336:     current.detach();
1337:       current = null;
1338:     }
1339: 
1340:     public Node item(int index)
1341:     {
1342:       if (current == null)
1343:         {
1344:           current = createIterator();
1345:           lastIndex = -1;
1346:         }
1347:       
1348:       // last node or before?  go backwards
1349:       if (index <= lastIndex) {
1350:         while (index != lastIndex) {
1351:           current.previousNode ();
1352:           lastIndex--;
1353:         }
1354:         Node ret = current.previousNode ();
1355:     current.detach();
1356:         current = null;
1357:         return ret;
1358:       } 
1359:       
1360:       // somewhere after last node
1361:       while (++lastIndex != index)
1362:         current.nextNode ();
1363: 
1364:       Node ret = current.nextNode ();
1365:       current.detach();
1366:       current = null;
1367:       return ret;
1368:     }
1369:     
1370:     public int getLength()
1371:     {
1372:       int retval = 0;
1373:       NodeIterator iter = createIterator();
1374:       
1375:       while (iter.nextNode() != null)
1376:         {
1377:           retval++;
1378:         }
1379:       iter.detach();
1380:       return retval;
1381:     }
1382:     
1383:   }
1384: 
1385:   //
1386:   // EventTarget support
1387:   //
1388:   static final class ListenerRecord
1389:   {
1390:   
1391:     String type;
1392:     EventListener listener;
1393:     boolean useCapture;
1394: 
1395:     // XXX use JDK 1.2 java.lang.ref.WeakReference to listener,
1396:     // and we can both get rid of "shadow" classes and remove
1397:     // the need for applications to apply similar trix ... but
1398:     // JDK 1.2 support isn't generally available yet
1399: 
1400:     ListenerRecord(String type, EventListener listener, boolean useCapture)
1401:     {
1402:       this.type = type.intern();
1403:       this.listener = listener;
1404:       this.useCapture = useCapture;
1405:     }
1406: 
1407:     public boolean equals(Object o)
1408:     {
1409:       ListenerRecord rec = (ListenerRecord)o;
1410:       return listener == rec.listener
1411:         && useCapture == rec.useCapture
1412:         && type == rec.type;
1413:     }
1414:     
1415:     public int hashCode()
1416:     {
1417:     return listener.hashCode() ^ type.hashCode();
1418:     }
1419:   }
1420: 
1421:   /**
1422:    * <b>DOM L2 (Events)</b>
1423:    * Returns an instance of the specified type of event object.
1424:    * Understands about DOM Mutation, HTML, and UI events.
1425:    *
1426:    * <p>If the name of the event type begins with "USER-", then an object
1427:    * implementing the "Event" class will be returned; this provides a
1428:    * limited facility for application-defined events to use the DOM event
1429:    * infrastructure.  Alternatively, use one of the standard DOM event
1430:    * classes and initialize it using use such a "USER-" event type name;
1431:    * or defin, instantiate, and initialize an application-specific subclass
1432:    * of DomEvent and pass that to dispatchEvent().
1433:    *
1434:    * @param eventType Identifies the particular DOM feature module
1435:    *    defining the type of event, such as "MutationEvents".
1436:    *    <em>The event "name" is a different kind of "type".</em>
1437:    */
1438:   public Event createEvent(String eventType)
1439:   {
1440:     eventType = eventType.toLowerCase();
1441:     
1442:     if ("mutationevents".equals(eventType))
1443:       {
1444:         return new DomEvent.DomMutationEvent(null);
1445:       }
1446:     
1447:     if ("htmlevents".equals(eventType)
1448:         || "events".equals(eventType)
1449:         || "user-events".equals(eventType))
1450:       {
1451:         return new DomEvent(null);
1452:       }
1453:     
1454:     if ("uievents".equals(eventType))
1455:       {
1456:         return new DomEvent.DomUIEvent(null);
1457:       }
1458: 
1459:     // mouse events 
1460:     
1461:     throw new DomDOMException(DOMException.NOT_SUPPORTED_ERR,
1462:                               eventType, null, 0);
1463:   }
1464: 
1465:   /**
1466:    * <b>DOM L2 (Events)</b>
1467:    * Registers an event listener's interest in a class of events.
1468:    */
1469:   public final void addEventListener(String type,
1470:                                      EventListener listener,
1471:                                      boolean useCapture)
1472:   {
1473:     // prune duplicates
1474:     ListenerRecord record;
1475: 
1476:     record = new ListenerRecord(type, listener, useCapture);
1477:     listeners.add(record);
1478:     nListeners = listeners.size();
1479:   }
1480: 
1481:   // XXX this exception should be discarded from DOM
1482: 
1483:   // this class can be instantiated, unlike the one in the spec
1484:   static final class DomEventException
1485:     extends EventException
1486:   {
1487:    
1488:     DomEventException()
1489:     {
1490:       super(UNSPECIFIED_EVENT_TYPE_ERR, "unspecified event type");
1491:     }
1492:     
1493:   }
1494: 
1495:   /**
1496:    * <b>DOM L2 (Events)</b>
1497:    * Delivers an event to all relevant listeners, returning true if the
1498:    * caller should perform their default action.  Note that the event
1499:    * must have been provided by the createEvent() method on this
1500:    * class, else it can't be dispatched.
1501:    *
1502:    * @see #createEvent
1503:    *
1504:    * @exception NullPointerException When a null event is passed.
1505:    * @exception ClassCastException When the event wasn't provided by
1506:    *    the createEvent method, or otherwise isn't a DomEvent.
1507:    * @exception EventException If the event type wasn't specified
1508:    */
1509:   public final boolean dispatchEvent(Event event)
1510:     throws EventException
1511:   {
1512:     DomEvent e = (DomEvent) event;
1513:     DomNode[] ancestors = null;
1514:     int ancestorMax = 0;
1515:     boolean haveDispatchDataLock = false;
1516:     
1517:     if (e.type == null)
1518:       {
1519:         throw new DomEventException();
1520:       }
1521: 
1522:     e.doDefault = true;
1523:     e.target = this;
1524:     
1525:     //
1526:     // Typical case:  one nonrecursive dispatchEvent call at a time
1527:     // for this class.  If that's our case, we can avoid allocating
1528:     // garbage, which is overall a big win.  Even with advanced GCs
1529:     // that deal well with short-lived garbage, and wayfast allocators,
1530:     // it still helps.
1531:     //
1532:     // Remember -- EVERY mutation goes though here at least once.
1533:     //
1534:     // When populating a DOM tree, trying to send mutation events is
1535:     // the primary cost; this dominates the critical path.
1536:     //
1537:     try
1538:       {
1539:         DomNode current;
1540:         int index;
1541:         boolean haveAncestorRegistrations = false;
1542:         ListenerRecord[] notificationSet;
1543:         int ancestorLen;
1544:         
1545:         synchronized (lockNode)
1546:           {
1547:             if (!dispatchDataLock)
1548:               {
1549:                 haveDispatchDataLock = dispatchDataLock = true;
1550:                 notificationSet = DomNode.notificationSet;
1551:                 ancestors = DomNode.ancestors;
1552:               }
1553:             else
1554:               {
1555:                 notificationSet = new ListenerRecord[NOTIFICATIONS_INIT];
1556:                 ancestors = new DomNode[ANCESTORS_INIT];
1557:               }
1558:             ancestorLen = ancestors.length;
1559:           }
1560:         
1561:         // Climb to the top of this subtree and handle capture, letting
1562:         // each node (from the top down) capture until one stops it or
1563:         // until we get to this one.
1564:         current = parent;
1565:         if (current.depth >= ANCESTORS_INIT)
1566:           {
1567:             DomNode[] newants = new DomNode[current.depth + 1];
1568:             System.arraycopy(ancestors, 0, newants, 0, ancestors.length);
1569:             ancestors = newants;
1570:             ancestorLen = ancestors.length;
1571:           }
1572:         for (index = 0; index < ancestorLen; index++)
1573:           {
1574:             if (current == null || current.depth == 0)
1575:               break;
1576:             
1577:             if (current.nListeners != 0)
1578:               {
1579:                 haveAncestorRegistrations = true;
1580:               }
1581:             ancestors [index] = current;
1582:             current = current.parent;
1583:           }
1584:         if (current.depth > 0)
1585:           {
1586:             throw new RuntimeException("dispatchEvent capture stack size");
1587:           }
1588:         
1589:         ancestorMax = index;
1590:         e.stop = false;
1591:         
1592:         if (haveAncestorRegistrations)
1593:           {
1594:             e.eventPhase = Event.CAPTURING_PHASE;
1595:             while (!e.stop && index-- > 0)
1596:               {
1597:                 current = ancestors [index];
1598:                 if (current.nListeners != 0)
1599:                   {
1600:                     notifyNode(e, current, true, notificationSet);
1601:                   }
1602:               }
1603:           }
1604:         
1605:         // Always deliver events to the target node (this)
1606:         // unless stopPropagation was called.  If we saw
1607:         // no registrations yet (typical!), we never will.
1608:         if (!e.stop && nListeners != 0)
1609:           {
1610:             e.eventPhase = Event.AT_TARGET;
1611:             notifyNode (e, this, false, notificationSet);
1612:           }
1613:         else if (!haveAncestorRegistrations)
1614:           {
1615:             e.stop = true;
1616:           }
1617:         
1618:         // If the event bubbles and propagation wasn't halted,
1619:         // walk back up the ancestor list.  Stop bubbling when
1620:         // any bubbled event handler stops it.
1621:         
1622:         if (!e.stop && e.bubbles)
1623:           {
1624:             e.eventPhase = Event.BUBBLING_PHASE;
1625:             for (index = 0;
1626:                  !e.stop
1627:                  && index < ancestorMax
1628:                  && (current = ancestors[index]) != null;
1629:                  index++)
1630:               {
1631:                 if (current.nListeners != 0)
1632:                   {
1633:                     notifyNode(e, current, false, notificationSet);
1634:                   }
1635:               }
1636:           }
1637:         e.eventPhase = 0;
1638:         
1639:         // Caller chooses whether to perform the default
1640:         // action based on return from this method.
1641:         return e.doDefault;
1642:         
1643:       }
1644:     finally
1645:       {
1646:         if (haveDispatchDataLock)
1647:           {
1648:             // synchronize to force write ordering
1649:             synchronized (lockNode)
1650:               {
1651:                 // null out refs to ensure they'll be GC'd
1652:                 for (int i = 0; i < ancestorMax; i++)
1653:                   {
1654:                     ancestors [i] = null;
1655:                   }
1656:                 // notificationSet handled by notifyNode
1657:                 
1658:                 dispatchDataLock = false;
1659:               }
1660:           }
1661:       }
1662:   }
1663:   
1664:   private void notifyNode(DomEvent e,
1665:                           DomNode current,
1666:                           boolean capture,
1667:                           ListenerRecord[] notificationSet)
1668:   {
1669:     int count = 0;
1670:     Iterator iter;
1671: 
1672:     iter = current.listeners.iterator();
1673: 
1674:     // do any of this set of listeners get notified?
1675:     while (iter.hasNext())
1676:       {
1677:         ListenerRecord rec = (ListenerRecord)iter.next();
1678: 
1679:         if (rec.useCapture != capture)
1680:           {
1681:             continue;
1682:           }
1683:         if (!e.type.equals (rec.type)) 
1684:           {
1685:             continue;
1686:           }
1687:         if (count >= notificationSet.length)
1688:           {
1689:             // very simple growth algorithm
1690:             int len = Math.max(notificationSet.length, 1);
1691:             ListenerRecord[] tmp = new ListenerRecord[len * 2];
1692:             System.arraycopy(notificationSet, 0, tmp, 0,
1693:                              notificationSet.length);
1694:             notificationSet = tmp;
1695:           }
1696:         notificationSet[count++] = rec;
1697:       }
1698:     iter = null;
1699: 
1700:     // Notify just those listeners
1701:     e.currentNode = current; 
1702:     for (int i = 0; i < count; i++)
1703:       {
1704:         try
1705:           {
1706:         iter = current.listeners.iterator();
1707:             // Late in the DOM CR process (3rd or 4th CR?) the
1708:             // removeEventListener spec became asymmetric with respect
1709:             // to addEventListener ... effect is now immediate.
1710:         while (iter.hasNext())
1711:               {
1712:         ListenerRecord rec = (ListenerRecord)iter.next();
1713: 
1714:                 if (rec.equals(notificationSet[i]))
1715:                   {
1716:                     notificationSet[i].listener.handleEvent(e);
1717:                     break;
1718:                   }
1719:               }
1720:             iter = null;
1721:           }
1722:         catch (Exception x)
1723:           {
1724:             // ignore all exceptions
1725:           }
1726:         notificationSet[i] = null;        // free for GC
1727:       }
1728:   }
1729: 
1730:   /**
1731:    * <b>DOM L2 (Events)</b>
1732:    * Unregisters an event listener.
1733:    */
1734:   public final void removeEventListener(String type,
1735:                                         EventListener listener,
1736:                                         boolean useCapture)
1737:   {
1738:     listeners.remove(new ListenerRecord(type, listener, useCapture));
1739:     nListeners = listeners.size();
1740:     // no exceptions reported
1741:   }
1742: 
1743:   /**
1744:    * <b>DOM L1 (relocated in DOM L2)</b>
1745:    * In this node and all contained nodes (including attributes if
1746:    * relevant) merge adjacent text nodes.  This is done while ignoring
1747:    * text which happens to use CDATA delimiters).
1748:    */
1749:   public final void normalize()
1750:   {
1751:     // Suspend readonly status
1752:     boolean saved = readonly;
1753:     readonly = false;
1754:     for (DomNode ctx = first; ctx != null; ctx = ctx.next)
1755:       {
1756:         boolean saved2 = ctx.readonly;
1757:         ctx.readonly = false;
1758:         switch (ctx.nodeType)
1759:           {
1760:           case TEXT_NODE:
1761:           case CDATA_SECTION_NODE:
1762:             while (ctx.next != null &&
1763:                    (ctx.next.nodeType == TEXT_NODE ||
1764:                     ctx.next.nodeType == CDATA_SECTION_NODE))
1765:               {
1766:                 Text text = (Text) ctx;
1767:                 text.appendData(ctx.next.getNodeValue());
1768:                 removeChild(ctx.next);
1769:               }
1770:             break;
1771:           case ELEMENT_NODE:
1772:             NamedNodeMap attrs = ctx.getAttributes();
1773:             int len = attrs.getLength();
1774:             for (int i = 0; i < len; i++)
1775:               {
1776:                 DomNode attr = (DomNode) attrs.item(i);
1777:                 boolean saved3 = attr.readonly;
1778:                 attr.readonly = false;
1779:                 attr.normalize();
1780:                 attr.readonly = saved3;
1781:               }
1782:             // Fall through
1783:           case DOCUMENT_NODE:
1784:           case DOCUMENT_FRAGMENT_NODE:
1785:           case ATTRIBUTE_NODE:
1786:           case ENTITY_REFERENCE_NODE:
1787:             ctx.normalize();
1788:             break;
1789:           }
1790:         ctx.readonly = saved2;
1791:       }
1792:     readonly = saved;
1793:   }
1794: 
1795:   /**
1796:    * Returns true iff node types match, and either (a) both nodes have no
1797:    * namespace and their getNodeName() values are the same, or (b) both
1798:    * nodes have the same getNamespaceURI() and same getLocalName() values.
1799:    *
1800:    * <p>Note that notion of a "Per-Element-Type" attribute name scope, as
1801:    * found in a non-normative appendix of the XML Namespaces specification,
1802:    * is not supported here.  Your application must implement that notion,
1803:    * typically by not bothering to check nameAndTypeEquals for attributes
1804:    * without namespace URIs unless you already know their elements are
1805:    * nameAndTypeEquals.
1806:    */
1807:   public boolean nameAndTypeEquals(Node other)
1808:   {
1809:     if (other == this)
1810:       {
1811:         return true;
1812:       }
1813:     // node types must match
1814:     if (nodeType != other.getNodeType())
1815:       {
1816:         return false;
1817:       }
1818: 
1819:     // if both have namespaces, do a "full" comparision
1820:     // this is a "global" partition
1821:     String ns1 = this.getNamespaceURI();
1822:     String ns2 = other.getNamespaceURI();
1823: 
1824:     if (ns1 != null && ns2 != null)
1825:       {
1826:         return ns1.equals(ns2) &&
1827:           equal(getLocalName(), other.getLocalName());
1828:       }
1829: 
1830:     // if neither has a namespace, this is a "no-namespace" name.
1831:     if (ns1 == null && ns2 == null)
1832:       {
1833:         if (!getNodeName().equals(other.getNodeName()))
1834:           {
1835:             return false;
1836:           }
1837:         // can test the non-normative "per-element-type" scope here.
1838:         // if this is an attribute node and both nodes have been bound
1839:         // to elements (!!), then return the nameAndTypeEquals()
1840:         // comparison of those elements.
1841:         return true;
1842:       }
1843: 
1844:     // otherwise they're unequal: one scoped, one not.
1845:     return false;
1846:   }
1847: 
1848:   // DOM Level 3 methods
1849: 
1850:   public String getBaseURI()
1851:   {
1852:     return (parent != null) ? parent.getBaseURI() : null;
1853:   }
1854: 
1855:   public short compareDocumentPosition(Node other)
1856:     throws DOMException
1857:   {
1858:     return (short) compareTo(other);
1859:   }
1860: 
1861:   /**
1862:    * DOM nodes have a natural ordering: document order.
1863:    */
1864:   public final int compareTo(Object other)
1865:   {
1866:     if (other instanceof DomNode)
1867:       {
1868:         DomNode n1 = this;
1869:         DomNode n2 = (DomNode) other;
1870:         if (n1.owner != n2.owner)
1871:           {
1872:             return 0;
1873:           }
1874:         int d1 = n1.depth, d2 = n2.depth;
1875:         int delta = d1 - d2;
1876:         while (d1 > d2)
1877:           {
1878:             n1 = n1.parent;
1879:             d1--;
1880:           }
1881:         while (d2 > d1)
1882:           {
1883:             n2 = n2.parent;
1884:             d2--;
1885:           }
1886:         int c = compareTo2(n1, n2);
1887:         return (c != 0) ? c : delta;
1888:       }
1889:     return 0;
1890:   }
1891: 
1892:   /**
1893:    * Compare two nodes at the same depth.
1894:    */
1895:   final int compareTo2(DomNode n1, DomNode n2)
1896:   {
1897:     if (n1 == n2 || n1.depth == 0 || n2.depth == 0)
1898:       {
1899:         return 0;
1900:       }
1901:     int c = compareTo2(n1.parent, n2.parent);
1902:     return (c != 0) ? c : n1.index - n2.index;
1903:   }
1904: 
1905:   public final String getTextContent()
1906:     throws DOMException
1907:   {
1908:     return getTextContent(true);
1909:   }
1910: 
1911:   final String getTextContent(boolean topLevel)
1912:     throws DOMException
1913:   {
1914:     switch (nodeType)
1915:       {
1916:       case ELEMENT_NODE:
1917:       case ENTITY_NODE:
1918:       case ENTITY_REFERENCE_NODE:
1919:       case DOCUMENT_FRAGMENT_NODE:
1920:         StringBuffer buffer = new StringBuffer();
1921:         for (DomNode ctx = first; ctx != null; ctx = ctx.next)
1922:           {
1923:             String textContent = ctx.getTextContent(false);
1924:             if (textContent != null)
1925:               {
1926:                 buffer.append(textContent);
1927:               }
1928:           }
1929:         return buffer.toString();
1930:       case TEXT_NODE:
1931:       case CDATA_SECTION_NODE:
1932:         if (((Text) this).isElementContentWhitespace())
1933:           {
1934:             return "";
1935:           }
1936:         return getNodeValue();
1937:       case ATTRIBUTE_NODE:
1938:         return getNodeValue();
1939:       case COMMENT_NODE:
1940:       case PROCESSING_INSTRUCTION_NODE:
1941:         return topLevel ? getNodeValue() : "";
1942:       default:
1943:         return null;
1944:       }
1945:   }
1946: 
1947:   public void setTextContent(String textContent)
1948:     throws DOMException
1949:   {
1950:     switch (nodeType)
1951:       {
1952:       case ELEMENT_NODE:
1953:       case ATTRIBUTE_NODE:
1954:       case ENTITY_NODE:
1955:       case ENTITY_REFERENCE_NODE:
1956:       case DOCUMENT_FRAGMENT_NODE:
1957:         for (DomNode ctx = first; ctx != null; )
1958:           {
1959:             DomNode n = ctx.next;
1960:             removeChild(ctx);
1961:             ctx = n;
1962:           }
1963:         if (textContent != null)
1964:           {
1965:             Text text = owner.createTextNode(textContent);
1966:             appendChild(text);
1967:           }
1968:         break;
1969:       case TEXT_NODE:
1970:       case CDATA_SECTION_NODE:
1971:       case COMMENT_NODE:
1972:       case PROCESSING_INSTRUCTION_NODE:
1973:         setNodeValue(textContent);
1974:         break;
1975:       }
1976:   }
1977: 
1978:   public boolean isSameNode(Node other)
1979:   {
1980:     return this == other;
1981:   }
1982: 
1983:   public String lookupPrefix(String namespaceURI)
1984:   {
1985:     return (parent == null || parent == owner) ? null :
1986:       parent.lookupPrefix(namespaceURI);
1987:   }
1988: 
1989:   public boolean isDefaultNamespace(String namespaceURI)
1990:   {
1991:     return (parent == null || parent == owner) ? false :
1992:       parent.isDefaultNamespace(namespaceURI);
1993:   }
1994: 
1995:   public String lookupNamespaceURI(String prefix)
1996:   {
1997:     return (parent == null || parent == owner) ? null :
1998:       parent.lookupNamespaceURI(prefix);
1999:   }
2000: 
2001:   public boolean isEqualNode(Node arg)
2002:   {
2003:     if (this == arg)
2004:       return true;
2005:     if (arg == null)
2006:       return false;
2007:     if (nodeType != arg.getNodeType())
2008:       return false;
2009:     switch (nodeType)
2010:       {
2011:       case ELEMENT_NODE:
2012:       case ATTRIBUTE_NODE:
2013:         if (!equal(getLocalName(), arg.getLocalName()) ||
2014:             !equal(getNamespaceURI(), arg.getNamespaceURI()))
2015:           return false;
2016:         break;
2017:       case PROCESSING_INSTRUCTION_NODE:
2018:         if (!equal(getNodeName(), arg.getNodeName()) ||
2019:             !equal(getNodeValue(), arg.getNodeValue()))
2020:           return false;
2021:         break;
2022:       case COMMENT_NODE:
2023:       case TEXT_NODE:
2024:       case CDATA_SECTION_NODE:
2025:         if (!equal(getNodeValue(), arg.getNodeValue()))
2026:           return false;
2027:         break;
2028:       }
2029:     // Children
2030:     Node argCtx = arg.getFirstChild();
2031:     getFirstChild(); // because of DomAttr lazy children
2032:     DomNode ctx = first;
2033:     for (; ctx != null && argCtx != null; ctx = ctx.next)
2034:       {
2035:         if (nodeType == DOCUMENT_NODE)
2036:           {
2037:             // Ignore whitespace outside document element
2038:             while (ctx != null && ctx.nodeType == TEXT_NODE)
2039:               ctx = ctx.next;
2040:             while (argCtx != null && ctx.getNodeType() == TEXT_NODE)
2041:               argCtx = argCtx.getNextSibling();
2042:             if (ctx == null && argCtx != null)
2043:               return false;
2044:             else if (argCtx == null && ctx != null)
2045:               return false;
2046:           }
2047:         if (!ctx.isEqualNode(argCtx))
2048:           return false;
2049:         argCtx = argCtx.getNextSibling();
2050:       }
2051:     if (ctx != null || argCtx != null)
2052:       return false;
2053:     
2054:     // TODO DocumentType
2055:     return true;
2056:   }
2057: 
2058:   boolean equal(String arg1, String arg2)
2059:   {
2060:     return ((arg1 == null && arg2 == null) ||
2061:             (arg1 != null && arg1.equals(arg2))); 
2062:   }
2063:   
2064:   public Object getFeature(String feature, String version)
2065:   {
2066:     DOMImplementation impl = (nodeType == DOCUMENT_NODE) ?
2067:       ((Document) this).getImplementation() : owner.getImplementation();
2068:     if (impl.hasFeature(feature, version))
2069:       {
2070:         return this;
2071:       }
2072:     return null;
2073:   }
2074: 
2075:   public Object setUserData(String key, Object data, UserDataHandler handler)
2076:   {
2077:     if (userData == null)
2078:       {
2079:         userData = new HashMap();
2080:       }
2081:     if (handler != null)
2082:       {
2083:         if (userDataHandlers == null)
2084:           {
2085:             userDataHandlers = new HashMap();
2086:           }
2087:         userDataHandlers.put(key, handler);
2088:       }
2089:     return userData.put(key, data);
2090:   }
2091: 
2092:   public Object getUserData(String key)
2093:   {
2094:     if (userData == null)
2095:       {
2096:         return null;
2097:       }
2098:     return userData.get(key);
2099:   }
2100: 
2101:   public String toString()
2102:   {
2103:     String nodeName = getNodeName();
2104:     String nodeValue = getNodeValue();
2105:     StringBuffer buf = new StringBuffer(getClass().getName());
2106:     buf.append('[');
2107:     if (nodeName != null)
2108:       {
2109:         buf.append(nodeName);
2110:       }
2111:     if (nodeValue != null)
2112:       {
2113:         if (nodeName != null)
2114:           {
2115:             buf.append('=');
2116:           }
2117:         buf.append('\'');
2118:         buf.append(encode(nodeValue));
2119:         buf.append('\'');
2120:       }
2121:     buf.append(']');
2122:     return buf.toString();
2123:   }
2124:   
2125:   String encode(String value)
2126:   {
2127:     StringBuffer buf = null;
2128:     int len = value.length();
2129:     for (int i = 0; i < len; i++)
2130:       {
2131:         char c = value.charAt(i);
2132:         if (c == '\n')
2133:           {
2134:             if (buf == null)
2135:               {
2136:                 buf = new StringBuffer(value.substring(0, i));
2137:               }
2138:             buf.append("\\n");
2139:           }
2140:         else if (c == '\r')
2141:           {
2142:             if (buf == null)
2143:               {
2144:                 buf = new StringBuffer(value.substring(0, i));
2145:               }
2146:             buf.append("\\r");
2147:           }
2148:         else if (buf != null)
2149:           {
2150:             buf.append(c);
2151:           }
2152:       }
2153:     return (buf != null) ? buf.toString() : value;
2154:   }
2155: 
2156:   String nodeTypeToString(short nodeType)
2157:   {
2158:     switch (nodeType)
2159:       {
2160:       case ELEMENT_NODE:
2161:         return "ELEMENT_NODE";
2162:       case ATTRIBUTE_NODE:
2163:         return "ATTRIBUTE_NODE";
2164:       case TEXT_NODE:
2165:         return "TEXT_NODE";
2166:       case CDATA_SECTION_NODE:
2167:         return "CDATA_SECTION_NODE";
2168:       case DOCUMENT_NODE:
2169:         return "DOCUMENT_NODE";
2170:       case DOCUMENT_TYPE_NODE:
2171:         return "DOCUMENT_TYPE_NODE";
2172:       case COMMENT_NODE:
2173:         return "COMMENT_NODE";
2174:       case PROCESSING_INSTRUCTION_NODE:
2175:         return "PROCESSING_INSTRUCTION_NODE";
2176:       case DOCUMENT_FRAGMENT_NODE:
2177:         return "DOCUMENT_FRAGMENT_NODE";
2178:       case ENTITY_NODE:
2179:         return "ENTITY_NODE";
2180:       case ENTITY_REFERENCE_NODE:
2181:         return "ENTITY_REFERENCE_NODE";
2182:       case NOTATION_NODE:
2183:         return "NOTATION_NODE";
2184:       default:
2185:         return "UNKNOWN";
2186:       }
2187:   }
2188: 
2189:   public void list(java.io.PrintStream out, int indent)
2190:   {
2191:     for (int i = 0; i < indent; i++)
2192:       out.print(" ");
2193:     out.println(toString());
2194:     for (DomNode ctx = first; ctx != null; ctx = ctx.next)
2195:       ctx.list(out, indent + 1);
2196:   }
2197: 
2198: }