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