1 /*
2
3    Licensed to the Apache Software Foundation (ASF) under one or more
4    contributor license agreements.  See the NOTICE file distributed with
5    this work for additional information regarding copyright ownership.
6    The ASF licenses this file to You under the Apache License, Version 2.0
7    (the "License"); you may not use this file except in compliance with
8    the License.  You may obtain a copy of the License at
9
10        http://www.apache.org/licenses/LICENSE-2.0
11
12    Unless required by applicable law or agreed to in writing, software
13    distributed under the License is distributed on an "AS IS" BASIS,
14    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15    See the License for the specific language governing permissions and
16    limitations under the License.
17
18  */

19 package org.apache.batik.dom;
20
21 import java.io.Serializable;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.Map;
25
26 import org.apache.batik.dom.events.DOMMutationEvent;
27 import org.apache.batik.dom.events.EventSupport;
28 import org.apache.batik.dom.events.NodeEventTarget;
29 import org.apache.batik.dom.util.DOMUtilities;
30 import org.apache.batik.dom.util.XMLSupport;
31 import org.apache.batik.dom.xbl.NodeXBL;
32 import org.apache.batik.dom.xbl.XBLManagerData;
33 import org.apache.batik.util.ParsedURL;
34 import org.apache.batik.constants.XMLConstants;
35
36 import org.w3c.dom.Attr;
37 import org.w3c.dom.DOMException;
38 import org.w3c.dom.Document;
39 import org.w3c.dom.DocumentType;
40 import org.w3c.dom.Element;
41 import org.w3c.dom.NamedNodeMap;
42 import org.w3c.dom.Node;
43 import org.w3c.dom.NodeList;
44 import org.w3c.dom.UserDataHandler;
45 import org.w3c.dom.events.Event;
46 import org.w3c.dom.events.EventException;
47 import org.w3c.dom.events.EventListener;
48 import org.w3c.dom.events.MutationEvent;
49
50 /**
51  * This class implements the {@link org.w3c.dom.Node} interface.
52  *
53  * @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
54  * @version $Id: AbstractNode.java 1851346 2019-01-15 13:41:00Z ssteiner $
55  */

