001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.configuration;
019
020import java.io.File;
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.Reader;
024import java.io.StringReader;
025import java.io.StringWriter;
026import java.io.Writer;
027import java.net.URL;
028import java.util.ArrayList;
029import java.util.Collection;
030import java.util.Collections;
031import java.util.HashMap;
032import java.util.Iterator;
033import java.util.List;
034import java.util.Map;
035
036import javax.xml.parsers.DocumentBuilder;
037import javax.xml.parsers.DocumentBuilderFactory;
038import javax.xml.parsers.ParserConfigurationException;
039import javax.xml.transform.OutputKeys;
040import javax.xml.transform.Result;
041import javax.xml.transform.Source;
042import javax.xml.transform.Transformer;
043import javax.xml.transform.TransformerException;
044import javax.xml.transform.TransformerFactory;
045import javax.xml.transform.TransformerFactoryConfigurationError;
046import javax.xml.transform.dom.DOMSource;
047import javax.xml.transform.stream.StreamResult;
048
049import org.apache.commons.collections.CollectionUtils;
050import org.apache.commons.configuration.resolver.DefaultEntityResolver;
051import org.apache.commons.configuration.resolver.EntityRegistry;
052import org.apache.commons.configuration.tree.ConfigurationNode;
053import org.apache.commons.lang.StringUtils;
054import org.apache.commons.logging.LogFactory;
055import org.w3c.dom.Attr;
056import org.w3c.dom.CDATASection;
057import org.w3c.dom.DOMException;
058import org.w3c.dom.Document;
059import org.w3c.dom.Element;
060import org.w3c.dom.NamedNodeMap;
061import org.w3c.dom.NodeList;
062import org.w3c.dom.Text;
063import org.xml.sax.EntityResolver;
064import org.xml.sax.InputSource;
065import org.xml.sax.SAXException;
066import org.xml.sax.SAXParseException;
067import org.xml.sax.helpers.DefaultHandler;
068
069/**
070 * <p>A specialized hierarchical configuration class that is able to parse XML
071 * documents.</p>
072 *
073 * <p>The parsed document will be stored keeping its structure. The class also
074 * tries to preserve as much information from the loaded XML document as
075 * possible, including comments and processing instructions. These will be
076 * contained in documents created by the {@code save()} methods, too.</p>
077 *
078 * <p>Like other file based configuration classes this class maintains the name
079 * and path to the loaded configuration file. These properties can be altered
080 * using several setter methods, but they are not modified by {@code save()}
081 * and {@code load()} methods. If XML documents contain relative paths to
082 * other documents (e.g. to a DTD), these references are resolved based on the
083 * path set for this configuration.</p>
084 *
085 * <p>By inheriting from {@link AbstractConfiguration} this class
086 * provides some extended functionality, e.g. interpolation of property values.
087 * Like in {@link PropertiesConfiguration} property values can
088 * contain delimiter characters (the comma ',' per default) and are then split
089 * into multiple values. This works for XML attributes and text content of
090 * elements as well. The delimiter can be escaped by a backslash. As an example
091 * consider the following XML fragment:</p>
092 *
093 * <p>
094 * <pre>
095 * &lt;config&gt;
096 *   &lt;array&gt;10,20,30,40&lt;/array&gt;
097 *   &lt;scalar&gt;3\,1415&lt;/scalar&gt;
098 *   &lt;cite text="To be or not to be\, this is the question!"/&gt;
099 * &lt;/config&gt;
100 * </pre>
101 * </p>
102 * <p>Here the content of the {@code array} element will be split at
103 * the commas, so the {@code array} key will be assigned 4 values. In the
104 * {@code scalar} property and the {@code text} attribute of the
105 * {@code cite} element the comma is escaped, so that no splitting is
106 * performed.</p>
107 *
108 * <p>The configuration API allows setting multiple values for a single attribute,
109 * e.g. something like the following is legal (assuming that the default
110 * expression engine is used):
111 * <pre>
112 * XMLConfiguration config = new XMLConfiguration();
113 * config.addProperty("test.dir[@name]", "C:\\Temp\\");
114 * config.addProperty("test.dir[@name]", "D:\\Data\\");
115 * </pre></p>
116 *
117 * <p>Because in XML such a constellation is not directly supported (an attribute
118 * can appear only once for a single element), the values are concatenated to a
119 * single value. If delimiter parsing is enabled (refer to the
120 * {@link #setDelimiterParsingDisabled(boolean)} method), the
121 * current list delimiter character will be used as separator. Otherwise the
122 * pipe symbol ("|") will be used for this purpose. No matter which character is
123 * used as delimiter, it can always be escaped with a backslash. A backslash
124 * itself can also be escaped with another backslash. Consider the following
125 * example fragment from a configuration file:
126 * <pre>
127 * &lt;directories names="C:\Temp\\|D:\Data\"/&gt;
128 * </pre>
129 * Here the backslash after Temp is escaped. This is necessary because it
130 * would escape the list delimiter (the pipe symbol assuming that list delimiter
131 * parsing is disabled) otherwise. So this attribute would have two values.</p>
132 *
133 * <p>Note: You should ensure that the <em>delimiter parsing disabled</em>
134 * property is always consistent when you load and save a configuration file.
135 * Otherwise the values of properties can become corrupted.</p>
136 *
137 * <p>Whitespace in the content of XML documents is trimmed per default. In most
138 * cases this is desired. However, sometimes whitespace is indeed important and
139 * should be treated as part of the value of a property as in the following
140 * example:
141 * <pre>
142 *   &lt;indent&gt;    &lt;/indent&gt;
143 * </pre></p>
144 *
145 * <p>Per default the spaces in the {@code indent} element will be trimmed
146 * resulting in an empty element. To tell {@code XMLConfiguration} that
147 * spaces are relevant the {@code xml:space} attribute can be used, which is
148 * defined in the <a href="http://www.w3.org/TR/REC-xml/#sec-white-space">XML
149 * specification</a>. This will look as follows:
150 * <pre>
151 *   &lt;indent <strong>xml:space=&quot;preserve&quot;</strong>&gt;    &lt;/indent&gt;
152 * </pre>
153 * The value of the {@code indent} property will now contain the spaces.</p>
154 *
155 * <p>{@code XMLConfiguration} implements the {@link FileConfiguration}
156 * interface and thus provides full support for loading XML documents from
157 * different sources like files, URLs, or streams. A full description of these
158 * features can be found in the documentation of
159 * {@link AbstractFileConfiguration}.</p>
160 *
161 * <p><em>Note:</em>Configuration objects of this type can be read concurrently
162 * by multiple threads. However if one of these threads modifies the object,
163 * synchronization has to be performed manually.</p>
164 *
165 * @since commons-configuration 1.0
166 *
167 * @author J&ouml;rg Schaible
168 * @version $Id: XMLConfiguration.java 1534429 2013-10-22 00:45:36Z henning $
169 */
170public class XMLConfiguration extends AbstractHierarchicalFileConfiguration
171    implements EntityResolver, EntityRegistry
172{
173    /**
174     * The serial version UID.
175     */
176    private static final long serialVersionUID = 2453781111653383552L;
177
178    /** Constant for the default root element name. */
179    private static final String DEFAULT_ROOT_NAME = "configuration";
180
181    /** Constant for the name of the space attribute.*/
182    private static final String ATTR_SPACE = "xml:space";
183
184    /** Constant for the xml:space value for preserving whitespace.*/
185    private static final String VALUE_PRESERVE = "preserve";
186
187    /** Constant for the delimiter for multiple attribute values.*/
188    private static final char ATTR_VALUE_DELIMITER = '|';
189
190    /** Schema Langauge key for the parser */
191    private static final String JAXP_SCHEMA_LANGUAGE =
192        "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
193
194    /** Schema Language for the parser */
195    private static final String W3C_XML_SCHEMA =
196        "http://www.w3.org/2001/XMLSchema";
197
198    /** The document from this configuration's data source. */
199    private Document document;
200
201    /** Stores the name of the root element. */
202    private String rootElementName;
203
204    /** Stores the public ID from the DOCTYPE.*/
205    private String publicID;
206
207    /** Stores the system ID from the DOCTYPE.*/
208    private String systemID;
209
210    /** Stores the document builder that should be used for loading.*/
211    private DocumentBuilder documentBuilder;
212
213    /** Stores a flag whether DTD or Schema validation should be performed.*/
214    private boolean validating;
215
216    /** Stores a flag whether DTD or Schema validation is used */
217    private boolean schemaValidation;
218
219    /** A flag whether attribute splitting is disabled.*/
220    private boolean attributeSplittingDisabled;
221
222    /** The EntityResolver to use */
223    private EntityResolver entityResolver = new DefaultEntityResolver();
224
225    /**
226     * Creates a new instance of {@code XMLConfiguration}.
227     */
228    public XMLConfiguration()
229    {
230        super();
231        setLogger(LogFactory.getLog(XMLConfiguration.class));
232    }
233
234    /**
235     * Creates a new instance of {@code XMLConfiguration} and copies the
236     * content of the passed in configuration into this object. Note that only
237     * the data of the passed in configuration will be copied. If, for instance,
238     * the other configuration is a {@code XMLConfiguration}, too,
239     * things like comments or processing instructions will be lost.
240     *
241     * @param c the configuration to copy
242     * @since 1.4
243     */
244    public XMLConfiguration(HierarchicalConfiguration c)
245    {
246        super(c);
247        clearReferences(getRootNode());
248        setRootElementName(getRootNode().getName());
249        setLogger(LogFactory.getLog(XMLConfiguration.class));
250    }
251
252    /**
253     * Creates a new instance of{@code XMLConfiguration}. The
254     * configuration is loaded from the specified file
255     *
256     * @param fileName the name of the file to load
257     * @throws ConfigurationException if the file cannot be loaded
258     */
259    public XMLConfiguration(String fileName) throws ConfigurationException
260    {
261        super(fileName);
262        setLogger(LogFactory.getLog(XMLConfiguration.class));
263    }
264
265    /**
266     * Creates a new instance of {@code XMLConfiguration}.
267     * The configuration is loaded from the specified file.
268     *
269     * @param file the file
270     * @throws ConfigurationException if an error occurs while loading the file
271     */
272    public XMLConfiguration(File file) throws ConfigurationException
273    {
274        super(file);
275        setLogger(LogFactory.getLog(XMLConfiguration.class));
276    }
277
278    /**
279     * Creates a new instance of {@code XMLConfiguration}.
280     * The configuration is loaded from the specified URL.
281     *
282     * @param url the URL
283     * @throws ConfigurationException if loading causes an error
284     */
285    public XMLConfiguration(URL url) throws ConfigurationException
286    {
287        super(url);
288        setLogger(LogFactory.getLog(XMLConfiguration.class));
289    }
290
291    /**
292     * Returns the name of the root element. If this configuration was loaded
293     * from a XML document, the name of this document's root element is
294     * returned. Otherwise it is possible to set a name for the root element
295     * that will be used when this configuration is stored.
296     *
297     * @return the name of the root element
298     */
299    public String getRootElementName()
300    {
301        if (getDocument() == null)
302        {
303            return (rootElementName == null) ? DEFAULT_ROOT_NAME : rootElementName;
304        }
305        else
306        {
307            return getDocument().getDocumentElement().getNodeName();
308        }
309    }
310
311    /**
312     * Sets the name of the root element. This name is used when this
313     * configuration object is stored in an XML file. Note that setting the name
314     * of the root element works only if this configuration has been newly
315     * created. If the configuration was loaded from an XML file, the name
316     * cannot be changed and an {@code UnsupportedOperationException}
317     * exception is thrown. Whether this configuration has been loaded from an
318     * XML document or not can be found out using the {@code getDocument()}
319     * method.
320     *
321     * @param name the name of the root element
322     */
323    public void setRootElementName(String name)
324    {
325        if (getDocument() != null)
326        {
327            throw new UnsupportedOperationException("The name of the root element "
328                    + "cannot be changed when loaded from an XML document!");
329        }
330        rootElementName = name;
331        getRootNode().setName(name);
332    }
333
334    /**
335     * Returns the {@code DocumentBuilder} object that is used for
336     * loading documents. If no specific builder has been set, this method
337     * returns <b>null</b>.
338     *
339     * @return the {@code DocumentBuilder} for loading new documents
340     * @since 1.2
341     */
342    public DocumentBuilder getDocumentBuilder()
343    {
344        return documentBuilder;
345    }
346
347    /**
348     * Sets the {@code DocumentBuilder} object to be used for loading
349     * documents. This method makes it possible to specify the exact document
350     * builder. So an application can create a builder, configure it for its
351     * special needs, and then pass it to this method.
352     *
353     * @param documentBuilder the document builder to be used; if undefined, a
354     * default builder will be used
355     * @since 1.2
356     */
357    public void setDocumentBuilder(DocumentBuilder documentBuilder)
358    {
359        this.documentBuilder = documentBuilder;
360    }
361
362    /**
363     * Returns the public ID of the DOCTYPE declaration from the loaded XML
364     * document. This is <b>null</b> if no document has been loaded yet or if
365     * the document does not contain a DOCTYPE declaration with a public ID.
366     *
367     * @return the public ID
368     * @since 1.3
369     */
370    public String getPublicID()
371    {
372        return publicID;
373    }
374
375    /**
376     * Sets the public ID of the DOCTYPE declaration. When this configuration is
377     * saved, a DOCTYPE declaration will be constructed that contains this
378     * public ID.
379     *
380     * @param publicID the public ID
381     * @since 1.3
382     */
383    public void setPublicID(String publicID)
384    {
385        this.publicID = publicID;
386    }
387
388    /**
389     * Returns the system ID of the DOCTYPE declaration from the loaded XML
390     * document. This is <b>null</b> if no document has been loaded yet or if
391     * the document does not contain a DOCTYPE declaration with a system ID.
392     *
393     * @return the system ID
394     * @since 1.3
395     */
396    public String getSystemID()
397    {
398        return systemID;
399    }
400
401    /**
402     * Sets the system ID of the DOCTYPE declaration. When this configuration is
403     * saved, a DOCTYPE declaration will be constructed that contains this
404     * system ID.
405     *
406     * @param systemID the system ID
407     * @since 1.3
408     */
409    public void setSystemID(String systemID)
410    {
411        this.systemID = systemID;
412    }
413
414    /**
415     * Returns the value of the validating flag.
416     *
417     * @return the validating flag
418     * @since 1.2
419     */
420    public boolean isValidating()
421    {
422        return validating;
423    }
424
425    /**
426     * Sets the value of the validating flag. This flag determines whether
427     * DTD/Schema validation should be performed when loading XML documents. This
428     * flag is evaluated only if no custom {@code DocumentBuilder} was set.
429     *
430     * @param validating the validating flag
431     * @since 1.2
432     */
433    public void setValidating(boolean validating)
434    {
435        if (!schemaValidation)
436        {
437            this.validating = validating;
438        }
439    }
440
441
442    /**
443     * Returns the value of the schemaValidation flag.
444     *
445     * @return the schemaValidation flag
446     * @since 1.7
447     */
448    public boolean isSchemaValidation()
449    {
450        return schemaValidation;
451    }
452
453    /**
454     * Sets the value of the schemaValidation flag. This flag determines whether
455     * DTD or Schema validation should be used. This
456     * flag is evaluated only if no custom {@code DocumentBuilder} was set.
457     * If set to true the XML document must contain a schemaLocation definition
458     * that provides resolvable hints to the required schemas.
459     *
460     * @param schemaValidation the validating flag
461     * @since 1.7
462     */
463    public void setSchemaValidation(boolean schemaValidation)
464    {
465        this.schemaValidation = schemaValidation;
466        if (schemaValidation)
467        {
468            this.validating = true;
469        }
470    }
471
472    /**
473     * Sets a new EntityResolver. Setting this will cause RegisterEntityId to have no
474     * effect.
475     * @param resolver The EntityResolver to use.
476     * @since 1.7
477     */
478    public void setEntityResolver(EntityResolver resolver)
479    {
480        this.entityResolver = resolver;
481    }
482
483    /**
484     * Returns the EntityResolver.
485     * @return The EntityResolver.
486     * @since 1.7
487     */
488    public EntityResolver getEntityResolver()
489    {
490        return this.entityResolver;
491    }
492
493    /**
494     * Returns the flag whether attribute splitting is disabled.
495     *
496     * @return the flag whether attribute splitting is disabled
497     * @see #setAttributeSplittingDisabled(boolean)
498     * @since 1.6
499     */
500    public boolean isAttributeSplittingDisabled()
501    {
502        return attributeSplittingDisabled;
503    }
504
505    /**
506     * <p>
507     * Sets a flag whether attribute splitting is disabled.
508     * </p>
509     * <p>
510     * The Configuration API allows adding multiple values to an attribute. This
511     * is problematic when storing the configuration because in XML an attribute
512     * can appear only once with a single value. To solve this problem, per
513     * default multiple attribute values are concatenated using a special
514     * separator character and split again when the configuration is loaded. The
515     * separator character is either the list delimiter character (see
516     * {@link #setListDelimiter(char)}) or the pipe symbol (&quot;|&quot;) if
517     * list delimiter parsing is disabled.
518     * </p>
519     * <p>
520     * In some constellations the splitting of attribute values can have
521     * undesired effects, especially if list delimiter parsing is disabled and
522     * attributes may contain the &quot;|&quot; character. In these cases it is
523     * possible to disable the attribute splitting mechanism by calling this
524     * method with a boolean value set to <b>false</b>. If attribute splitting
525     * is disabled, the values of attributes will not be processed, but stored
526     * as configuration properties exactly as they are returned by the XML
527     * parser.
528     * </p>
529     * <p>
530     * Note that in this mode multiple attribute values cannot be handled
531     * correctly. It is possible to create a {@code XMLConfiguration}
532     * object, add multiple values to an attribute and save it. When the
533     * configuration is loaded again and attribute splitting is disabled, the
534     * attribute will only have a single value, which is the concatenation of
535     * all values set before. So it lies in the responsibility of the
536     * application to carefully set the values of attributes.
537     * </p>
538     * <p>
539     * As is true for the {@link #setDelimiterParsingDisabled(boolean)} method,
540     * this method must be called before the configuration is loaded. So it
541     * can't be used together with one of the constructors expecting the
542     * specification of the file to load. Instead the default constructor has to
543     * be used, then {@code setAttributeSplittingDisabled(false)} has to be
544     * called, and finally the configuration can be loaded using one of its
545     * {@code load()} methods.
546     * </p>
547     *
548     * @param attributeSplittingDisabled <b>true</b> for disabling attribute
549     *        splitting, <b>false</b> for enabling it
550     * @see #setDelimiterParsingDisabled(boolean)
551     * @since 1.6
552     */
553    public void setAttributeSplittingDisabled(boolean attributeSplittingDisabled)
554    {
555        this.attributeSplittingDisabled = attributeSplittingDisabled;
556    }
557
558    /**
559     * Returns the XML document this configuration was loaded from. The return
560     * value is <b>null</b> if this configuration was not loaded from a XML
561     * document.
562     *
563     * @return the XML document this configuration was loaded from
564     */
565    public Document getDocument()
566    {
567        return document;
568    }
569
570    /**
571     * Removes all properties from this configuration. If this configuration
572     * was loaded from a file, the associated DOM document is also cleared.
573     */
574    @Override
575    public void clear()
576    {
577        super.clear();
578        setRoot(new Node());
579        document = null;
580    }
581
582    /**
583     * Initializes this configuration from an XML document.
584     *
585     * @param document the document to be parsed
586     * @param elemRefs a flag whether references to the XML elements should be set
587     */
588    public void initProperties(Document document, boolean elemRefs)
589    {
590        if (document.getDoctype() != null)
591        {
592            setPublicID(document.getDoctype().getPublicId());
593            setSystemID(document.getDoctype().getSystemId());
594        }
595
596        constructHierarchy(getRoot(), document.getDocumentElement(), elemRefs, true);
597        getRootNode().setName(document.getDocumentElement().getNodeName());
598        if (elemRefs)
599        {
600            getRoot().setReference(document.getDocumentElement());
601        }
602    }
603
604    /**
605     * Helper method for building the internal storage hierarchy. The XML
606     * elements are transformed into node objects.
607     *
608     * @param node the actual node
609     * @param element the actual XML element
610     * @param elemRefs a flag whether references to the XML elements should be
611     *        set
612     * @param trim a flag whether the text content of elements should be
613     *        trimmed; this controls the whitespace handling
614     * @return a map with all attribute values extracted for the current node;
615     *         this map also contains the value of the trim flag for this node
616     *         under the key {@value #ATTR_SPACE}
617     */
618    private Map<String, Collection<String>> constructHierarchy(Node node,
619            Element element, boolean elemRefs, boolean trim)
620    {
621        boolean trimFlag = shouldTrim(element, trim);
622        Map<String, Collection<String>> attributes =
623                processAttributes(node, element, elemRefs);
624        attributes.put(ATTR_SPACE, Collections.singleton(String.valueOf(trimFlag)));
625        StringBuilder buffer = new StringBuilder();
626        NodeList list = element.getChildNodes();
627        for (int i = 0; i < list.getLength(); i++)
628        {
629            org.w3c.dom.Node w3cNode = list.item(i);
630            if (w3cNode instanceof Element)
631            {
632                Element child = (Element) w3cNode;
633                Node childNode = new XMLNode(child.getTagName(),
634                        elemRefs ? child : null);
635                Map<String, Collection<String>> attrmap =
636                        constructHierarchy(childNode, child, elemRefs, trimFlag);
637                node.addChild(childNode);
638                Collection<String> attrSpace = attrmap.remove(ATTR_SPACE);
639
640                Boolean childTrim = CollectionUtils.isEmpty(attrSpace)
641                    ? Boolean.FALSE
642                    : Boolean.valueOf(attrSpace.iterator().next());
643
644                handleDelimiters(node, childNode, childTrim.booleanValue(), attrmap);
645            }
646            else if (w3cNode instanceof Text)
647            {
648                Text data = (Text) w3cNode;
649                buffer.append(data.getData());
650            }
651        }
652
653        String text = determineValue(node, buffer.toString(), trimFlag);
654        if (text.length() > 0 || (!node.hasChildren() && node != getRoot()))
655        {
656            node.setValue(text);
657        }
658        return attributes;
659    }
660
661    /**
662     * Determines the value of a configuration node. This method mainly checks
663     * whether the text value is to be trimmed or not. This is normally defined
664     * by the trim flag. However, if the node has children and its content is
665     * only whitespace, then it makes no sense to store any value; this would
666     * only scramble layout when the configuration is saved again.
667     *
668     * @param node the current {@code ConfigurationNode}
669     * @param content the text content of this node
670     * @param trimFlag the trim flag
671     * @return the value to be stored for this node
672     */
673    private static String determineValue(ConfigurationNode node,
674            String content, boolean trimFlag)
675    {
676        boolean shouldTrim =
677                trimFlag
678                        || (StringUtils.isBlank(content) && node
679                                .getChildrenCount() > 0);
680        return shouldTrim ? content.trim() : content;
681    }
682
683    /**
684     * Helper method for constructing node objects for the attributes of the
685     * given XML element.
686     *
687     * @param node the current node
688     * @param element the actual XML element
689     * @param elemRefs a flag whether references to the XML elements should be set
690     * @return a map with all attribute values extracted for the current node
691     */
692    private Map<String, Collection<String>> processAttributes(Node node,
693            Element element, boolean elemRefs)
694    {
695        NamedNodeMap attributes = element.getAttributes();
696        Map<String, Collection<String>> attrmap = new HashMap<String, Collection<String>>();
697
698        for (int i = 0; i < attributes.getLength(); ++i)
699        {
700            org.w3c.dom.Node w3cNode = attributes.item(i);
701            if (w3cNode instanceof Attr)
702            {
703                Attr attr = (Attr) w3cNode;
704                List<String> values;
705                if (isAttributeSplittingDisabled())
706                {
707                    values = Collections.singletonList(attr.getValue());
708                }
709                else
710                {
711                    values = PropertyConverter.split(attr.getValue(),
712                            isDelimiterParsingDisabled() ? ATTR_VALUE_DELIMITER
713                                    : getListDelimiter());
714                }
715
716                appendAttributes(node, element, elemRefs, attr.getName(), values);
717                attrmap.put(attr.getName(), values);
718            }
719        }
720
721        return attrmap;
722    }
723
724    /**
725     * Adds attribute nodes to the given node. For each attribute value, a new
726     * attribute node is created and added as child to the current node.
727     *
728     * @param node the current node
729     * @param element the corresponding XML element
730     * @param attr the name of the attribute
731     * @param values the attribute values
732     */
733    private void appendAttributes(Node node, Element element, boolean elemRefs,
734            String attr, Collection<String> values)
735    {
736        for (String value : values)
737        {
738            Node child = new XMLNode(attr, elemRefs ? element : null);
739            child.setValue(value);
740            node.addAttribute(child);
741        }
742    }
743
744    /**
745     * Deals with elements whose value is a list. In this case multiple child
746     * elements must be added.
747     *
748     * @param parent the parent element
749     * @param child the child element
750     * @param trim flag whether texts of elements should be trimmed
751     * @param attrmap a map with the attributes of the current node
752     */
753    private void handleDelimiters(Node parent, Node child, boolean trim,
754            Map<String, Collection<String>> attrmap)
755    {
756        if (child.getValue() != null)
757        {
758            List<String> values;
759            if (isDelimiterParsingDisabled())
760            {
761                values = new ArrayList<String>();
762                values.add(child.getValue().toString());
763            }
764            else
765            {
766                values = PropertyConverter.split(child.getValue().toString(),
767                    getListDelimiter(), trim);
768            }
769
770            if (values.size() > 1)
771            {
772                Iterator<String> it = values.iterator();
773                // Create new node for the original child's first value
774                Node c = createNode(child.getName());
775                c.setValue(it.next());
776                // Copy original attributes to the new node
777                for (ConfigurationNode ndAttr : child.getAttributes())
778                {
779                    ndAttr.setReference(null);
780                    c.addAttribute(ndAttr);
781                }
782                parent.remove(child);
783                parent.addChild(c);
784
785                // add multiple new children
786                while (it.hasNext())
787                {
788                    c = new XMLNode(child.getName(), null);
789                    c.setValue(it.next());
790                    for (Map.Entry<String, Collection<String>> e : attrmap
791                            .entrySet())
792                    {
793                        appendAttributes(c, null, false, e.getKey(),
794                                e.getValue());
795                    }
796                    parent.addChild(c);
797                }
798            }
799            else if (values.size() == 1)
800            {
801                // we will have to replace the value because it might
802                // contain escaped delimiters
803                child.setValue(values.get(0));
804            }
805        }
806    }
807
808    /**
809     * Checks whether the content of the current XML element should be trimmed.
810     * This method checks whether a {@code xml:space} attribute is
811     * present and evaluates its value. See <a
812     * href="http://www.w3.org/TR/REC-xml/#sec-white-space">
813     * http://www.w3.org/TR/REC-xml/#sec-white-space</a> for more details.
814     *
815     * @param element the current XML element
816     * @param currentTrim the current trim flag
817     * @return a flag whether the content of this element should be trimmed
818     */
819    private boolean shouldTrim(Element element, boolean currentTrim)
820    {
821        Attr attr = element.getAttributeNode(ATTR_SPACE);
822
823        if (attr == null)
824        {
825            return currentTrim;
826        }
827        else
828        {
829            return !VALUE_PRESERVE.equals(attr.getValue());
830        }
831    }
832
833    /**
834     * Creates the {@code DocumentBuilder} to be used for loading files.
835     * This implementation checks whether a specific
836     * {@code DocumentBuilder} has been set. If this is the case, this
837     * one is used. Otherwise a default builder is created. Depending on the
838     * value of the validating flag this builder will be a validating or a non
839     * validating {@code DocumentBuilder}.
840     *
841     * @return the {@code DocumentBuilder} for loading configuration
842     * files
843     * @throws ParserConfigurationException if an error occurs
844     * @since 1.2
845     */
846    protected DocumentBuilder createDocumentBuilder()
847            throws ParserConfigurationException
848    {
849        if (getDocumentBuilder() != null)
850        {
851            return getDocumentBuilder();
852        }
853        else
854        {
855            DocumentBuilderFactory factory = DocumentBuilderFactory
856                    .newInstance();
857            if (isValidating())
858            {
859                factory.setValidating(true);
860                if (isSchemaValidation())
861                {
862                    factory.setNamespaceAware(true);
863                    factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
864                }
865            }
866
867            DocumentBuilder result = factory.newDocumentBuilder();
868            result.setEntityResolver(this.entityResolver);
869
870            if (isValidating())
871            {
872                // register an error handler which detects validation errors
873                result.setErrorHandler(new DefaultHandler()
874                {
875                    @Override
876                    public void error(SAXParseException ex) throws SAXException
877                    {
878                        throw ex;
879                    }
880                });
881            }
882            return result;
883        }
884    }
885
886    /**
887     * Creates a DOM document from the internal tree of configuration nodes.
888     *
889     * @return the new document
890     * @throws ConfigurationException if an error occurs
891     */
892    protected Document createDocument() throws ConfigurationException
893    {
894        try
895        {
896            if (document == null)
897            {
898                DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
899                Document newDocument = builder.newDocument();
900                Element rootElem = newDocument.createElement(getRootElementName());
901                newDocument.appendChild(rootElem);
902                document = newDocument;
903            }
904
905            XMLBuilderVisitor builder = new XMLBuilderVisitor(document,
906                    isDelimiterParsingDisabled() ? (char) 0 : getListDelimiter(),
907                    isAttributeSplittingDisabled());
908            builder.processDocument(getRoot());
909            initRootElementText(document, getRootNode().getValue());
910            return document;
911        }
912        catch (DOMException domEx)
913        {
914            throw new ConfigurationException(domEx);
915        }
916        catch (ParserConfigurationException pex)
917        {
918            throw new ConfigurationException(pex);
919        }
920    }
921
922    /**
923     * Sets the text of the root element of a newly created XML Document.
924     *
925     * @param doc the document
926     * @param value the new text to be set
927     */
928    private void initRootElementText(Document doc, Object value)
929    {
930        Element elem = doc.getDocumentElement();
931        NodeList children = elem.getChildNodes();
932
933        // Remove all existing text nodes
934        for (int i = 0; i < children.getLength(); i++)
935        {
936            org.w3c.dom.Node nd = children.item(i);
937            if (nd.getNodeType() == org.w3c.dom.Node.TEXT_NODE)
938            {
939                elem.removeChild(nd);
940            }
941        }
942
943        if (value != null)
944        {
945            // Add a new text node
946            elem.appendChild(doc.createTextNode(String.valueOf(value)));
947        }
948    }
949
950    /**
951     * Creates a new node object. This implementation returns an instance of the
952     * {@code XMLNode} class.
953     *
954     * @param name the node's name
955     * @return the new node
956     */
957    @Override
958    protected Node createNode(String name)
959    {
960        return new XMLNode(name, null);
961    }
962
963    /**
964     * Loads the configuration from the given input stream.
965     *
966     * @param in the input stream
967     * @throws ConfigurationException if an error occurs
968     */
969    @Override
970    public void load(InputStream in) throws ConfigurationException
971    {
972        load(new InputSource(in));
973    }
974
975    /**
976     * Load the configuration from the given reader.
977     * Note that the {@code clear()} method is not called, so
978     * the properties contained in the loaded file will be added to the
979     * actual set of properties.
980     *
981     * @param in An InputStream.
982     *
983     * @throws ConfigurationException if an error occurs
984     */
985    public void load(Reader in) throws ConfigurationException
986    {
987        load(new InputSource(in));
988    }
989
990    /**
991     * Loads a configuration file from the specified input source.
992     * @param source the input source
993     * @throws ConfigurationException if an error occurs
994     */
995    private void load(InputSource source) throws ConfigurationException
996    {
997        try
998        {
999            URL sourceURL = getDelegate().getURL();
1000            if (sourceURL != null)
1001            {
1002                source.setSystemId(sourceURL.toString());
1003            }
1004
1005            DocumentBuilder builder = createDocumentBuilder();
1006            Document newDocument = builder.parse(source);
1007            Document oldDocument = document;
1008            document = null;
1009            initProperties(newDocument, oldDocument == null);
1010            document = (oldDocument == null) ? newDocument : oldDocument;
1011        }
1012        catch (SAXParseException spe)
1013        {
1014            throw new ConfigurationException("Error parsing " + source.getSystemId(), spe);
1015        }
1016        catch (Exception e)
1017        {
1018            this.getLogger().debug("Unable to load the configuraton", e);
1019            throw new ConfigurationException("Unable to load the configuration", e);
1020        }
1021    }
1022
1023    /**
1024     * Saves the configuration to the specified writer.
1025     *
1026     * @param writer the writer used to save the configuration
1027     * @throws ConfigurationException if an error occurs
1028     */
1029    public void save(Writer writer) throws ConfigurationException
1030    {
1031        try
1032        {
1033            Transformer transformer = createTransformer();
1034            Source source = new DOMSource(createDocument());
1035            Result result = new StreamResult(writer);
1036            transformer.transform(source, result);
1037        }
1038        catch (TransformerException e)
1039        {
1040            throw new ConfigurationException("Unable to save the configuration", e);
1041        }
1042        catch (TransformerFactoryConfigurationError e)
1043        {
1044            throw new ConfigurationException("Unable to save the configuration", e);
1045        }
1046    }
1047
1048    /**
1049     * Validate the document against the Schema.
1050     * @throws ConfigurationException if the validation fails.
1051     */
1052    public void validate() throws ConfigurationException
1053    {
1054        try
1055        {
1056            Transformer transformer = createTransformer();
1057            Source source = new DOMSource(createDocument());
1058            StringWriter writer = new StringWriter();
1059            Result result = new StreamResult(writer);
1060            transformer.transform(source, result);
1061            Reader reader = new StringReader(writer.getBuffer().toString());
1062            DocumentBuilder builder = createDocumentBuilder();
1063            builder.parse(new InputSource(reader));
1064        }
1065        catch (SAXException e)
1066        {
1067            throw new ConfigurationException("Validation failed", e);
1068        }
1069        catch (IOException e)
1070        {
1071            throw new ConfigurationException("Validation failed", e);
1072        }
1073        catch (TransformerException e)
1074        {
1075            throw new ConfigurationException("Validation failed", e);
1076        }
1077        catch (ParserConfigurationException pce)
1078        {
1079            throw new ConfigurationException("Validation failed", pce);
1080        }
1081    }
1082
1083    /**
1084     * Creates and initializes the transformer used for save operations. This
1085     * base implementation initializes all of the default settings like
1086     * indention mode and the DOCTYPE. Derived classes may overload this method
1087     * if they have specific needs.
1088     *
1089     * @return the transformer to use for a save operation
1090     * @throws TransformerException if an error occurs
1091     * @since 1.3
1092     */
1093    protected Transformer createTransformer() throws TransformerException
1094    {
1095        Transformer transformer = TransformerFactory.newInstance()
1096                .newTransformer();
1097
1098        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
1099        if (getEncoding() != null)
1100        {
1101            transformer.setOutputProperty(OutputKeys.ENCODING, getEncoding());
1102        }
1103        if (getPublicID() != null)
1104        {
1105            transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC,
1106                    getPublicID());
1107        }
1108        if (getSystemID() != null)
1109        {
1110            transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,
1111                    getSystemID());
1112        }
1113
1114        return transformer;
1115    }
1116
1117    /**
1118     * Creates a copy of this object. The new configuration object will contain
1119     * the same properties as the original, but it will lose any connection to a
1120     * source document (if one exists). This is to avoid race conditions if both
1121     * the original and the copy are modified and then saved.
1122     *
1123     * @return the copy
1124     */
1125    @Override
1126    public Object clone()
1127    {
1128        XMLConfiguration copy = (XMLConfiguration) super.clone();
1129
1130        // clear document related properties
1131        copy.document = null;
1132        copy.setDelegate(copy.createDelegate());
1133        // clear all references in the nodes, too
1134        clearReferences(copy.getRootNode());
1135
1136        return copy;
1137    }
1138
1139    /**
1140     * Creates the file configuration delegate for this object. This implementation
1141     * will return an instance of a class derived from {@code FileConfigurationDelegate}
1142     * that deals with some specialties of {@code XMLConfiguration}.
1143     * @return the delegate for this object
1144     */
1145    @Override
1146    protected FileConfigurationDelegate createDelegate()
1147    {
1148        return new XMLFileConfigurationDelegate();
1149    }
1150
1151    /**
1152     * Adds a collection of nodes directly to this configuration. This
1153     * implementation ensures that the nodes to be added are of the correct node
1154     * type (they have to be converted to {@code XMLNode} if necessary).
1155     *
1156     * @param key the key where the nodes are to be added
1157     * @param nodes the collection with the new nodes
1158     * @since 1.5
1159     */
1160    @Override
1161    public void addNodes(String key, Collection<? extends ConfigurationNode> nodes)
1162    {
1163        if (nodes != null && !nodes.isEmpty())
1164        {
1165            Collection<XMLNode> xmlNodes;
1166            xmlNodes = new ArrayList<XMLNode>(nodes.size());
1167            for (ConfigurationNode node : nodes)
1168            {
1169                xmlNodes.add(convertToXMLNode(node));
1170            }
1171            super.addNodes(key, xmlNodes);
1172        }
1173        else
1174        {
1175            super.addNodes(key, nodes);
1176        }
1177    }
1178
1179    /**
1180     * Converts the specified node into a {@code XMLNode} if necessary.
1181     * This is required for nodes that are directly added, e.g. by
1182     * {@code addNodes()}. If the passed in node is already an instance
1183     * of {@code XMLNode}, it is directly returned, and conversion
1184     * stops. Otherwise a new {@code XMLNode} is created, and the
1185     * children are also converted.
1186     *
1187     * @param node the node to be converted
1188     * @return the converted node
1189     */
1190    private XMLNode convertToXMLNode(ConfigurationNode node)
1191    {
1192        if (node instanceof XMLNode)
1193        {
1194            return (XMLNode) node;
1195        }
1196
1197        XMLNode nd = (XMLNode) createNode(node.getName());
1198        nd.setValue(node.getValue());
1199        nd.setAttribute(node.isAttribute());
1200        for (ConfigurationNode child : node.getChildren())
1201        {
1202            nd.addChild(convertToXMLNode(child));
1203        }
1204        for (ConfigurationNode attr : node.getAttributes())
1205        {
1206            nd.addAttribute(convertToXMLNode(attr));
1207        }
1208        return nd;
1209    }
1210
1211    /**
1212     * <p>
1213     * Registers the specified DTD URL for the specified public identifier.
1214     * </p>
1215     * <p>
1216     * {@code XMLConfiguration} contains an internal
1217     * {@code EntityResolver} implementation. This maps
1218     * {@code PUBLICID}'s to URLs (from which the resource will be
1219     * loaded). A common use case for this method is to register local URLs
1220     * (possibly computed at runtime by a class loader) for DTDs. This allows
1221     * the performance advantage of using a local version without having to
1222     * ensure every {@code SYSTEM} URI on every processed XML document is
1223     * local. This implementation provides only basic functionality. If more
1224     * sophisticated features are required, using
1225     * {@link #setDocumentBuilder(DocumentBuilder)} to set a custom
1226     * {@code DocumentBuilder} (which also can be initialized with a
1227     * custom {@code EntityResolver}) is recommended.
1228     * </p>
1229     * <p>
1230     * <strong>Note:</strong> This method will have no effect when a custom
1231     * {@code DocumentBuilder} has been set. (Setting a custom
1232     * {@code DocumentBuilder} overrides the internal implementation.)
1233     * </p>
1234     * <p>
1235     * <strong>Note:</strong> This method must be called before the
1236     * configuration is loaded. So the default constructor of
1237     * {@code XMLConfiguration} should be used, the location of the
1238     * configuration file set, {@code registerEntityId()} called, and
1239     * finally the {@code load()} method can be invoked.
1240     * </p>
1241     *
1242     * @param publicId Public identifier of the DTD to be resolved
1243     * @param entityURL The URL to use for reading this DTD
1244     * @throws IllegalArgumentException if the public ID is undefined
1245     * @since 1.5
1246     */
1247    public void registerEntityId(String publicId, URL entityURL)
1248    {
1249        if (entityResolver instanceof EntityRegistry)
1250        {
1251            ((EntityRegistry) entityResolver).registerEntityId(publicId, entityURL);
1252        }
1253    }
1254
1255    /**
1256     * Resolves the requested external entity. This is the default
1257     * implementation of the {@code EntityResolver} interface. It checks
1258     * the passed in public ID against the registered entity IDs and uses a
1259     * local URL if possible.
1260     *
1261     * @param publicId the public identifier of the entity being referenced
1262     * @param systemId the system identifier of the entity being referenced
1263     * @return an input source for the specified entity
1264     * @throws SAXException if a parsing exception occurs
1265     * @since 1.5
1266     * @deprecated Use getEntityResolver().resolveEntity()
1267     */
1268    @Deprecated
1269    public InputSource resolveEntity(String publicId, String systemId)
1270            throws SAXException
1271    {
1272        try
1273        {
1274            return entityResolver.resolveEntity(publicId, systemId);
1275        }
1276        catch (IOException e)
1277        {
1278            throw new SAXException(e);
1279        }
1280    }
1281
1282    /**
1283     * Returns a map with the entity IDs that have been registered using the
1284     * {@code registerEntityId()} method.
1285     *
1286     * @return a map with the registered entity IDs
1287     */
1288    public Map<String, URL> getRegisteredEntities()
1289    {
1290        if (entityResolver instanceof EntityRegistry)
1291        {
1292            return ((EntityRegistry) entityResolver).getRegisteredEntities();
1293        }
1294        return new HashMap<String, URL>();
1295    }
1296
1297    /**
1298     * A specialized {@code Node} class that is connected with an XML
1299     * element. Changes on a node are also performed on the associated element.
1300     */
1301    class XMLNode extends Node
1302    {
1303        /**
1304         * The serial version UID.
1305         */
1306        private static final long serialVersionUID = -4133988932174596562L;
1307
1308        /**
1309         * Creates a new instance of {@code XMLNode} and initializes it
1310         * with a name and the corresponding XML element.
1311         *
1312         * @param name the node's name
1313         * @param elem the XML element
1314         */
1315        public XMLNode(String name, Element elem)
1316        {
1317            super(name);
1318            setReference(elem);
1319        }
1320
1321        /**
1322         * Sets the value of this node. If this node is associated with an XML
1323         * element, this element will be updated, too.
1324         *
1325         * @param value the node's new value
1326         */
1327        @Override
1328        public void setValue(Object value)
1329        {
1330            super.setValue(value);
1331
1332            if (getReference() != null && document != null)
1333            {
1334                if (isAttribute())
1335                {
1336                    updateAttribute();
1337                }
1338                else
1339                {
1340                    updateElement(value);
1341                }
1342            }
1343        }
1344
1345        /**
1346         * Updates the associated XML elements when a node is removed.
1347         */
1348        @Override
1349        protected void removeReference()
1350        {
1351            if (getReference() != null)
1352            {
1353                Element element = (Element) getReference();
1354                if (isAttribute())
1355                {
1356                    updateAttribute();
1357                }
1358                else
1359                {
1360                    org.w3c.dom.Node parentElem = element.getParentNode();
1361                    if (parentElem != null)
1362                    {
1363                        parentElem.removeChild(element);
1364                    }
1365                }
1366            }
1367        }
1368
1369        /**
1370         * Updates the node's value if it represents an element node.
1371         *
1372         * @param value the new value
1373         */
1374        private void updateElement(Object value)
1375        {
1376            Text txtNode = findTextNodeForUpdate();
1377            if (value == null)
1378            {
1379                // remove text
1380                if (txtNode != null)
1381                {
1382                    ((Element) getReference()).removeChild(txtNode);
1383                }
1384            }
1385            else
1386            {
1387                if (txtNode == null)
1388                {
1389                    String newValue = isDelimiterParsingDisabled() ? value.toString()
1390                        : PropertyConverter.escapeDelimiters(value.toString(), getListDelimiter());
1391                    txtNode = document.createTextNode(newValue);
1392                    if (((Element) getReference()).getFirstChild() != null)
1393                    {
1394                        ((Element) getReference()).insertBefore(txtNode,
1395                                ((Element) getReference()).getFirstChild());
1396                    }
1397                    else
1398                    {
1399                        ((Element) getReference()).appendChild(txtNode);
1400                    }
1401                }
1402                else
1403                {
1404                    String newValue = isDelimiterParsingDisabled() ? value.toString()
1405                        : PropertyConverter.escapeDelimiters(value.toString(), getListDelimiter());
1406                    txtNode.setNodeValue(newValue);
1407                }
1408            }
1409        }
1410
1411        /**
1412         * Updates the node's value if it represents an attribute.
1413         *
1414         */
1415        private void updateAttribute()
1416        {
1417            XMLBuilderVisitor.updateAttribute(getParent(), getName(), getListDelimiter(),
1418                    isAttributeSplittingDisabled());
1419        }
1420
1421        /**
1422         * Returns the only text node of this element for update. This method is
1423         * called when the element's text changes. Then all text nodes except
1424         * for the first are removed. A reference to the first is returned or
1425         * <b>null </b> if there is no text node at all.
1426         *
1427         * @return the first and only text node
1428         */
1429        private Text findTextNodeForUpdate()
1430        {
1431            Text result = null;
1432            Element elem = (Element) getReference();
1433            // Find all Text nodes
1434            NodeList children = elem.getChildNodes();
1435            Collection<org.w3c.dom.Node> textNodes = new ArrayList<org.w3c.dom.Node>();
1436            for (int i = 0; i < children.getLength(); i++)
1437            {
1438                org.w3c.dom.Node nd = children.item(i);
1439                if (nd instanceof Text)
1440                {
1441                    if (result == null)
1442                    {
1443                        result = (Text) nd;
1444                    }
1445                    else
1446                    {
1447                        textNodes.add(nd);
1448                    }
1449                }
1450            }
1451
1452            // We don't want CDATAs
1453            if (result instanceof CDATASection)
1454            {
1455                textNodes.add(result);
1456                result = null;
1457            }
1458
1459            // Remove all but the first Text node
1460            for (org.w3c.dom.Node tn : textNodes)
1461            {
1462                elem.removeChild(tn);
1463            }
1464            return result;
1465        }
1466    }
1467
1468    /**
1469     * A concrete {@code BuilderVisitor} that can construct XML
1470     * documents.
1471     */
1472    static class XMLBuilderVisitor extends BuilderVisitor
1473    {
1474        /** Stores the document to be constructed. */
1475        private Document document;
1476
1477        /** Stores the list delimiter.*/
1478        private final char listDelimiter;
1479
1480        /** True if attributes should not be split */
1481        private boolean isAttributeSplittingDisabled;
1482
1483        /**
1484         * Creates a new instance of {@code XMLBuilderVisitor}.
1485         *
1486         * @param doc the document to be created
1487         * @param listDelimiter the delimiter for attribute properties with multiple values
1488         * @param isAttributeSplittingDisabled true if attribute splitting is disabled.
1489         */
1490        public XMLBuilderVisitor(Document doc, char listDelimiter, boolean isAttributeSplittingDisabled)
1491        {
1492            document = doc;
1493            this.listDelimiter = listDelimiter;
1494            this.isAttributeSplittingDisabled = isAttributeSplittingDisabled;
1495        }
1496
1497        /**
1498         * Processes the node hierarchy and adds new nodes to the document.
1499         *
1500         * @param rootNode the root node
1501         */
1502        public void processDocument(Node rootNode)
1503        {
1504            rootNode.visit(this, null);
1505        }
1506
1507        /**
1508         * Inserts a new node. This implementation ensures that the correct
1509         * XML element is created and inserted between the given siblings.
1510         *
1511         * @param newNode the node to insert
1512         * @param parent the parent node
1513         * @param sibling1 the first sibling
1514         * @param sibling2 the second sibling
1515         * @return the new node
1516         */
1517        @Override
1518        protected Object insert(Node newNode, Node parent, Node sibling1, Node sibling2)
1519        {
1520            if (newNode.isAttribute())
1521            {
1522                updateAttribute(parent, getElement(parent), newNode.getName(), listDelimiter,
1523                    isAttributeSplittingDisabled);
1524                return null;
1525            }
1526
1527            else
1528            {
1529                Element elem = document.createElement(newNode.getName());
1530                if (newNode.getValue() != null)
1531                {
1532                    String txt = newNode.getValue().toString();
1533                    if (listDelimiter != 0)
1534                    {
1535                        txt = PropertyConverter.escapeListDelimiter(txt, listDelimiter);
1536                    }
1537                    elem.appendChild(document.createTextNode(txt));
1538                }
1539                if (sibling2 == null)
1540                {
1541                    getElement(parent).appendChild(elem);
1542                }
1543                else if (sibling1 != null)
1544                {
1545                    getElement(parent).insertBefore(elem, getElement(sibling1).getNextSibling());
1546                }
1547                else
1548                {
1549                    getElement(parent).insertBefore(elem, getElement(parent).getFirstChild());
1550                }
1551                return elem;
1552            }
1553        }
1554
1555        /**
1556         * Helper method for updating the value of the specified node's
1557         * attribute with the given name.
1558         *
1559         * @param node the affected node
1560         * @param elem the element that is associated with this node
1561         * @param name the name of the affected attribute
1562         * @param listDelimiter the delimiter for attributes with multiple values
1563         * @param isAttributeSplittingDisabled true if attribute splitting is disabled.
1564         */
1565        private static void updateAttribute(Node node, Element elem, String name, char listDelimiter,
1566                                            boolean isAttributeSplittingDisabled)
1567        {
1568            if (node != null && elem != null)
1569            {
1570                boolean hasAttribute = false;
1571                List<ConfigurationNode> attrs = node.getAttributes(name);
1572                StringBuilder buf = new StringBuilder();
1573                char delimiter = (listDelimiter != 0) ? listDelimiter : ATTR_VALUE_DELIMITER;
1574                for (ConfigurationNode attr : attrs)
1575                {
1576                    if (attr.getValue() != null)
1577                    {
1578                        hasAttribute = true;
1579                        if (buf.length() > 0)
1580                        {
1581                            buf.append(delimiter);
1582                        }
1583                        String value = isAttributeSplittingDisabled ? attr.getValue().toString()
1584                            : PropertyConverter.escapeDelimiters(attr.getValue().toString(),
1585                                    delimiter);
1586                        buf.append(value);
1587                    }
1588                    attr.setReference(elem);
1589                }
1590
1591                if (!hasAttribute)
1592                {
1593                    elem.removeAttribute(name);
1594                }
1595                else
1596                {
1597                    elem.setAttribute(name, buf.toString());
1598                }
1599            }
1600        }
1601
1602        /**
1603         * Updates the value of the specified attribute of the given node.
1604         * Because there can be multiple child nodes representing this attribute
1605         * the new value is determined by iterating over all those child nodes.
1606         *
1607         * @param node the affected node
1608         * @param name the name of the attribute
1609         * @param listDelimiter the delimiter for attributes with multiple values
1610         * @param isAttributeSplittingDisabled true if attributes splitting is disabled.
1611         */
1612        static void updateAttribute(Node node, String name, char listDelimiter,
1613                                    boolean isAttributeSplittingDisabled)
1614        {
1615            if (node != null)
1616            {
1617                updateAttribute(node, (Element) node.getReference(), name, listDelimiter,
1618                        isAttributeSplittingDisabled);
1619            }
1620        }
1621
1622        /**
1623         * Helper method for accessing the element of the specified node.
1624         *
1625         * @param node the node
1626         * @return the element of this node
1627         */
1628        private Element getElement(Node node)
1629        {
1630            // special treatment for root node of the hierarchy
1631            return (node.getName() != null && node.getReference() != null) ? (Element) node
1632                    .getReference()
1633                    : document.getDocumentElement();
1634        }
1635    }
1636
1637    /**
1638     * A special implementation of the {@code FileConfiguration} interface that is
1639     * used internally to implement the {@code FileConfiguration} methods
1640     * for {@code XMLConfiguration}, too.
1641     */
1642    private class XMLFileConfigurationDelegate extends FileConfigurationDelegate
1643    {
1644        @Override
1645        public void load(InputStream in) throws ConfigurationException
1646        {
1647            XMLConfiguration.this.load(in);
1648        }
1649    }
1650}