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