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