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