View Javadoc

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