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