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