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