56 public abstract class AbstractNode
57     implements ExtendedNode,
58                NodeXBL,
59                XBLManagerData,
60                Serializable {
61
62     /**
63      * An empty instance of NodeList.
64      */

65     public static final NodeList EMPTY_NODE_LIST = new NodeList() {
66         public Node item(int i) { return null; }
67         public int  getLength() { return 0; }
68     };
69
70     /**
71      * The owner document.
72      */

73     protected AbstractDocument ownerDocument;
74
75     /**
76      * The event support.
77      */

78     protected transient EventSupport eventSupport;
79
80     /**
81      * User data.
82      */

83     protected HashMap userData;
84
85     /**
86      * User data handlers.
87      */

88     protected HashMap userDataHandlers;
89
90     /**
91      * The XBL manager data.
92      */

93     protected Object managerData;
94
95     /**
96      * Sets the name of this node.
97      * Do nothing.
98      */

99     public void setNodeName(String v) {
100     }
101
102     /**
103      * Sets the owner document of this node.
104      */

105     public void setOwnerDocument(Document doc) {
106         ownerDocument = (AbstractDocument)doc;
107     }
108
109      /**
110      * Sets the value of the specified attribute. This method only applies
111      * to Attr objects.
112      */

113     public void setSpecified(boolean v) {
114         throw createDOMException(DOMException.INVALID_STATE_ERR,
115                                  "node.type",
116                                  new Object[] {(int) getNodeType(),
117                                                 getNodeName()});
118     }
119
120     /**
121      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#getNodeValue()}.
122      * @return null.
123      */

124     public String getNodeValue() throws DOMException {
125         return null;
126     }
127
128     /**
129      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#setNodeValue(String)}.
130      * Do nothing.
131      */

132     public void setNodeValue(String nodeValue) throws DOMException {
133     }
134
135     /**
136      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#getParentNode()}.
137      * @return null.
138      */

139     public Node getParentNode() {
140         return null;
141     }
142
143     /**
144      * Sets the parent node.
145      * Throws a HIERARCHY_REQUEST_ERR {@link org.w3c.dom.DOMException}.
146      */

147     public void setParentNode(Node v) {
148         throw createDOMException(DOMException.HIERARCHY_REQUEST_ERR,
149                                  "parent.not.allowed",
150                                  new Object[] {(int) getNodeType(),
151                                                 getNodeName() });
152     }
153
154     /**
155      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#getChildNodes()}.
156      * @return {@link #EMPTY_NODE_LIST}.
157      */

158     public NodeList getChildNodes() {
159         return EMPTY_NODE_LIST;
160     }
161
162     /**
163      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#getFirstChild()}.
164      * @return null.
165      */

166     public Node getFirstChild() {
167         return null;
168     }
169
170     /**
171      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#getLastChild()}.
172      * @return null.
173      */

174     public Node getLastChild() {
175         return null;
176     }
177
178     /**
179      * Sets the node immediately preceding this node.
180      * Throws a HIERARCHY_REQUEST_ERR {@link org.w3c.dom.DOMException}.
181      */

182     public void setPreviousSibling(Node n) {
183         throw createDOMException(DOMException.HIERARCHY_REQUEST_ERR,
184                                  "sibling.not.allowed",
185                                  new Object[] {(int) getNodeType(),
186                                                 getNodeName() });
187     }
188
189     /**
190      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#getPreviousSibling()}.
191      * @return null.
192      */

193     public Node getPreviousSibling() {
194         return null;
195     }
196
197     /**
198      * Sets the node immediately following this node.
199      * Throws a HIERARCHY_REQUEST_ERR {@link org.w3c.dom.DOMException}.
200      */

201     public void setNextSibling(Node n) {
202         throw createDOMException(DOMException.HIERARCHY_REQUEST_ERR,
203                                  "sibling.not.allowed",
204                                  new Object[] {(int) getNodeType(),
205                                                 getNodeName() });
206     }
207
208     /**
209      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#getNextSibling()}.
210      * @return null.
211      */

212     public Node getNextSibling() {
213         return null;
214     }
215
216     /**
217      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#hasAttributes()}.
218      * @return false.
219      */

220     public boolean hasAttributes() {
221         return false;
222     }
223
224     /**
225      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#getAttributes()}.
226      * @return null.
227      */

228     public NamedNodeMap getAttributes() {
229         return null;
230     }
231
232     /**
233      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#getOwnerDocument()}.
234      * @return {@link #ownerDocument}.
235      */

236     public Document getOwnerDocument() {
237         return ownerDocument;
238     }
239
240     /**
241      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#getNamespaceURI()}.
242      * @return null.
243      */

244     public String getNamespaceURI() {
245         return null;
246     }
247
248     /**
249      * <b>DOM</b>: Implements {@link
250      * org.w3c.dom.Node#insertBefore(Node, Node)}.
251      * Throws a HIERARCHY_REQUEST_ERR {@link org.w3c.dom.DOMException}.
252      */

253     public Node insertBefore(Node newChild, Node refChild)
254         throws DOMException {
255         throw createDOMException(DOMException.HIERARCHY_REQUEST_ERR,
256                                  "children.not.allowed",
257                                  new Object[] {(int) getNodeType(),
258                                                 getNodeName() });
259     }
260
261     /**
262      * <b>DOM</b>: Implements {@link
263      * org.w3c.dom.Node#replaceChild(Node, Node)}.
264      * Throws a HIERARCHY_REQUEST_ERR {@link org.w3c.dom.DOMException}.
265      */

266     public Node replaceChild(Node newChild, Node oldChild)
267         throws DOMException {
268         throw createDOMException(DOMException.HIERARCHY_REQUEST_ERR,
269                                  "children.not.allowed",
270                                  new Object[] {(int) getNodeType(),
271                                                 getNodeName()});
272     }
273
274     /**
275      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#removeChild(Node)}.
276      * Throws a HIERARCHY_REQUEST_ERR {@link org.w3c.dom.DOMException}.
277      */

278     public Node removeChild(Node oldChild) throws DOMException {
279         throw createDOMException(DOMException.HIERARCHY_REQUEST_ERR,
280                                  "children.not.allowed",
281                                  new Object[] {(int) getNodeType(),
282                                                 getNodeName() });
283     }
284
285     /**
286      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#appendChild(Node)}.
287      * Throws a HIERARCHY_REQUEST_ERR {@link org.w3c.dom.DOMException}.
288      */

289     public Node appendChild(Node newChild) throws DOMException {
290         throw createDOMException(DOMException.HIERARCHY_REQUEST_ERR,
291                                  "children.not.allowed",
292                                  new Object[] {(int) getNodeType(),
293                                                 getNodeName() });
294     }
295
296     /**
297      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#hasChildNodes()}.
298      * @return false.
299      */

300     public boolean hasChildNodes() {
301         return false;
302     }
303
304     /**
305      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#cloneNode(boolean)}.
306      */

307     public Node cloneNode(boolean deep) {
308         Node n = deep ? deepCopyInto(newNode()) : copyInto(newNode());
309         fireUserDataHandlers(UserDataHandler.NODE_CLONED, this, n);
310         return n;
311     }
312
313     /**
314      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#normalize()}.
315      * Do nothing.
316      */

317     public void normalize() {
318     }
319
320     /**
321      * <b>DOM</b>: Implements {@link
322      * org.w3c.dom.Node#isSupported(String,String)}.
323      */

324     public boolean isSupported(String feature, String version) {
325         return getCurrentDocument().getImplementation().hasFeature(feature,
326                                                                    version);
327     }
328
329     /**
330      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#getPrefix()}.
331      */

332     public String getPrefix() {
333         return (getNamespaceURI() == null)
334             ? null
335             : DOMUtilities.getPrefix(getNodeName());
336     }
337
338     /**
339      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#setPrefix(String)}.
340      */

341     public void setPrefix(String prefix) throws DOMException {
342         if (isReadonly()) {
343             throw createDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
344                                      "readonly.node",
345                                      new Object[] {(int) getNodeType(),
346                                                     getNodeName() });
347         }
348         String uri = getNamespaceURI();
349         if (uri == null) {
350             throw createDOMException(DOMException.NAMESPACE_ERR,
351                                      "namespace",
352                                      new Object[] {(int) getNodeType(),
353                                                     getNodeName() });
354         }
355
356         String name = getLocalName();
357         if (prefix == null) {
358             // prefix null is explicitly allowed by org.w3c.dom.Node#setPrefix(String)
359             setNodeName(name);
360             return;
361         }
362
363         // prefix is guaranteed to be non-null here...
364         if (!prefix.equals("") && !DOMUtilities.isValidName(prefix)) {
365             throw createDOMException(DOMException.INVALID_CHARACTER_ERR,
366                                      "prefix",
367                                      new Object[] {(int) getNodeType(),
368                                                     getNodeName(),
369                                                     prefix });
370         }
371         if (!DOMUtilities.isValidPrefix(prefix)) {
372             throw createDOMException(DOMException.NAMESPACE_ERR,
373                                      "prefix",
374                                      new Object[] {(int) getNodeType(),
375                                                     getNodeName(),
376                                                     prefix });
377         }
378         if ((prefix.equals("xml") &&
379              !XMLSupport.XML_NAMESPACE_URI.equals(uri)) ||
380             (prefix.equals("xmlns") &&
381              !XMLSupport.XMLNS_NAMESPACE_URI.equals(uri))) {
382             throw createDOMException(DOMException.NAMESPACE_ERR,
383                                      "namespace.uri",
384                                      new Object[] {(int) getNodeType(),
385                                                     getNodeName(),
386                                                     uri });
387         }
388         setNodeName(prefix + ':' + name);
389     }
390
391     /**
392      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#getLocalName()}.
393      */

