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