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