OSDN Git Service

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