001    /*
002     * File:    $HeadURL: https://jvoicexml.svn.sourceforge.net/svnroot/jvoicexml/core/trunk/org.jvoicexml.xml/src/org/jvoicexml/xml/AbstractXmlNode.java $
003     * Version: $LastChangedRevision: 2914 $
004     * Date:    $Date $
005     * Author:  $LastChangedBy: schnelle $
006     *
007     * JVoiceXML - A free VoiceXML implementation.
008     *
009     * Copyright (C) 2005-2012 JVoiceXML group - http://jvoicexml.sourceforge.net
010     *
011     * This library is free software; you can redistribute it and/or
012     * modify it under the terms of the GNU Library General Public
013     * License as published by the Free Software Foundation; either
014     * version 2 of the License, or (at your option) any later version.
015     *
016     * This library is distributed in the hope that it will be useful,
017     * but WITHOUT ANY WARRANTY; without even the implied warranty of
018     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     * Library General Public License for more details.
020     *
021     * You should have received a copy of the GNU Library General Public
022     * License along with this library; if not, write to the Free Software
023     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
024     *
025     */
026    
027    package org.jvoicexml.xml;
028    
029    import java.io.ByteArrayOutputStream;
030    import java.io.UnsupportedEncodingException;
031    import java.lang.reflect.Constructor;
032    import java.lang.reflect.InvocationTargetException;
033    import java.util.ArrayList;
034    import java.util.Collection;
035    
036    import javax.xml.transform.OutputKeys;
037    import javax.xml.transform.Result;
038    import javax.xml.transform.Source;
039    import javax.xml.transform.Transformer;
040    import javax.xml.transform.TransformerException;
041    import javax.xml.transform.TransformerFactory;
042    import javax.xml.transform.dom.DOMSource;
043    import javax.xml.transform.stream.StreamResult;
044    
045    import org.w3c.dom.Document;
046    import org.w3c.dom.NamedNodeMap;
047    import org.w3c.dom.Node;
048    import org.w3c.dom.NodeList;
049    import org.w3c.dom.UserDataHandler;
050    
051    /**
052     * Abstract base class for all nodes in an XML document.
053     *
054     * @see org.jvoicexml.xml.XmlDocument
055     *
056     * @author Steve Doyle
057     * @author Dirk Schnelle-Walka
058     * @version $Revision: 2914 $
059     */
060    public abstract class AbstractXmlNode
061            implements XmlNode {
062        /** The encapsulated node. */
063        private final Node node;
064    
065        /** The node factory. */
066        private final XmlNodeFactory<? extends XmlNode> factory;
067    
068        /**
069         * Constructs a new XmlNode.
070         *
071         * @param n
072         *        The encapsulated node.
073         * @param nodeFactory
074         *        Node factory to create node lists.
075         */
076        protected AbstractXmlNode(final Node n,
077                                  final XmlNodeFactory<? extends XmlNode>
078                                  nodeFactory) {
079            node = n;
080            factory = nodeFactory;
081        }
082    
083        /**
084         * A {@link NodeList} that contains all children of this node.
085         *
086         * @return NodeList
087         */
088        @SuppressWarnings({ "unchecked", "rawtypes" })
089        public final NodeList getChildNodes() {
090            return new XmlNodeList(factory, node.getChildNodes());
091        }
092    
093        /**
094         * {@inheritDoc}
095         */
096        public final Node getNode() {
097            return node;
098        }
099    
100        /**
101         * Retrieves the factory to create node lists.
102         * @return Factory to create node lists.
103         *
104         * @since 0.5
105         */
106        public final XmlNodeFactory<? extends XmlNode> getNodeFactory() {
107            return factory;
108        }
109    
110        /**
111         * {@inheritDoc}
112         */
113        public final String getAttribute(final String attribute) {
114            final NamedNodeMap attributes = node.getAttributes();
115    
116            if (attributes == null) {
117                return null;
118            }
119    
120            final Node item = attributes.getNamedItem(attribute);
121            if (item == null) {
122                return null;
123            }
124    
125            return item.getNodeValue();
126        }
127    
128        /**
129         * {@inheritDoc}
130         */
131        public final void setAttribute(final String name, final String value) {
132            final NamedNodeMap attributes = node.getAttributes();
133    
134            if (attributes == null) {
135                return;
136            }
137    
138            if (value == null) {
139                // Remove the attribute if no value was specified.
140                if (attributes.getNamedItem(name) != null) {
141                    attributes.removeNamedItem(name);
142                }
143            } else {
144                // Remove a possibly existing attribute
145                if (attributes.getNamedItem(name) != null) {
146                    attributes.removeNamedItem(name);
147                }
148                // Create a new attribute.
149                final Document owner = node.getOwnerDocument();
150                final Node item = owner.createAttribute(name);
151                item.setNodeValue(value);
152                attributes.setNamedItem(item);
153            }
154        }
155    
156        /**
157         * Adds the node <code>newChild</code> to the end of the list of children
158         * of this node.
159         *
160         * @param newChild
161         *        The node to add.If it is a <code>DocumentFragment</code> object,
162         *        the entire contents of the document fragment are moved into the
163         *        child list of this node
164         * @return The node added.
165         */
166        public final Node appendChild(final Node newChild) {
167            return node.appendChild(getRawNode(newChild));
168        }
169    
170        /**
171         * Appends a deep clone of the given node to the cildren of this node.
172         * @param origin the node to clone
173         * @since 0.7.5
174         */
175        public AbstractXmlNode appendDeepClone(final AbstractXmlNode origin) {
176            final String tag = origin.getNodeName();
177            final AbstractXmlNode clone = (AbstractXmlNode)addChild(tag);
178            final NamedNodeMap attributes = origin.getAttributes();
179            for (int i = 0; i < attributes.getLength(); i++) {
180                final Node attribute = attributes.item(i);
181    
182                String name = attribute.getNodeName();
183                final String value = attribute.getNodeValue();
184                clone.setAttribute(name, value);
185            }
186            final Collection<AbstractXmlNode> children = origin.getChildren();
187            for (AbstractXmlNode child : children) {
188                clone.appendDeepClone(child);
189            }
190            return clone;
191        }
192    
193        /**
194         * Returns a duplicate of this node, i.e., serves as a generic copy
195         * constructor for nodes.
196         *
197         * @param deep
198         *        If <code>true</code>, recursively clone the subtree under the
199         *        specified node; if <code>false</code>, clone only the node
200         *        itself (and its attributes, if it is an <code>Element</code>).
201         * @return The duplicate node.
202         */
203        public final Node cloneNode(final boolean deep) {
204            return node.cloneNode(deep);
205        }
206    
207        /**
208         * A <code>NamedNodeMap</code> containing the attributes of this node (if
209         * it is an <code>Element</code>) or <code>null</code> otherwise.
210         *
211         * @return NamedNodeMap
212         */
213        public final NamedNodeMap getAttributes() {
214            return node.getAttributes();
215        }
216    
217        /**
218         * Returns the local part of the qualified name of this node.
219         *
220         * @return String
221         */
222        public final String getLocalName() {
223            return node.getLocalName();
224        }
225    
226        /**
227         * The namespace URI of this node, or <code>null</code> if it is
228         * unspecified.
229         *
230         * @return String
231         */
232        public final String getNamespaceURI() {
233            return node.getNamespaceURI();
234        }
235    
236        /**
237         * The name of this node, depending on its type; see the table above.
238         *
239         * @return String
240         */
241        public final String getNodeName() {
242            return node.getNodeName();
243        }
244    
245        /**
246         * A code representing the type of the underlying object, as defined above.
247         *
248         * @return short
249         */
250        public final short getNodeType() {
251            return node.getNodeType();
252        }
253    
254        /**
255         * The value of this node, depending on its type; see the table above.
256         * @return String
257         */
258        public final String getNodeValue() {
259            return node.getNodeValue();
260        }
261    
262        /**
263         * The <code>Document</code> object associated with this node.
264         *
265         * @return Document
266         */
267        public final Document getOwnerDocument() {
268            return node.getOwnerDocument();
269        }
270    
271        /**
272         * Retrieves the {@link XmlDocument} containing this node.
273         * @param <T> type of the owner document.
274         * @param documentClass owner document's class.
275         * @return document containing this class.
276         * @since 0.6
277         */
278        public final <T extends XmlDocument> T getOwnerXmlDocument(
279                final Class<T> documentClass) {
280            final Document doc = node.getOwnerDocument();
281            Constructor<T> constructor;
282            try {
283                constructor = documentClass.getConstructor(Document.class);
284                return constructor.newInstance(doc);
285            } catch (SecurityException e) {
286                throw new IllegalArgumentException(e.getMessage(), e);
287            } catch (NoSuchMethodException e) {
288                throw new IllegalArgumentException(e.getMessage(), e);
289            } catch (InstantiationException e) {
290                throw new IllegalArgumentException(e.getMessage(), e);
291            } catch (IllegalAccessException e) {
292                throw new IllegalArgumentException(e.getMessage(), e);
293            } catch (InvocationTargetException e) {
294                throw new IllegalArgumentException(e.getMessage(), e);
295            }
296        }
297    
298        /**
299         * The namespace prefix of this node, or <code>null</code> if it is
300         * unspecified.
301         *
302         * @return String
303         */
304        public final String getPrefix() {
305            return node.getPrefix();
306        }
307    
308        /**
309         * Retrieves the object associated to a key on a this node. The object must
310         * first have been set to this node by calling setUserData with the same
311         * key.
312         *
313         * @param key
314         *        The key the object is associated to.
315         * @return Returns the DOMUserData associated to the given key on this node,
316         *         or <code>null</code> if there was none.
317         */
318        public final Object getUserData(final String key) {
319            return node.getUserData(key);
320        }
321    
322        /**
323         * Returns whether this node (if it is an element) has any attributes.
324         *
325         * @return <code>true</code> if this node has any attributes,
326         *         <code>false</code> otherwise.
327         */
328        public final boolean hasAttributes() {
329            return node.hasAttributes();
330        }
331    
332        /**
333         * Returns whether this node has any children.
334         *
335         * @return <code>true</code> if this node has any children,
336         *         <code>false</code> otherwise.
337         */
338        public final boolean hasChildNodes() {
339            return node.hasChildNodes();
340        }
341    
342        /**
343         * Inserts the node <code>newChild</code> before the existing child node
344         * <code>refChild</code>.
345         *
346         * @param newChild
347         *        The node to insert.
348         * @param refChild
349         *        The reference node, i.e., the node before which the new node must
350         *        be inserted.
351         * @return The node being inserted.
352         */
353        public final Node insertBefore(final Node newChild, final Node refChild) {
354            return node.insertBefore(getRawNode(newChild), getRawNode(refChild));
355        }
356    
357        /**
358         * Tests whether the DOM implementation implements a specific feature and
359         * that feature is supported by this node.
360         *
361         * @param feature
362         *        The name of the feature to test. This is the same name which can
363         *        be passed to the method <code>hasFeature</code> on
364         *        <code>DOMImplementation</code>.
365         * @param version
366         *        This is the version number of the feature to test. In Level 2,
367         *        version 1, this is the string "2.0". If the version is not
368         *        specified, supporting any version of the feature will cause the
369         *        method to return <code>true</code>.
370         * @return Returns <code>true</code> if the specified feature is supported
371         *         on this node, <code>false</code> otherwise.
372         */
373        public final boolean isSupported(final String feature,
374                                         final String version) {
375            return node.isSupported(feature, version);
376        }
377    
378        /**
379         * Puts all <code>Text</code> nodes in the full depth of the sub-tree
380         * underneath this <code>Node</code>, including attribute nodes, into a
381         * "normal" form where only structure (e.g., elements, comments, processing
382         * instructions, CDATA sections, and entity references) separates
383         * <code>Text</code> nodes, i.e., there are neither adjacent
384         * <code>Text</code> nodes nor empty <code>Text</code> nodes.
385         */
386        public final void normalize() {
387            node.normalize();
388        }
389    
390        /**
391         * Removes the child node indicated by <code>oldChild</code> from the list
392         * of children, and returns it.
393         *
394         * @param oldChild
395         *        The node being removed.
396         * @return The node removed.
397         */
398        public final Node removeChild(final Node oldChild) {
399            return node.removeChild(getRawNode(oldChild));
400        }
401    
402        /**
403         * Replaces the child node <code>oldChild</code> with
404         * <code>newChild</code> in the list of children, and returns the
405         * <code>oldChild</code> node.
406         *
407         * @param newChild
408         *        The new node to put in the child list.
409         * @param oldChild
410         *        The node being replaced in the list.
411         * @return The node replaced.
412         */
413        public final Node replaceChild(final Node newChild, final Node oldChild) {
414            return node.replaceChild(getRawNode(newChild), getRawNode(oldChild));
415        }
416    
417        /**
418         * The value of this node, depending on its type; see the table above.
419         *
420         * @param nodeValue
421         *        String
422         */
423        public final void setNodeValue(final String nodeValue) {
424            node.setNodeValue(nodeValue);
425        }
426    
427        /**
428         * The namespace prefix of this node, or <code>null</code> if it is
429         * unspecified.
430         *
431         * @param prefix
432         *        String
433         */
434        public final void setPrefix(final String prefix) {
435            node.setPrefix(prefix);
436        }
437    
438        /**
439         * Associate an object to a key on this node. The object can later be
440         * retrieved from this node by calling getUserData with the same key.
441         *
442         * @param key
443         *        The key to associate the object to.
444         * @param data
445         *        The object to associate to the given key, or <code>null</code>
446         *        to remove any existing association to that key.
447         * @param handler
448         *        The handler to associate to that key, or <code>null</code>
449         * @return Returns the <code>DOMUserData</code> previously associated to
450         *         the given key on this node, or <code>null</code> if there was
451         *         none.
452         */
453        public final Object setUserData(final String key, final Object data,
454                                  final UserDataHandler handler) {
455            return node.setUserData(key, data, handler);
456        }
457    
458        /**
459         * The first child of this node.
460         *
461         * @return Node
462         */
463        public final Node getFirstChild() {
464            return factory.getXmlNode(node.getFirstChild());
465        }
466    
467        /**
468         * The last child of this node.
469         *
470         * @return Node
471         */
472        public final Node getLastChild() {
473            final Node lastChild = node.getLastChild();
474            return factory.getXmlNode(lastChild);
475        }
476    
477        /**
478         * The node immediately following this node.
479         *
480         * @return Node
481         */
482        public final Node getNextSibling() {
483            return factory.getXmlNode(node.getNextSibling());
484        }
485    
486        /**
487         * The node immediately preceding this node.
488         *
489         * @return Node
490         */
491        public final Node getPreviousSibling() {
492            return factory.getXmlNode(node.getPreviousSibling());
493        }
494    
495        /**
496         * The parent of this node.
497         *
498         * @return Node
499         */
500        public final Node getParentNode() {
501            return factory.getXmlNode(node.getParentNode());
502        }
503    
504        /**
505         * The absolute base URI of this node or <code>null</code> if the
506         * implementation wasn't able to obtain an absolute URI.
507         *
508         * @return String
509         */
510        public final String getBaseURI() {
511            return node.getBaseURI();
512        }
513    
514        /**
515         * Compares the reference node, i.e.
516         *
517         * @param other
518         *        The node to compare against the reference node.
519         * @return Returns how the node is positioned relatively to the reference
520         *         node.
521         */
522        public final short compareDocumentPosition(final Node other) {
523            return node.compareDocumentPosition(getRawNode(other));
524        }
525    
526        /**
527         * This attribute returns the text content of this node and its descendants.
528         *
529         * @return String
530         */
531        public final String getTextContent() {
532            return node.getTextContent();
533        }
534    
535        /**
536         * Returns the text contents of this node, similar to
537         * {@link #getTextContent()} but without recursion.
538         *
539         * @return text content.
540         * @since 0.6
541         */
542        public final String getFirstLevelTextContent() {
543            if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
544                return node.getNodeValue();
545            }
546    
547            StringBuilder str = new StringBuilder();
548            Node child = node.getFirstChild();
549            while (child != null) {
550                final short type = child.getNodeType();
551                if ((type == Node.TEXT_NODE) || (type == Node.CDATA_SECTION_NODE)) {
552                    str.append(child.getNodeValue());
553                }
554                child = child.getNextSibling();
555            }
556    
557            return str.toString();
558        }
559    
560        /**
561         * This attribute returns the text content of this node and its descendants.
562         *
563         * @param textContent
564         *        String
565         */
566        public final void setTextContent(final String textContent) {
567            node.setTextContent(textContent);
568        }
569    
570        /**
571         * Returns whether this node is the same node as the given one.
572         *
573         * @param other
574         *        The node to test against.
575         * @return Returns <code>true</code> if the nodes are the same,
576         *         <code>false</code> otherwise.
577         */
578        public final boolean isSameNode(final Node other) {
579            return node.isSameNode(getRawNode(other));
580        }
581    
582        /**
583         * Look up the prefix associated to the given namespace URI, starting from
584         * this node.
585         *
586         * @param namespaceURI
587         *        The namespace URI to look for.
588         * @return Returns an associated namespace prefix if found or
589         *         <code>null</code> if none is found. If more than one prefix are
590         *         associated to the namespace prefix, the returned namespace prefix
591         *         is implementation dependent.
592         */
593        public final String lookupPrefix(final String namespaceURI) {
594            return node.lookupPrefix(namespaceURI);
595        }
596    
597        /**
598         * This method checks if the specified <code>namespaceURI</code> is the
599         * default namespace or not.
600         *
601         * @param namespaceURI
602         *        The namespace URI to look for.
603         * @return Returns <code>true</code> if the specified
604         *         <code>namespaceURI</code> is the default namespace,
605         *         <code>false</code> otherwise.
606         */
607        public final boolean isDefaultNamespace(final String namespaceURI) {
608            return node.isDefaultNamespace(namespaceURI);
609        }
610    
611        /**
612         * Look up the namespace URI associated to the given prefix, starting from
613         * this node.
614         *
615         * @param prefix
616         *        The prefix to look for. If this parameter is <code>null</code>,
617         *        the method will return the default namespace URI if any.
618         * @return Returns the associated namespace URI or <code>null</code> if
619         *         none is found.
620         */
621        public final String lookupNamespaceURI(final String prefix) {
622            return node.lookupNamespaceURI(prefix);
623        }
624    
625        /**
626         * Tests whether two nodes are equal.
627         *
628         * @param other
629         *        The node to compare equality with.
630         * @return Returns <code>true</code> if the nodes are equal,
631         *         <code>false</code> otherwise.
632         */
633        public final boolean isEqualNode(final Node other) {
634            return node.isEqualNode(getRawNode(other));
635        }
636    
637        /**
638         * This method returns a specialized object which implements the specialized
639         * APIs of the specified feature and version, as specified in .
640         *
641         * @param feature
642         *        The name of the feature requested. Note that any plus sign "+"
643         *        prepended to the name of the feature will be ignored since it is
644         *        not significant in the context of this method.
645         * @param version
646         *        This is the version number of the feature to test.
647         * @return Returns an object which implements the specialized APIs of the
648         *         specified feature and version, if any, or <code>null</code> if
649         *         there is no object which implements interfaces associated with
650         *         that feature. If the <code>DOMObject</code> returned by this
651         *         method implements the <code>Node</code> interface, it must
652         *         delegate to the primary core <code>Node</code> and not return
653         *         results inconsistent with the primary core <code>Node</code>
654         *         such as attributes, childNodes, etc.
655         */
656        public final Object getFeature(final String feature, final String version) {
657            return node.getFeature(feature, version);
658        }
659    
660        /**
661         * Get the raw node encapsulated by the specified node. If the specified
662         * node is a raw node then it is returned.
663         *
664         * @param arg
665         *        The node that may be wrapping a raw node.
666         * @return The raw node.
667         */
668        private Node getRawNode(final Node arg) {
669            final Node rawNode;
670            if (arg instanceof XmlNode) {
671                final XmlNode xmlNode = (XmlNode) arg;
672                rawNode = xmlNode.getNode();
673            } else {
674                rawNode = arg;
675            }
676            return rawNode;
677        }
678    
679        /**
680         * Adds an instance of the specified child class to this node. This causes a
681         * new node to be created and appended to this node. The type of the node to
682         * add must be a subclass of the XmlNode class.
683         *
684         * @param <T>
685         *        Node type to load.
686         * @param tagClass
687         *        The class type of the node to add.
688         * @return Newly created and appended node or null if the child is not
689         *         allowed on this node.
690         * @since 0.6
691         */
692        public final <T extends XmlNode> T addChild(final Class<T> tagClass) {
693            try {
694                final T tempTag = tagClass.newInstance();
695                final String tagName = tempTag.getTagName();
696    
697                if (canContainChild(tagName)) {
698                    final Document document = getOwnerDocument();
699                    final Node newNode = document.createElement(tagName);
700    
701                    final T newTag =
702                        tagClass.cast(tempTag.newInstance(newNode, factory));
703    
704                    return newTag;
705                } else {
706                    throw new IllegalArgumentException("<" + getTagName()
707                            + "> must not contain <" + tagName + ">");
708                }
709            } catch (final InstantiationException e) {
710                e.printStackTrace();
711            } catch (final IllegalAccessException e) {
712                e.printStackTrace();
713            }
714    
715            return null;
716        }
717    
718        /**
719         * Adds an instance of the specified child class to this node and appends
720         * it to the child nodes of this node.
721         *
722         * @param <T>
723         *        Node type to load.
724         * @param tagClass
725         *        The class type of the node to add.
726         * @return Newly created and appended node or null if the child is not
727         *         allowed on this node.
728         */
729        public final <T extends XmlNode> T appendChild(final Class<T> tagClass) {
730            final T newTag = addChild(tagClass);
731    
732            appendChild(newTag);
733    
734            return newTag;
735        }
736    
737        /**
738         * {@inheritDoc}
739         */
740        public final XmlNode addChild(final String tagName) {
741            if (tagName == null) {
742                throw new IllegalArgumentException("tag name must not be null!");
743            }
744            final String tag = tagName.trim();
745            if (tag.indexOf(' ') >= 0) {
746                throw new IllegalArgumentException(
747                        "tag name must not contain attributes!");
748            }
749            int dotPos = tag.indexOf(':');
750            if (canContainChild(tag) || dotPos >= 0) {
751                final Document document = getOwnerDocument();
752                final Node newNode = document.createElement(tag);
753    
754                /** @todo This does not work for text nodes. */
755                final XmlNode newTag = factory.getXmlNode(newNode);
756                appendChild(newTag);
757    
758                return newTag;
759            } else {
760                throw new IllegalArgumentException("<" + getTagName()
761                        + "> must not contain <" + tagName + ">");
762            }
763        }
764    
765    
766        /**
767         * Return a collection of child nodes with the specified tag class.
768         *
769         * @param <T>
770         *        Type of the child nodes.
771         * @param tagClass
772         *        Class of child node to return.
773         * @return A collection of child nodes of the specified type. If this node
774         *         does not contain any child nodes of the specified type then an
775         *         empty collection is returned.
776         */
777        public final <T extends XmlNode> Collection<T> getChildNodes(
778                final Class<T> tagClass) {
779            final Collection<T> nodes = new java.util.ArrayList<T>();
780    
781            try {
782                final T newInstance = tagClass.newInstance();
783                final String tagName = newInstance.getTagName();
784    
785                final NodeList list = getChildNodes();
786                for (int i = 0; i < list.getLength(); i++) {
787                    final Node n = list.item(i);
788                    if (n instanceof XmlNode) {
789                        final XmlNode xmlNode = (XmlNode) n;
790                        final String xmlNodeTagName = xmlNode.getTagName();
791                        if (xmlNodeTagName.compareTo(tagName) == 0) {
792                            nodes.add(tagClass.cast(xmlNode));
793                        }
794                    }
795                }
796            } catch (InstantiationException e) {
797                e.printStackTrace();
798            } catch (IllegalAccessException e) {
799                e.printStackTrace();
800            }
801    
802            return nodes;
803        }
804    
805        /**
806         * {@inheritDoc}
807         */
808        @SuppressWarnings("unchecked")
809        public final <T extends XmlNode> Collection<T> getChildren() {
810            final Collection<T> nodes = new java.util.ArrayList<T>();
811    
812            final NodeList list = getChildNodes();
813            for (int i = 0; i < list.getLength(); i++) {
814                final Node n = list.item(i);
815                if (n instanceof XmlNode) {
816                    final T xmlNode = (T) factory.getXmlNode(n);
817                    nodes.add(xmlNode);
818                }
819            }
820    
821            return nodes;
822        }
823    
824        /**
825         * Can the specified sub-tag be contained within this node?
826         *
827         * @param childName
828         *        Name of child.
829         * @return True if the sub-tag is allowed on this node.
830         */
831        protected abstract boolean canContainChild(final String childName);
832    
833        /**
834         * {@inheritDoc}
835         */
836        @Override
837        public String toString() {
838            final ByteArrayOutputStream out = new ByteArrayOutputStream();
839            final Result result = new StreamResult(out);
840            final TransformerFactory transformerFactory =
841                TransformerFactory.newInstance();
842            try {
843                final Transformer transformer = transformerFactory.newTransformer();
844                final String encoding = System.getProperty("jvoicexml.xml.encoding",
845                    "UTF-8");
846                transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
847                final Source source = new DOMSource(this);
848                transformer.transform(source, result);
849                return out.toString(encoding);
850            } catch (TransformerException e) {
851                return super.toString();
852            } catch (UnsupportedEncodingException e) {
853                return super.toString();
854            }
855        }
856    
857        /**
858         * {@inheritDoc}
859         */
860        public Collection<String> getAttributeNames() {
861            return new ArrayList<String>();
862        }
863    
864        /**
865         * Retrieves a list of all attributes defined in this node.
866         * @return list of all defined attributes
867         * @since 0.7.5
868         */
869        public final Collection<String> getDefinedAttributeNames() {
870            final Collection<String> attributes = new java.util.ArrayList<String>();
871            final NamedNodeMap nodes = getAttributes();
872            int index = 0;
873            for (Node current = nodes.item(index); index < nodes.getLength();
874                index++) {
875                final String name = current.getNodeName();
876                attributes.add(name);
877            }
878            return attributes;
879        }
880    
881        /**
882         * {@inheritDoc}
883         * @since 0.7
884         */
885        @Override
886        public final boolean equals(final Object obj) {
887            if (obj == null) {
888                return false;
889            }
890            if (!(obj instanceof AbstractXmlNode)) {
891                return false;
892            }
893            final AbstractXmlNode other = (AbstractXmlNode) obj;
894    
895            if (node == other.node) {
896                return true;
897            }
898            if (node == null) {
899                return false;
900            }
901            return node.isEqualNode(other.node);
902        }
903    
904        /**
905         * {@inheritDoc}
906         * @since 0.7
907         */
908        @Override
909        public final int hashCode() {
910            if (node == null) {
911                return super.hashCode();
912            }
913            return node.hashCode();
914        }
915    }