394     public String getLocalName() {
395         return (getNamespaceURI() == null)
396             ? null
397             : DOMUtilities.getLocalName(getNodeName());
398     }
399
400     /**
401      * Creates an exception with the appropriate error message.
402      */

403     public DOMException createDOMException(short    type,
404                                            String   key,
405                                            Object[] args) {
406         try {
407             return new DOMException
408                 (type, getCurrentDocument().formatMessage(key, args));
409         } catch (Exception e) {
410             return new DOMException(type, key);
411         }
412     }
413
414     /**
415      * Returns the xml:base attribute value of the given element,
416      * resolving any dependency on parent bases if needed.
417      */

418     protected String getCascadedXMLBase(Node node) {
419         String base = null;
420         Node n = node.getParentNode();
421         while (n != null) {
422             if (n.getNodeType() == Node.ELEMENT_NODE) {
423                 base = getCascadedXMLBase(n);
424                 break;
425             }
426             n = n.getParentNode();
427         }
428         if (base == null) {
429             AbstractDocument doc;
430             if (node.getNodeType() == Node.DOCUMENT_NODE) {
431                 doc = (AbstractDocument) node;
432             } else {
433                 doc = (AbstractDocument) node.getOwnerDocument();
434             }
435             base = doc.getDocumentURI();
436         }
437         while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
438             node = node.getParentNode();
439         }
440         if (node == null) {
441             return base;
442         }
443         Element e = (Element) node;
444         Attr attr = e.getAttributeNodeNS(XMLConstants.XML_NAMESPACE_URI,
445                                          XMLConstants.XML_BASE_ATTRIBUTE);
446         if (attr != null) {
447             if (base == null) {
448                 base = attr.getNodeValue();
449             } else {
450                 base = new ParsedURL(base, attr.getNodeValue()).toString();
451             }
452         }
453         return base;
454     }
455
456     /**
457      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#getBaseURI()}.
458      */

459     public String getBaseURI() {
460         return getCascadedXMLBase(this);
461     }
462
463     public static String getBaseURI(Node n) {
464         return n.getBaseURI();
465     }
466
467     // DocumentPosition constants from DOM Level 3 Core org.w3c.dom.Node
468     // interface.
469
470     public static final short DOCUMENT_POSITION_DISCONNECTED = 0x01;
471     public static final short DOCUMENT_POSITION_PRECEDING = 0x02;
472     public static final short DOCUMENT_POSITION_FOLLOWING = 0x04;
473     public static final short DOCUMENT_POSITION_CONTAINS = 0x08;
474     public static final short DOCUMENT_POSITION_CONTAINED_BY = 0x10;
475     public static final short DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 0x20;
476
477     /**
478      * <b>DOM</b>: Implements
479      * {@link org.w3c.dom.Node#compareDocumentPosition(Node)}.
480      * XXX Doesn't handle notation or entity nodes.
481      */

