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