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 }