482     public short compareDocumentPosition(Node other) throws DOMException {
483         if (this == other) {
484             return 0;
485         }
486         ArrayList a1 = new ArrayList(10);
487         ArrayList a2 = new ArrayList(10);
488         int c1 = 0;
489         int c2 = 0;
490         Node n;
491         if (getNodeType() == ATTRIBUTE_NODE) {
492             a1.add(this);
493             c1++;
494             n = ((Attr) this).getOwnerElement();
495             if (other.getNodeType() == ATTRIBUTE_NODE) {
496                 Attr otherAttr = (Attr) other;
497                 if (n == otherAttr.getOwnerElement()) {
498                     if (hashCode() < other.hashCode()) {
499                         return DOCUMENT_POSITION_PRECEDING
500                             | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
501                     } else {
502                         return DOCUMENT_POSITION_FOLLOWING
503                             | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
504                     }
505                 }
506             }
507         } else {
508             n = this;
509         }
510         while (n != null) {
511             if (n == other) {
512                 return DOCUMENT_POSITION_CONTAINED_BY
513                     | DOCUMENT_POSITION_FOLLOWING;
514             }
515             a1.add(n);
516             c1++;
517             n = n.getParentNode();
518         }
519         if (other.getNodeType() == ATTRIBUTE_NODE) {
520             a2.add(other);
521             c2++;
522             n = ((Attr) other).getOwnerElement();
523         } else {
524             n = other;
525         }
526         while (n != null) {
527             if (n == this) {
528                 return DOCUMENT_POSITION_CONTAINS
529                     | DOCUMENT_POSITION_PRECEDING;
530             }
531             a2.add(n);
532             c2++;
533             n = n.getParentNode();
534         }
535         int i1 = c1 - 1;
536         int i2 = c2 - 1;
537         if (a1.get(i1) != a2.get(i2)) {
538             if (hashCode() < other.hashCode()) {
539                 return DOCUMENT_POSITION_DISCONNECTED
540                     | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
541                     | DOCUMENT_POSITION_PRECEDING;
542             } else {
543                 return DOCUMENT_POSITION_DISCONNECTED
544                     | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
545                     | DOCUMENT_POSITION_FOLLOWING;
546             }
547         }
548         Object n1 = a1.get(i1);
549         Object n2 = a2.get(i2);
550         while (n1 == n2) {
551             n = (Node) n1;
552             n1 = a1.get(--i1);
553             n2 = a2.get(--i2);
554         }
555         for (n = n.getFirstChild(); n != null; n = n.getNextSibling()) {
556             if (n == n1) {
557                 return DOCUMENT_POSITION_PRECEDING;
558             } else if (n == n2) {
559                 return DOCUMENT_POSITION_FOLLOWING;
560             }
561         }
562         return DOCUMENT_POSITION_DISCONNECTED;
563     }
564
565     /**
566      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#getTextContent()}.
567      */

568     public String getTextContent() {
569         return null;
570     }
571
572     /**
573      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#setTextContent(String)}.
574      */

575     public void setTextContent(String s) throws DOMException {
576         if (isReadonly()) {
577             throw createDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
578                                      "readonly.node",
579                                      new Object[] {(int) getNodeType(),
580                                                     getNodeName() });
581         }
582         if (getNodeType() != DOCUMENT_TYPE_NODE) {
583             while (getFirstChild() != null) {
584                 removeChild(getFirstChild());
585             }
586             appendChild(getOwnerDocument().createTextNode(s));
587         }
588     }
589
590     /**
591      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#isSameNode(Node)}.
592      */

593     public boolean isSameNode(Node other) {
594         return this == other;
595     }
596
597     /**
598      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#lookupPrefix(String)}.
599      */

600     public String lookupPrefix(String namespaceURI) {
601         if (namespaceURI == null || namespaceURI.length() == 0) {
602             return null;
603         }
604         int type = getNodeType();
605         switch (type) {
606             case Node.ELEMENT_NODE:
607                 return lookupNamespacePrefix(namespaceURI, (Element) this);
608             case Node.DOCUMENT_NODE:
609                 AbstractNode de
610                     = (AbstractNode) ((Document) this).getDocumentElement();
611                 return de.lookupPrefix(namespaceURI);
612             case Node.ENTITY_NODE :
613             case Node.NOTATION_NODE:
614             case Node.DOCUMENT_FRAGMENT_NODE:
615             case Node.DOCUMENT_TYPE_NODE:
616                 return null;
617             case Node.ATTRIBUTE_NODE:
618                 AbstractNode ownerElement
619                     = (AbstractNode) ((Attr) this).getOwnerElement();
620                 if (ownerElement != null) {
621                     return ownerElement.lookupPrefix(namespaceURI);
622                 }
623                 return null;
624             default:
625                 for (Node n = this.getParentNode();
626                         n != null;
627                         n = n.getParentNode()) {
628                     if (n.getNodeType() == ELEMENT_NODE) {
629                         return n.lookupPrefix(namespaceURI);
630                     }
631                 }
632                 return null;
633         }
634     }
635
636     /**
637      * Helper function for {@link #lookupPrefix}.
638      */

639     protected String lookupNamespacePrefix(String namespaceURI,
640                                            Element originalElement) {
641         String ns = originalElement.getNamespaceURI();
642         String prefix = originalElement.getPrefix();
643         if (ns != null
644                 && ns.equals(namespaceURI)
645                 && prefix != null) {
646             String pns =
647                 originalElement.lookupNamespaceURI(prefix);
648             if (pns != null && pns.equals(namespaceURI)) {
649                 return prefix;
650             }
651         }
652         NamedNodeMap nnm = originalElement.getAttributes();
653         if (nnm != null) {
654             for (int i = 0; i < nnm.getLength(); i++) {
655                 Node attr = nnm.item(i);
656                 if (XMLConstants.XMLNS_PREFIX.equals(attr.getPrefix())
657                         && attr.getNodeValue().equals(namespaceURI)) {
658                     String ln = attr.getLocalName();
659                     AbstractNode oe = (AbstractNode) originalElement;
660                     String pns = oe.lookupNamespaceURI(ln);
661                     if (pns != null && pns.equals(namespaceURI)) {
662                         return ln;
663                     }
664                 }
665             }
666         }
667         for (Node n = getParentNode(); n != null; n = n.getParentNode()) {
668             if (n.getNodeType() == ELEMENT_NODE) {
669                 return ((AbstractNode) n).lookupNamespacePrefix
670                     (namespaceURI, originalElement);
671             }
672         }
673         return null;
674     }
675
676     /**
677      * <b>DOM</b>: Implements
678      * {@link org.w3c.dom.Node#isDefaultNamespace(String)}.
679      */

