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 }