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