680     public boolean isDefaultNamespace(String namespaceURI) {
681         switch (getNodeType()) {
682             case DOCUMENT_NODE:
683                 AbstractNode de
684                     = (AbstractNode) ((Document) this).getDocumentElement();
685                 return de.isDefaultNamespace(namespaceURI);
686             case ENTITY_NODE:
687             case NOTATION_NODE:
688             case DOCUMENT_TYPE_NODE:
689             case DOCUMENT_FRAGMENT_NODE:
690                 return false;
691             case ATTRIBUTE_NODE:
692                 AbstractNode owner
693                     = (AbstractNode) ((Attr) this).getOwnerElement();
694                 if (owner != null) {
695                     return owner.isDefaultNamespace(namespaceURI);
696                 }
697                 return false;
698             case ELEMENT_NODE:
699                 if (getPrefix() == null) {
700                     String ns = getNamespaceURI();
701                     return ns == null && namespaceURI == null
702                         || ns != null && ns.equals(namespaceURI);
703                 }
704                 NamedNodeMap nnm = getAttributes();
705                 if (nnm != null) {
706                     for (int i = 0; i < nnm.getLength(); i++) {
707                         Node attr = nnm.item(i);
708                         if (XMLConstants.XMLNS_PREFIX
709                                 .equals(attr.getLocalName())) {
710                             return attr.getNodeValue().equals(namespaceURI);
711                         }
712                     }
713                 }
714                 // fall through
715             default:
716                 for (Node n = this; n != null; n = n.getParentNode()) {
717                     if (n.getNodeType() == ELEMENT_NODE) {
718                         AbstractNode an = (AbstractNode) n;
719                         return an.isDefaultNamespace(namespaceURI);
720                     }
721                 }
722                 return false;
723         }
724     }
725
726     /**
727      * <b>DOM</b>: Implements
728      * {@link org.w3c.dom.Node#lookupNamespaceURI(String)}.
729      */

730     public String lookupNamespaceURI(String prefix) {
731         switch (getNodeType()) {
732             case DOCUMENT_NODE:
733                 AbstractNode de =
734                     (AbstractNode) ((Document) this).getDocumentElement();
735                 return de.lookupNamespaceURI(prefix);
736             case ENTITY_NODE:
737             case NOTATION_NODE:
738             case DOCUMENT_TYPE_NODE:
739             case DOCUMENT_FRAGMENT_NODE:
740                 return null;
741             case ATTRIBUTE_NODE:
742                 AbstractNode owner
743                     = (AbstractNode) ((Attr) this).getOwnerElement();
744                 if (owner != null) {
745                     return owner.lookupNamespaceURI(prefix);
746                 }
747                 return null;
748             case ELEMENT_NODE:
749                 /*String ns = getNamespaceURI();
750                 if (ns != null && compareStrings(getPrefix(), prefix)) {
751                     return getNamespaceURI();
752                 } */

753                 NamedNodeMap nnm = getAttributes();
754                 if (nnm != null) {
755                     for (int i = 0; i < nnm.getLength(); i++) {
756                         Node attr = nnm.item(i);
757                         String attrPrefix = attr.getPrefix();
758                         String localName = attr.getLocalName();
759                         if (localName == null) {
760                             localName = attr.getNodeName();
761                         }
762                         if (XMLConstants.XMLNS_PREFIX.equals(attrPrefix)
763                                 && compareStrings(localName, prefix)
764                                 || XMLConstants.XMLNS_PREFIX.equals(localName)
765                                 && prefix == null) {
766                             String value = attr.getNodeValue();
767                             if (value.length() > 0) {
768                                 return value;
769                             }
770                             return null;
771                         }
772                     }
773                 }
774                 // fall through
775             default:
776                 for (Node n = this.getParentNode(); n != null; n = n.getParentNode()) {
777                     if (n.getNodeType() == ELEMENT_NODE) {
778                         AbstractNode an = (AbstractNode) n;
779                         return an.lookupNamespaceURI(prefix);
780                     }
781                 }
782                 return null;
783         }
784     }
785
786     /**
787      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#isEqualNode(Node)}.
788      */

789     public boolean isEqualNode(Node other) {
790         if (other == null) {
791             return false;
792         }
793         int nt = other.getNodeType();
794         if (nt != getNodeType()
795                 || !compareStrings(getNodeName(), other.getNodeName())
796                 || !compareStrings(getLocalName(), other.getLocalName())
797                 || !compareStrings(getPrefix(), other.getPrefix())
798                 || !compareStrings(getNodeValue(), other.getNodeValue())
799                 || !compareStrings(getNodeValue(), other.getNodeValue())
800                 || !compareNamedNodeMaps(getAttributes(),
801                                          other.getAttributes())) {
802             return false;
803         }
804         if (nt == Node.DOCUMENT_TYPE_NODE) {
805             DocumentType dt1 = (DocumentType) this;
806             DocumentType dt2 = (DocumentType) other;
807             if (!compareStrings(dt1.getPublicId(), dt2.getPublicId())
808                     || !compareStrings(dt1.getSystemId(), dt2.getSystemId())
809                     || !compareStrings(dt1.getInternalSubset(),
810                                        dt2.getInternalSubset())
811                     || !compareNamedNodeMaps(dt1.getEntities(),
812                                              dt2.getEntities())
813                     || !compareNamedNodeMaps(dt1.getNotations(),
814                                              dt2.getNotations())) {
815                 return false;
816             }
817         }
818         Node n = getFirstChild();
819         Node m = other.getFirstChild();
820         if (n != null && m != null) {
821             if (!n.isEqualNode(m)) {
822                 return false;
823             }
824         }
825         return n == m;
826     }
827
828     /**
829      * Compare two strings for equality.
830      */

