1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.configuration;
19
20 import java.io.File;
21 import java.io.InputStream;
22 import java.io.Reader;
23 import java.io.Writer;
24 import java.net.URL;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.Iterator;
28 import java.util.List;
29
30 import javax.xml.parsers.DocumentBuilder;
31 import javax.xml.parsers.DocumentBuilderFactory;
32 import javax.xml.parsers.ParserConfigurationException;
33 import javax.xml.transform.OutputKeys;
34 import javax.xml.transform.Result;
35 import javax.xml.transform.Source;
36 import javax.xml.transform.Transformer;
37 import javax.xml.transform.TransformerException;
38 import javax.xml.transform.TransformerFactory;
39 import javax.xml.transform.TransformerFactoryConfigurationError;
40 import javax.xml.transform.dom.DOMSource;
41 import javax.xml.transform.stream.StreamResult;
42
43 import org.apache.commons.collections.iterators.SingletonIterator;
44 import org.w3c.dom.Attr;
45 import org.w3c.dom.CDATASection;
46 import org.w3c.dom.DOMException;
47 import org.w3c.dom.Document;
48 import org.w3c.dom.Element;
49 import org.w3c.dom.NamedNodeMap;
50 import org.w3c.dom.NodeList;
51 import org.w3c.dom.Text;
52 import org.xml.sax.InputSource;
53 import org.xml.sax.SAXException;
54 import org.xml.sax.SAXParseException;
55 import org.xml.sax.helpers.DefaultHandler;
56
57 /***
58 * <p>A specialized hierarchical configuration class that is able to parse XML
59 * documents.</p>
60 *
61 * <p>The parsed document will be stored keeping its structure. The class also
62 * tries to preserve as much information from the loaded XML document as
63 * possible, including comments and processing instructions. These will be
64 * contained in documents created by the <code>save()</code> methods, too.</p>
65 *
66 * <p>Like other file based configuration classes this class maintains the name
67 * and path to the loaded configuration file. These properties can be altered
68 * using several setter methods, but they are not modified by <code>save()</code>
69 * and <code>load()</code> methods. If XML documents contain relative paths to
70 * other documents (e.g. to a DTD), these references are resolved based on the
71 * path set for this configuration.</p>
72 *
73 * <p>By inheriting from <code>{@link AbstractConfiguration}</code> this class
74 * provides some extended functionaly, e.g. interpolation of property values.
75 * Like in <code>{@link PropertiesConfiguration}</code> property values can
76 * contain delimiter characters (the comma ',' per default) and are then splitted
77 * into multiple values. This works for XML attributes and text content of
78 * elements as well. The delimiter can be escaped by a backslash. As an example
79 * consider the following XML fragment:</p>
80 *
81 * <p>
82 * <pre>
83 * <config>
84 * <array>10,20,30,40</array>
85 * <scalar>3\,1415</scalar>
86 * <cite text="To be or not to be\, this is the question!"/>
87 * </config>
88 * </pre>
89 * </p>
90 * <p>Here the content of the <code>array</code> element will be splitted at
91 * the commas, so the <code>array</code> key will be assigned 4 values. In the
92 * <code>scalar</code> property and the <code>text</code> attribute of the
93 * <code>cite</code> element the comma is escaped, so that no splitting is
94 * performed.</p>
95 *
96 * <p><code>XMLConfiguration</code> implements the <code>{@link FileConfiguration}</code>
97 * interface and thus provides full support for loading XML documents from
98 * different sources like files, URLs, or streams. A full description of these
99 * features can be found in the documentation of
100 * <code>{@link AbstractFileConfiguration}</code>.</p>
101 *
102 * @since commons-configuration 1.0
103 *
104 * @author Jörg Schaible
105 * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger </a>
106 * @version $Revision: 513498 $, $Date: 2007-03-01 22:15:07 +0100 (Do, 01 Mrz 2007) $
107 */
108 public class XMLConfiguration extends AbstractHierarchicalFileConfiguration
109 {
110 /***
111 * The serial version UID.
112 */
113 private static final long serialVersionUID = 2453781111653383552L;
114
115 /*** Constant for the default root element name. */
116 private static final String DEFAULT_ROOT_NAME = "configuration";
117
118 /*** The document from this configuration's data source. */
119 private Document document;
120
121 /*** Stores the name of the root element. */
122 private String rootElementName;
123
124 /*** Stores the public ID from the DOCTYPE.*/
125 private String publicID;
126
127 /*** Stores the system ID from the DOCTYPE.*/
128 private String systemID;
129
130 /*** Stores the document builder that should be used for loading.*/
131 private DocumentBuilder documentBuilder;
132
133 /*** Stores a flag whether DTD validation should be performed.*/
134 private boolean validating;
135
136 /***
137 * Creates a new instance of <code>XMLConfiguration</code>.
138 */
139 public XMLConfiguration()
140 {
141 super();
142 }
143
144 /***
145 * Creates a new instance of <code>XMLConfiguration</code> and copies the
146 * content of the passed in configuration into this object. Note that only
147 * the data of the passed in configuration will be copied. If, for instance,
148 * the other configuration is a <code>XMLConfiguration</code>, too,
149 * things like comments or processing instructions will be lost.
150 *
151 * @param c the configuration to copy
152 * @since 1.4
153 */
154 public XMLConfiguration(HierarchicalConfiguration c)
155 {
156 super(c);
157 clearReferences(getRootNode());
158 }
159
160 /***
161 * Creates a new instance of <code>XMLConfiguration</code>. The
162 * configuration is loaded from the specified file
163 *
164 * @param fileName the name of the file to load
165 * @throws ConfigurationException if the file cannot be loaded
166 */
167 public XMLConfiguration(String fileName) throws ConfigurationException
168 {
169 super(fileName);
170 }
171
172 /***
173 * Creates a new instance of <code>XMLConfiguration</code>.
174 * The configuration is loaded from the specified file.
175 *
176 * @param file the file
177 * @throws ConfigurationException if an error occurs while loading the file
178 */
179 public XMLConfiguration(File file) throws ConfigurationException
180 {
181 super(file);
182 }
183
184 /***
185 * Creates a new instance of <code>XMLConfiguration</code>.
186 * The configuration is loaded from the specified URL.
187 *
188 * @param url the URL
189 * @throws ConfigurationException if loading causes an error
190 */
191 public XMLConfiguration(URL url) throws ConfigurationException
192 {
193 super(url);
194 }
195
196 /***
197 * Returns the name of the root element. If this configuration was loaded
198 * from a XML document, the name of this document's root element is
199 * returned. Otherwise it is possible to set a name for the root element
200 * that will be used when this configuration is stored.
201 *
202 * @return the name of the root element
203 */
204 public String getRootElementName()
205 {
206 if (getDocument() == null)
207 {
208 return (rootElementName == null) ? DEFAULT_ROOT_NAME : rootElementName;
209 }
210 else
211 {
212 return getDocument().getDocumentElement().getNodeName();
213 }
214 }
215
216 /***
217 * Sets the name of the root element. This name is used when this
218 * configuration object is stored in an XML file. Note that setting the name
219 * of the root element works only if this configuration has been newly
220 * created. If the configuration was loaded from an XML file, the name
221 * cannot be changed and an <code>UnsupportedOperationException</code>
222 * exception is thrown. Whether this configuration has been loaded from an
223 * XML document or not can be found out using the <code>getDocument()</code>
224 * method.
225 *
226 * @param name the name of the root element
227 */
228 public void setRootElementName(String name)
229 {
230 if (getDocument() != null)
231 {
232 throw new UnsupportedOperationException("The name of the root element "
233 + "cannot be changed when loaded from an XML document!");
234 }
235 rootElementName = name;
236 }
237
238 /***
239 * Returns the <code>DocumentBuilder</code> object that is used for
240 * loading documents. If no specific builder has been set, this method
241 * returns <b>null</b>.
242 *
243 * @return the <code>DocumentBuilder</code> for loading new documents
244 * @since 1.2
245 */
246 public DocumentBuilder getDocumentBuilder()
247 {
248 return documentBuilder;
249 }
250
251 /***
252 * Sets the <code>DocumentBuilder</code> object to be used for loading
253 * documents. This method makes it possible to specify the exact document
254 * builder. So an application can create a builder, configure it for its
255 * special needs, and then pass it to this method.
256 *
257 * @param documentBuilder the document builder to be used; if undefined, a
258 * default builder will be used
259 * @since 1.2
260 */
261 public void setDocumentBuilder(DocumentBuilder documentBuilder)
262 {
263 this.documentBuilder = documentBuilder;
264 }
265
266 /***
267 * Returns the public ID of the DOCTYPE declaration from the loaded XML
268 * document. This is <b>null</b> if no document has been loaded yet or if
269 * the document does not contain a DOCTYPE declaration with a public ID.
270 *
271 * @return the public ID
272 * @since 1.3
273 */
274 public String getPublicID()
275 {
276 return publicID;
277 }
278
279 /***
280 * Sets the public ID of the DOCTYPE declaration. When this configuration is
281 * saved, a DOCTYPE declaration will be constructed that contains this
282 * public ID.
283 *
284 * @param publicID the public ID
285 * @since 1.3
286 */
287 public void setPublicID(String publicID)
288 {
289 this.publicID = publicID;
290 }
291
292 /***
293 * Returns the system ID of the DOCTYPE declaration from the loaded XML
294 * document. This is <b>null</b> if no document has been loaded yet or if
295 * the document does not contain a DOCTYPE declaration with a system ID.
296 *
297 * @return the system ID
298 * @since 1.3
299 */
300 public String getSystemID()
301 {
302 return systemID;
303 }
304
305 /***
306 * Sets the system ID of the DOCTYPE declaration. When this configuration is
307 * saved, a DOCTYPE declaration will be constructed that contains this
308 * system ID.
309 *
310 * @param systemID the system ID
311 * @since 1.3
312 */
313 public void setSystemID(String systemID)
314 {
315 this.systemID = systemID;
316 }
317
318 /***
319 * Returns the value of the validating flag.
320 *
321 * @return the validating flag
322 * @since 1.2
323 */
324 public boolean isValidating()
325 {
326 return validating;
327 }
328
329 /***
330 * Sets the value of the validating flag. This flag determines whether
331 * DTD validation should be performed when loading XML documents. This
332 * flag is evaluated only if no custom <code>DocumentBuilder</code> was set.
333 *
334 * @param validating the validating flag
335 * @since 1.2
336 */
337 public void setValidating(boolean validating)
338 {
339 this.validating = validating;
340 }
341
342 /***
343 * Returns the XML document this configuration was loaded from. The return
344 * value is <b>null</b> if this configuration was not loaded from a XML
345 * document.
346 *
347 * @return the XML document this configuration was loaded from
348 */
349 public Document getDocument()
350 {
351 return document;
352 }
353
354 /***
355 * Removes all properties from this configuration. If this configuration
356 * was loaded from a file, the associated DOM document is also cleared.
357 */
358 public void clear()
359 {
360 super.clear();
361 document = null;
362 }
363
364 /***
365 * Initializes this configuration from an XML document.
366 *
367 * @param document the document to be parsed
368 * @param elemRefs a flag whether references to the XML elements should be set
369 */
370 public void initProperties(Document document, boolean elemRefs)
371 {
372 if (document.getDoctype() != null)
373 {
374 setPublicID(document.getDoctype().getPublicId());
375 setSystemID(document.getDoctype().getSystemId());
376 }
377 constructHierarchy(getRoot(), document.getDocumentElement(), elemRefs);
378 }
379
380 /***
381 * Helper method for building the internal storage hierarchy. The XML
382 * elements are transformed into node objects.
383 *
384 * @param node the actual node
385 * @param element the actual XML element
386 * @param elemRefs a flag whether references to the XML elements should be set
387 */
388 private void constructHierarchy(Node node, Element element, boolean elemRefs)
389 {
390 processAttributes(node, element, elemRefs);
391 StringBuffer buffer = new StringBuffer();
392 NodeList list = element.getChildNodes();
393 for (int i = 0; i < list.getLength(); i++)
394 {
395 org.w3c.dom.Node w3cNode = list.item(i);
396 if (w3cNode instanceof Element)
397 {
398 Element child = (Element) w3cNode;
399 Node childNode = new XMLNode(child.getTagName(),
400 elemRefs ? child : null);
401 constructHierarchy(childNode, child, elemRefs);
402 node.addChild(childNode);
403 handleDelimiters(node, childNode);
404 }
405 else if (w3cNode instanceof Text)
406 {
407 Text data = (Text) w3cNode;
408 buffer.append(data.getData());
409 }
410 }
411 String text = buffer.toString().trim();
412 if (text.length() > 0 || !node.hasChildren())
413 {
414 node.setValue(text);
415 }
416 }
417
418 /***
419 * Helper method for constructing node objects for the attributes of the
420 * given XML element.
421 *
422 * @param node the actual node
423 * @param element the actual XML element
424 * @param elemRefs a flag whether references to the XML elements should be set
425 */
426 private void processAttributes(Node node, Element element, boolean elemRefs)
427 {
428 NamedNodeMap attributes = element.getAttributes();
429 for (int i = 0; i < attributes.getLength(); ++i)
430 {
431 org.w3c.dom.Node w3cNode = attributes.item(i);
432 if (w3cNode instanceof Attr)
433 {
434 Attr attr = (Attr) w3cNode;
435 Iterator it;
436 if (isDelimiterParsingDisabled())
437 {
438 it = new SingletonIterator(attr.getValue());
439 }
440 else
441 {
442 it = PropertyConverter.split(attr.getValue(), getListDelimiter()).iterator();
443 }
444 while (it.hasNext())
445 {
446 Node child = new XMLNode(attr.getName(),
447 elemRefs ? element : null);
448 child.setValue(it.next());
449 node.addAttribute(child);
450 }
451 }
452 }
453 }
454
455 /***
456 * Deals with elements whose value is a list. In this case multiple child
457 * elements must be added.
458 *
459 * @param parent the parent element
460 * @param child the child element
461 */
462 private void handleDelimiters(Node parent, Node child)
463 {
464 if (child.getValue() != null)
465 {
466 List values;
467 if (isDelimiterParsingDisabled())
468 {
469 values = new ArrayList();
470 values.add(child.getValue().toString());
471 }
472 else
473 {
474 values = PropertyConverter.split(child.getValue().toString(),
475 getListDelimiter());
476 }
477
478 if (values.size() > 1)
479 {
480
481 parent.remove(child);
482
483 for (Iterator it = values.iterator(); it.hasNext();)
484 {
485 Node c = new XMLNode(child.getName(), null);
486 c.setValue(it.next());
487 parent.addChild(c);
488 }
489 }
490 else if (values.size() == 1)
491 {
492
493
494 child.setValue(values.get(0));
495 }
496 }
497 }
498
499 /***
500 * Creates the <code>DocumentBuilder</code> to be used for loading files.
501 * This implementation checks whether a specific
502 * <code>DocumentBuilder</code> has been set. If this is the case, this
503 * one is used. Otherwise a default builder is created. Depending on the
504 * value of the validating flag this builder will be a validating or a non
505 * validating <code>DocumentBuilder</code>.
506 *
507 * @return the <code>DocumentBuilder</code> for loading configuration
508 * files
509 * @throws ParserConfigurationException if an error occurs
510 * @since 1.2
511 */
512 protected DocumentBuilder createDocumentBuilder()
513 throws ParserConfigurationException
514 {
515 if (getDocumentBuilder() != null)
516 {
517 return getDocumentBuilder();
518 }
519 else
520 {
521 DocumentBuilderFactory factory = DocumentBuilderFactory
522 .newInstance();
523 factory.setValidating(isValidating());
524 DocumentBuilder result = factory.newDocumentBuilder();
525
526 if (isValidating())
527 {
528
529 result.setErrorHandler(new DefaultHandler()
530 {
531 public void error(SAXParseException ex) throws SAXException
532 {
533 throw ex;
534 }
535 });
536 }
537 return result;
538 }
539 }
540
541 /***
542 * Creates a DOM document from the internal tree of configuration nodes.
543 *
544 * @return the new document
545 * @throws ConfigurationException if an error occurs
546 */
547 protected Document createDocument() throws ConfigurationException
548 {
549 try
550 {
551 if (document == null)
552 {
553 DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
554 Document newDocument = builder.newDocument();
555 Element rootElem = newDocument.createElement(getRootElementName());
556 newDocument.appendChild(rootElem);
557 document = newDocument;
558 }
559
560 XMLBuilderVisitor builder = new XMLBuilderVisitor(document, getListDelimiter());
561 builder.processDocument(getRoot());
562 return document;
563 }
564 catch (DOMException domEx)
565 {
566 throw new ConfigurationException(domEx);
567 }
568 catch (ParserConfigurationException pex)
569 {
570 throw new ConfigurationException(pex);
571 }
572 }
573
574 /***
575 * Creates a new node object. This implementation returns an instance of the
576 * <code>XMLNode</code> class.
577 *
578 * @param name the node's name
579 * @return the new node
580 */
581 protected Node createNode(String name)
582 {
583 return new XMLNode(name, null);
584 }
585
586 /***
587 * Loads the configuration from the given input stream.
588 *
589 * @param in the input stream
590 * @throws ConfigurationException if an error occurs
591 */
592 public void load(InputStream in) throws ConfigurationException
593 {
594 load(new InputSource(in));
595 }
596
597 /***
598 * Load the configuration from the given reader.
599 * Note that the <code>clear()</code> method is not called, so
600 * the properties contained in the loaded file will be added to the
601 * actual set of properties.
602 *
603 * @param in An InputStream.
604 *
605 * @throws ConfigurationException if an error occurs
606 */
607 public void load(Reader in) throws ConfigurationException
608 {
609 load(new InputSource(in));
610 }
611
612 /***
613 * Loads a configuration file from the specified input source.
614 * @param source the input source
615 * @throws ConfigurationException if an error occurs
616 */
617 private void load(InputSource source) throws ConfigurationException
618 {
619 try
620 {
621 URL sourceURL = getDelegate().getURL();
622 if (sourceURL != null)
623 {
624 source.setSystemId(sourceURL.toString());
625 }
626
627 DocumentBuilder builder = createDocumentBuilder();
628 Document newDocument = builder.parse(source);
629 Document oldDocument = document;
630 document = null;
631 initProperties(newDocument, oldDocument == null);
632 document = (oldDocument == null) ? newDocument : oldDocument;
633 }
634 catch (Exception e)
635 {
636 throw new ConfigurationException(e.getMessage(), e);
637 }
638 }
639
640 /***
641 * Saves the configuration to the specified writer.
642 *
643 * @param writer the writer used to save the configuration
644 * @throws ConfigurationException if an error occurs
645 */
646 public void save(Writer writer) throws ConfigurationException
647 {
648 try
649 {
650 Transformer transformer = createTransformer();
651 Source source = new DOMSource(createDocument());
652 Result result = new StreamResult(writer);
653 transformer.transform(source, result);
654 }
655 catch (TransformerException e)
656 {
657 throw new ConfigurationException(e.getMessage(), e);
658 }
659 catch (TransformerFactoryConfigurationError err)
660 {
661 throw new ConfigurationException(err.getMessage(), err);
662 }
663 }
664
665 /***
666 * Creates and initializes the transformer used for save operations. This
667 * base implementation initializes all of the default settings like
668 * indention mode and the DOCTYPE. Derived classes may overload this method
669 * if they have specific needs.
670 *
671 * @return the transformer to use for a save operation
672 * @throws TransformerException if an error occurs
673 * @since 1.3
674 */
675 protected Transformer createTransformer() throws TransformerException
676 {
677 Transformer transformer = TransformerFactory.newInstance()
678 .newTransformer();
679
680 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
681 if (getEncoding() != null)
682 {
683 transformer.setOutputProperty(OutputKeys.ENCODING, getEncoding());
684 }
685 if (getPublicID() != null)
686 {
687 transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC,
688 getPublicID());
689 }
690 if (getSystemID() != null)
691 {
692 transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,
693 getSystemID());
694 }
695
696 return transformer;
697 }
698
699 /***
700 * Creates a copy of this object. The new configuration object will contain
701 * the same properties as the original, but it will lose any connection to a
702 * source document (if one exists). This is to avoid race conditions if both
703 * the original and the copy are modified and then saved.
704 *
705 * @return the copy
706 */
707 public Object clone()
708 {
709 XMLConfiguration copy = (XMLConfiguration) super.clone();
710
711
712 copy.document = null;
713 copy.setDelegate(copy.createDelegate());
714
715 clearReferences(copy.getRootNode());
716
717 return copy;
718 }
719
720 /***
721 * Creates the file configuration delegate for this object. This implementation
722 * will return an instance of a class derived from <code>FileConfigurationDelegate</code>
723 * that deals with some specialities of <code>XMLConfiguration</code>.
724 * @return the delegate for this object
725 */
726 protected FileConfigurationDelegate createDelegate()
727 {
728 return new XMLFileConfigurationDelegate();
729 }
730
731 /***
732 * A specialized <code>Node</code> class that is connected with an XML
733 * element. Changes on a node are also performed on the associated element.
734 */
735 class XMLNode extends Node
736 {
737 /***
738 * The serial version UID.
739 */
740 private static final long serialVersionUID = -4133988932174596562L;
741
742 /***
743 * Creates a new instance of <code>XMLNode</code> and initializes it
744 * with a name and the corresponding XML element.
745 *
746 * @param name the node's name
747 * @param elem the XML element
748 */
749 public XMLNode(String name, Element elem)
750 {
751 super(name);
752 setReference(elem);
753 }
754
755 /***
756 * Sets the value of this node. If this node is associated with an XML
757 * element, this element will be updated, too.
758 *
759 * @param value the node's new value
760 */
761 public void setValue(Object value)
762 {
763 super.setValue(value);
764
765 if (getReference() != null && document != null)
766 {
767 if (isAttribute())
768 {
769 updateAttribute();
770 }
771 else
772 {
773 updateElement(value);
774 }
775 }
776 }
777
778 /***
779 * Updates the associated XML elements when a node is removed.
780 */
781 protected void removeReference()
782 {
783 if (getReference() != null)
784 {
785 Element element = (Element) getReference();
786 if (isAttribute())
787 {
788 updateAttribute();
789 }
790 else
791 {
792 org.w3c.dom.Node parentElem = element.getParentNode();
793 if (parentElem != null)
794 {
795 parentElem.removeChild(element);
796 }
797 }
798 }
799 }
800
801 /***
802 * Updates the node's value if it represents an element node.
803 *
804 * @param value the new value
805 */
806 private void updateElement(Object value)
807 {
808 Text txtNode = findTextNodeForUpdate();
809 if (value == null)
810 {
811
812 if (txtNode != null)
813 {
814 ((Element) getReference()).removeChild(txtNode);
815 }
816 }
817 else
818 {
819 if (txtNode == null)
820 {
821 txtNode = document
822 .createTextNode(PropertyConverter.escapeDelimiters(
823 value.toString(), getListDelimiter()));
824 if (((Element) getReference()).getFirstChild() != null)
825 {
826 ((Element) getReference()).insertBefore(txtNode,
827 ((Element) getReference()).getFirstChild());
828 }
829 else
830 {
831 ((Element) getReference()).appendChild(txtNode);
832 }
833 }
834 else
835 {
836 txtNode.setNodeValue(PropertyConverter.escapeDelimiters(
837 value.toString(), getListDelimiter()));
838 }
839 }
840 }
841
842 /***
843 * Updates the node's value if it represents an attribute.
844 *
845 */
846 private void updateAttribute()
847 {
848 XMLBuilderVisitor.updateAttribute(getParent(), getName(), getListDelimiter());
849 }
850
851 /***
852 * Returns the only text node of this element for update. This method is
853 * called when the element's text changes. Then all text nodes except
854 * for the first are removed. A reference to the first is returned or
855 * <b>null </b> if there is no text node at all.
856 *
857 * @return the first and only text node
858 */
859 private Text findTextNodeForUpdate()
860 {
861 Text result = null;
862 Element elem = (Element) getReference();
863
864 NodeList children = elem.getChildNodes();
865 Collection textNodes = new ArrayList();
866 for (int i = 0; i < children.getLength(); i++)
867 {
868 org.w3c.dom.Node nd = children.item(i);
869 if (nd instanceof Text)
870 {
871 if (result == null)
872 {
873 result = (Text) nd;
874 }
875 else
876 {
877 textNodes.add(nd);
878 }
879 }
880 }
881
882
883 if (result instanceof CDATASection)
884 {
885 textNodes.add(result);
886 result = null;
887 }
888
889
890 for (Iterator it = textNodes.iterator(); it.hasNext();)
891 {
892 elem.removeChild((org.w3c.dom.Node) it.next());
893 }
894 return result;
895 }
896 }
897
898 /***
899 * A concrete <code>BuilderVisitor</code> that can construct XML
900 * documents.
901 */
902 static class XMLBuilderVisitor extends BuilderVisitor
903 {
904 /*** Stores the document to be constructed. */
905 private Document document;
906
907 /*** Stores the list delimiter.*/
908 private char listDelimiter = AbstractConfiguration.
909 getDefaultListDelimiter();
910
911 /***
912 * Creates a new instance of <code>XMLBuilderVisitor</code>
913 *
914 * @param doc the document to be created
915 * @param listDelimiter the delimiter for attribute properties with multiple values
916 */
917 public XMLBuilderVisitor(Document doc, char listDelimiter)
918 {
919 document = doc;
920 this.listDelimiter = listDelimiter;
921 }
922
923 /***
924 * Processes the node hierarchy and adds new nodes to the document.
925 *
926 * @param rootNode the root node
927 */
928 public void processDocument(Node rootNode)
929 {
930 rootNode.visit(this, null);
931 }
932
933 /***
934 * Inserts a new node. This implementation ensures that the correct
935 * XML element is created and inserted between the given siblings.
936 *
937 * @param newNode the node to insert
938 * @param parent the parent node
939 * @param sibling1 the first sibling
940 * @param sibling2 the second sibling
941 * @return the new node
942 */
943 protected Object insert(Node newNode, Node parent, Node sibling1, Node sibling2)
944 {
945 if (newNode.isAttribute())
946 {
947 updateAttribute(parent, getElement(parent), newNode.getName(), listDelimiter);
948 return null;
949 }
950
951 else
952 {
953 Element elem = document.createElement(newNode.getName());
954 if (newNode.getValue() != null)
955 {
956 elem.appendChild(document.createTextNode(
957 PropertyConverter.escapeDelimiters(newNode.getValue().toString(), listDelimiter)));
958 }
959 if (sibling2 == null)
960 {
961 getElement(parent).appendChild(elem);
962 }
963 else if (sibling1 != null)
964 {
965 getElement(parent).insertBefore(elem, getElement(sibling1).getNextSibling());
966 }
967 else
968 {
969 getElement(parent).insertBefore(elem, getElement(parent).getFirstChild());
970 }
971 return elem;
972 }
973 }
974
975 /***
976 * Helper method for updating the value of the specified node's
977 * attribute with the given name.
978 *
979 * @param node the affected node
980 * @param elem the element that is associated with this node
981 * @param name the name of the affected attribute
982 * @param listDelimiter the delimiter vor attributes with multiple values
983 */
984 private static void updateAttribute(Node node, Element elem, String name, char listDelimiter)
985 {
986 if (node != null && elem != null)
987 {
988 List attrs = node.getAttributes(name);
989 StringBuffer buf = new StringBuffer();
990 for (Iterator it = attrs.iterator(); it.hasNext();)
991 {
992 Node attr = (Node) it.next();
993 if (attr.getValue() != null)
994 {
995 if (buf.length() > 0)
996 {
997 buf.append(listDelimiter);
998 }
999 buf.append(PropertyConverter.escapeDelimiters(attr
1000 .getValue().toString(), getDefaultListDelimiter()));
1001 }
1002 attr.setReference(elem);
1003 }
1004
1005 if (buf.length() < 1)
1006 {
1007 elem.removeAttribute(name);
1008 }
1009 else
1010 {
1011 elem.setAttribute(name, buf.toString());
1012 }
1013 }
1014 }
1015
1016 /***
1017 * Updates the value of the specified attribute of the given node.
1018 * Because there can be multiple child nodes representing this attribute
1019 * the new value is determined by iterating over all those child nodes.
1020 *
1021 * @param node the affected node
1022 * @param name the name of the attribute
1023 * @param listDelimiter the delimiter vor attributes with multiple values
1024 */
1025 static void updateAttribute(Node node, String name, char listDelimiter)
1026 {
1027 if (node != null)
1028 {
1029 updateAttribute(node, (Element) node.getReference(), name, listDelimiter);
1030 }
1031 }
1032
1033 /***
1034 * Helper method for accessing the element of the specified node.
1035 *
1036 * @param node the node
1037 * @return the element of this node
1038 */
1039 private Element getElement(Node node)
1040 {
1041
1042 return (node.getName() != null) ? (Element) node.getReference() : document.getDocumentElement();
1043 }
1044 }
1045
1046 /***
1047 * A special implementation of the <code>FileConfiguration</code> interface that is
1048 * used internally to implement the <code>FileConfiguration</code> methods
1049 * for <code>XMLConfiguration</code>, too.
1050 */
1051 private class XMLFileConfigurationDelegate extends FileConfigurationDelegate
1052 {
1053 public void load(InputStream in) throws ConfigurationException
1054 {
1055 XMLConfiguration.this.load(in);
1056 }
1057 }
1058 }