831     protected boolean compareStrings(String s1, String s2) {
832         return s1 != null && s1.equals(s2) || s1 == null && s2 == null;
833     }
834
835     /**
836      * Compare two NamedNodeMaps for equality.
837      */

838     protected boolean compareNamedNodeMaps(NamedNodeMap nnm1,
839                                            NamedNodeMap nnm2) {
840         if (nnm1 == null && nnm2 != null
841                 || nnm1 != null && nnm2 == null) {
842             return false;
843         }
844         if (nnm1 != null) {
845             int len = nnm1.getLength();
846             if (len != nnm2.getLength()) {
847                 return false;
848             }
849             for (int i = 0; i < len; i++) {
850                 Node n1 = nnm1.item(i);
851                 String n1ln = n1.getLocalName();
852                 Node n2;
853                 if (n1ln != null) {
854                     n2 = nnm2.getNamedItemNS(n1.getNamespaceURI(), n1ln);
855                 } else {
856                     n2 = nnm2.getNamedItem(n1.getNodeName());
857                 }
858                 if (!n1.isEqualNode(n2)) {
859                     return false;
860                 }
861             }
862         }
863         return true;
864     }
865
866     /**
867      * <b>DOM</b>: Implements
868      * {@link org.w3c.dom.Node#getFeature(String,String)}.
869      */

870     public Object getFeature(String feature, String version) {
871         return null;
872     }
873
874     /**
875      * <b>DOM</b>: Implements {@link org.w3c.dom.Node#getUserData(String)}.
876      */

877     public Object getUserData(String key) {
878         if (userData == null) {
879             return null;
880         }
881         return userData.get(key);
882     }
883
884     /**
885      * <b>DOM</b>: Implements
886      * {@link org.w3c.dom.Node#setUserData(String,Object,UserDataHandler)}.
887      */

888     public Object setUserData(String key, Object data, UserDataHandler handler) {
889         if (userData == null) {
890             userData = new HashMap();
891             userDataHandlers = new HashMap();
892         }
893         if (data == null) {
894             userData.remove(key);
895             return userDataHandlers.remove(key);
896         }
897         userDataHandlers.put(key, handler);
898         return userData.put(key, data);
899     }
900
901     /**
902      * Fire any UserDataHandlers on the given oldNode.
903      */

904     protected void fireUserDataHandlers(short type,
905                                         Node oldNode,
906                                         Node newNode) {
907         AbstractNode an = (AbstractNode) oldNode;
908         if (an.userData != null) {
909             for (Object o : an.userData.entrySet()) {
910                 Map.Entry e = (Map.Entry) o;
911                 UserDataHandler h
912                         = (UserDataHandler) an.userDataHandlers.get(e.getKey());
913                 if (h != null) {
914                     h.handle(type,
915                             (String) e.getKey(),
916                             e.getValue(),
917                             oldNode,
918                             newNode);
919                 }
920             }
921         }
922     }
923
924     // EventTarget ////////////////////////////////////////////////////////////
925
926     /**
927      * <b>DOM</b>: Implements
928      * {@link
929      * org.w3c.dom.events.EventTarget#addEventListener(String,EventListener,boolean)}.
930      */

931     public void addEventListener(String type,
932                                  EventListener listener,
933                                  boolean useCapture) {
934         if (eventSupport == null) {
935             initializeEventSupport();
936         }
937         eventSupport.addEventListener(type, listener, useCapture);
938     }
939
940     /**
941      * <b>DOM</b>: Implements
942      * {@link
943      * NodeEventTarget#addEventListenerNS(String,String,EventListener,boolean,Object)}.
944      */

945     public void addEventListenerNS(String namespaceURI,
946                                    String type,
947                                    EventListener listener,
948                                    boolean useCapture,
949                                    Object evtGroup) {
950         if (eventSupport == null) {
951             initializeEventSupport();
952         }
953         if (namespaceURI != null && namespaceURI.length() == 0) {
954             namespaceURI = null;
955         }
956         eventSupport.addEventListenerNS(namespaceURI,
957                                         type,
958                                         listener,
959                                         useCapture,
960                                         evtGroup);
961     }
962
963     /**
964      * <b>DOM</b>: Implements
965      * {@link
966      * org.w3c.dom.events.EventTarget#removeEventListener(String,EventListener,boolean)}.
967      */

968     public void removeEventListener(String type,
969                                     EventListener listener,
970                                     boolean useCapture) {
971         if (eventSupport != null) {
972             eventSupport.removeEventListener(type, listener, useCapture);
973         }
974     }
975
976     /**
977      * <b>DOM</b>: Implements
978      * {@link
979      * NodeEventTarget#removeEventListenerNS(String,String,EventListener,boolean)}.
980      */

981     public void removeEventListenerNS(String namespaceURI,
982                                       String type,
983                                       EventListener listener,
984                                       boolean useCapture) {
985         if (eventSupport != null) {
986             if (namespaceURI != null && namespaceURI.length() == 0) {
987                 namespaceURI = null;
988             }
989             eventSupport.removeEventListenerNS(namespaceURI,
990                                                type,
991                                                listener,
992                                                useCapture);
993         }
994     }
995
996     /**
997      * Implements {@link
998      * org.apache.batik.dom.events.NodeEventTarget#getParentNodeEventTarget()}.
999      */

1000     public NodeEventTarget getParentNodeEventTarget() {
1001         return (NodeEventTarget) getXblParentNode();
1002     }
1003
1004     /**
1005      * <b>DOM</b>: Implements
1006      * {@link org.w3c.dom.events.EventTarget#dispatchEvent(Event)}.
1007      */

1008     public boolean dispatchEvent(Event evt) throws EventException {
1009         if (eventSupport == null) {
1010             initializeEventSupport();
1011         }
1012         return eventSupport.dispatchEvent(this, evt);
1013     }
1014
1015     /**
1016      * <b>DOM</b>: Implements
1017      * <code>EventTarget#willTriggerNS(String,String)</code> from an old draft
1018      * of DOM Level 3 Events.
1019      */

1020     public boolean willTriggerNS(String namespaceURI, String type) {
1021         return true;
1022     }
1023
1024     /**
1025      * <b>DOM</b>: Implements
1026      * <code>EventTarget.hasEventListenerNS(String,String)</code> from an old
1027      * draft of DOM Level 3 Events.
1028      */

1029     public boolean hasEventListenerNS(String namespaceURI, String type) {
1030         if (eventSupport == null) {
1031             return false;
1032         }
1033         if (namespaceURI != null && namespaceURI.length() == 0) {
1034             namespaceURI = null;
1035         }
1036         return eventSupport.hasEventListenerNS(namespaceURI, type);
1037     }
1038
1039     /**
1040      * Returns the event support instance for this node, or null if any.
1041      */

1042     public EventSupport getEventSupport() {
1043         return eventSupport;
1044     }
1045
1046     /**
1047      * Initializes the event support instance for this node if it has not
1048      * been already, and returns it.
1049      */

1050     public EventSupport initializeEventSupport() {
1051         if (eventSupport == null) {
1052             AbstractDocument doc = getCurrentDocument();
1053             AbstractDOMImplementation di
1054                 = (AbstractDOMImplementation) doc.getImplementation();
1055             eventSupport = di.createEventSupport(this);
1056             doc.setEventsEnabled(true);
1057         }
1058         return eventSupport;
1059     }
1060
1061     /**
1062      * Recursively fires a DOMNodeInsertedIntoDocument event.
1063      */

1064     public void fireDOMNodeInsertedIntoDocumentEvent() {
1065         AbstractDocument doc = getCurrentDocument();
1066         if (doc.getEventsEnabled()) {
1067             DOMMutationEvent ev =
1068                 (DOMMutationEvent)doc.createEvent("MutationEvents");
1069             ev.initMutationEventNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
1070                                    "DOMNodeInsertedIntoDocument",
1071                                    true,   // canBubbleArg
1072                                    false,  // cancelableArg
1073                                    null,   // relatedNodeArg
1074                                    null,   // prevValueArg
1075                                    null,   // newValueArg
1076                                    null,   // attrNameArg
1077                                    MutationEvent.ADDITION);
1078             dispatchEvent(ev);
1079         }
1080     }
1081
1082     /**
1083      * Recursively fires a DOMNodeRemovedFromDocument event.
1084      */

1085     public void fireDOMNodeRemovedFromDocumentEvent() {
1086         AbstractDocument doc = getCurrentDocument();
1087         if (doc.getEventsEnabled()) {
1088             DOMMutationEvent ev
1089                 = (DOMMutationEvent) doc.createEvent("MutationEvents");
1090             ev.initMutationEventNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
1091                                    "DOMNodeRemovedFromDocument",
1092                                    true,   // canBubbleArg
1093                                    false,  // cancelableArg
1094                                    null,   // relatedNodeArg
1095                                    null,   // prevValueArg
1096                                    null,   // newValueArg
1097                                    null,   // attrNameArg
1098                                    MutationEvent.REMOVAL);
1099             dispatchEvent(ev);
1100         }
1101     }
1102
1103     /**
1104      * Fires a DOMCharacterDataModified event.
1105      */

1106     protected void fireDOMCharacterDataModifiedEvent(String oldv,
1107                                                      String newv) {
1108         AbstractDocument doc = getCurrentDocument();
1109         if (doc.getEventsEnabled()) {
1110             DOMMutationEvent ev
1111                 = (DOMMutationEvent) doc.createEvent("MutationEvents");
1112             ev.initMutationEventNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
1113                                    "DOMCharacterDataModified",
1114                                    true,  // canBubbleArg
1115                                    false// cancelableArg
1116                                    null,  // relatedNodeArg
1117                                    oldv,  // prevValueArg
1118                                    newv,  // newValueArg
1119                                    null,  // attrNameArg
1120                                    MutationEvent.MODIFICATION);
1121             dispatchEvent(ev);
1122         }
1123     }
1124
1125     /**
1126      * Returns the current document.
1127      */

1128     protected AbstractDocument getCurrentDocument() {
1129         return ownerDocument;
1130     }
1131
1132     /**
1133      * Returns a new uninitialized instance of this object's class.
1134      */

1135     protected abstract Node newNode();
1136
1137     /**
1138      * Exports this node to the given document.
1139      */

1140     protected Node export(Node n, AbstractDocument d) {
1141         AbstractNode p = (AbstractNode)n;
1142         p.ownerDocument = d;
1143         p.setReadonly(false);
1144         return n;
1145     }
1146
1147     /**
1148      * Deeply exports this node to the given document.
1149      */

1150     protected Node deepExport(Node n, AbstractDocument d) {
1151         AbstractNode p = (AbstractNode)n;
1152         p.ownerDocument = d;
1153         p.setReadonly(false);
1154         return n;
1155     }
1156
1157     /**
1158      * Copy the fields of the current node into the given node.
1159      * @param n a node of the type of this.
1160      */

1161     protected Node copyInto(Node n) {
1162         AbstractNode an = (AbstractNode)n;
1163         an.ownerDocument = ownerDocument;
1164         return n;
1165     }
1166
1167     /**
1168      * Deeply copy the fields of the current node into the given node.
1169      * @param n a node of the type of this.
1170      */

1171     protected Node deepCopyInto(Node n) {
1172         AbstractNode an = (AbstractNode)n;
1173         an.ownerDocument = ownerDocument;
1174         return n;
1175     }
1176
1177     /**
1178      * Checks the validity of a node to be inserted.
1179      */

1180     protected void checkChildType(Node n, boolean replace) {
1181         throw createDOMException(DOMException.HIERARCHY_REQUEST_ERR,
1182                                  "children.not.allowed",
1183                                  new Object[] {(int) getNodeType(),
1184                                                 getNodeName() });
1185     }
1186
1187     // NodeXBL //////////////////////////////////////////////////////////////
1188
1189     /**
1190      * Get the parent of this node in the fully flattened tree.
1191      */

1192     public Node getXblParentNode() {
1193         return ownerDocument.getXBLManager().getXblParentNode(this);
1194     }
1195
1196     /**
1197      * Get the list of child nodes of this node in the fully flattened tree.
1198      */

1199     public NodeList getXblChildNodes() {
1200         return ownerDocument.getXBLManager().getXblChildNodes(this);
1201     }
1202
1203     /**
1204      * Get the list of child nodes of this node in the fully flattened tree
1205      * that are within the same shadow scope.
1206      */

1207     public NodeList getXblScopedChildNodes() {
1208         return ownerDocument.getXBLManager().getXblScopedChildNodes(this);
1209     }
1210
1211     /**
1212      * Get the first child node of this node in the fully flattened tree.
1213      */

1214     public Node getXblFirstChild() {
1215         return ownerDocument.getXBLManager().getXblFirstChild(this);
1216     }
1217
1218     /**
1219      * Get the last child node of this node in the fully flattened tree.
1220      */

1221     public Node getXblLastChild() {
1222         return ownerDocument.getXBLManager().getXblLastChild(this);
1223     }
1224
1225     /**
1226      * Get the node which directly precedes the current node in the
1227      * xblParentNode's xblChildNodes list.
1228      */

1229     public Node getXblPreviousSibling() {
1230         return ownerDocument.getXBLManager().getXblPreviousSibling(this);
1231     }
1232
1233     /**
1234      * Get the node which directly follows the current node in the
1235      * xblParentNode's xblChildNodes list.
1236      */

1237     public Node getXblNextSibling() {
1238         return ownerDocument.getXBLManager().getXblNextSibling(this);
1239     }
1240
1241     /**
1242      * Get the first element child of this node in the fully flattened tree.
1243      */

1244     public Element getXblFirstElementChild() {
1245         return ownerDocument.getXBLManager().getXblFirstElementChild(this);
1246     }
1247
1248     /**
1249      * Get the last element child of this node in the fully flattened tree.
1250      */

1251     public Element getXblLastElementChild() {
1252         return ownerDocument.getXBLManager().getXblLastElementChild(this);
1253     }
1254
1255     /**
1256      * Get the first element that precedes the current node in the
1257      * xblParentNode's xblChildNodes list.
1258      */

1259     public Element getXblPreviousElementSibling() {
1260         return ownerDocument.getXBLManager().getXblPreviousElementSibling(this);
1261     }
1262
1263     /**
1264      * Get the first element that follows the current node in the
1265      * xblParentNode's xblChildNodes list.
1266      */

1267     public Element getXblNextElementSibling() {
1268         return ownerDocument.getXBLManager().getXblNextElementSibling(this);
1269     }
1270
1271     /**
1272      * Get the bound element whose shadow tree this current node resides in.
1273      */

1274     public Element getXblBoundElement() {
1275         return ownerDocument.getXBLManager().getXblBoundElement(this);
1276     }
1277
1278     /**
1279      * Get the shadow tree of this node.
1280      */

1281     public Element getXblShadowTree() {
1282         return ownerDocument.getXBLManager().getXblShadowTree(this);
1283     }
1284
1285     /**
1286      * Get the xbl:definition elements currently binding this element.
1287      */

1288     public NodeList getXblDefinitions() {
1289         return ownerDocument.getXBLManager().getXblDefinitions(this);
1290     }
1291
1292     // XBLManagerData ////////////////////////////////////////////////////////
1293
1294     /**
1295      * Returns the XBL manager associated data for this node.
1296      */

1297     public Object getManagerData() {
1298         return managerData;
1299     }
1300
1301     /**
1302      * Sets the XBL manager associated data for this node.
1303      */

1304     public void setManagerData(Object data) {
1305         managerData = data;
1306     }
1307 }
1308