[commons-configuration] 01/02: Imported Upstream version 1.10

Tony Mancill tmancill at moszumanska.debian.org
Thu Sep 3 04:48:34 UTC 2015


This is an automated email from the git hooks/post-receive script.

tmancill pushed a commit to branch master
in repository commons-configuration.

commit 59b61032b0392e3a2449290a30718349672474bb
Author: tony mancill <tmancill at debian.org>
Date:   Wed Sep 2 21:47:14 2015 -0700

    Imported Upstream version 1.10
---
 LICENSE.txt                                        |  202 ++
 NOTICE.txt                                         |    5 +
 RELEASE-NOTES.txt                                  |  100 +
 conf/CommonsConfiguration.xsd                      |  129 ++
 conf/HEADER.txt                                    |   16 +
 conf/README                                        |    4 +
 conf/checkstyle-suppressions.xml                   |   33 +
 conf/checkstyle.xml                                |  188 ++
 conf/findbugs-exclude-filter.xml                   |   31 +
 pom.xml                                            |  681 ++++++
 src/changes/changes.xml                            | 1805 +++++++++++++++
 src/main/assembly/bin.xml                          |   45 +
 src/main/assembly/src.xml                          |   40 +
 .../configuration/AbstractConfiguration.java       | 1309 +++++++++++
 .../configuration/AbstractFileConfiguration.java   | 1092 +++++++++
 .../AbstractHierarchicalFileConfiguration.java     |  579 +++++
 .../commons/configuration/BaseConfiguration.java   |  198 ++
 .../configuration/BaseConfigurationXMLReader.java  |  137 ++
 .../configuration/CombinedConfiguration.java       |  976 ++++++++
 .../configuration/CompositeConfiguration.java      |  578 +++++
 .../commons/configuration/Configuration.java       |  598 +++++
 .../configuration/ConfigurationBuilder.java        |   46 +
 .../configuration/ConfigurationComparator.java     |   40 +
 .../configuration/ConfigurationConverter.java      |  133 ++
 .../configuration/ConfigurationException.java      |   78 +
 .../configuration/ConfigurationFactory.java        |  903 ++++++++
 .../commons/configuration/ConfigurationKey.java    |  687 ++++++
 .../commons/configuration/ConfigurationMap.java    |  210 ++
 .../ConfigurationRuntimeException.java             |   79 +
 .../commons/configuration/ConfigurationUtils.java  |  759 +++++++
 .../configuration/ConfigurationXMLReader.java      |  364 +++
 .../commons/configuration/ConversionException.java |   77 +
 .../commons/configuration/DataConfiguration.java   | 1979 ++++++++++++++++
 .../configuration/DatabaseConfiguration.java       |  709 ++++++
 .../configuration/DefaultConfigurationBuilder.java | 1800 +++++++++++++++
 .../commons/configuration/DefaultFileSystem.java   |  373 +++
 .../DynamicCombinedConfiguration.java              |  935 ++++++++
 .../configuration/EnvironmentConfiguration.java    |   91 +
 .../commons/configuration/FileConfiguration.java   |  293 +++
 .../commons/configuration/FileOptionsProvider.java |   75 +
 .../apache/commons/configuration/FileSystem.java   |  181 ++
 .../commons/configuration/FileSystemBased.java     |   32 +
 .../configuration/HierarchicalConfiguration.java   | 1769 ++++++++++++++
 .../HierarchicalConfigurationConverter.java        |  203 ++
 .../HierarchicalConfigurationXMLReader.java        |  211 ++
 .../HierarchicalINIConfiguration.java              |  884 +++++++
 .../HierarchicalReloadableConfiguration.java       |   81 +
 .../HierarchicalXMLConfiguration.java              |   42 +
 .../commons/configuration/INIConfiguration.java    |  493 ++++
 .../commons/configuration/JNDIConfiguration.java   |  482 ++++
 .../org/apache/commons/configuration/Lock.java     |   84 +
 .../commons/configuration/MapConfiguration.java    |  295 +++
 .../MultiFileHierarchicalConfiguration.java        |  871 +++++++
 .../PatternSubtreeConfigurationWrapper.java        |  541 +++++
 .../configuration/PrefixedKeysIterator.java        |  125 +
 .../configuration/PropertiesConfiguration.java     | 1516 ++++++++++++
 .../PropertiesConfigurationLayout.java             | 1064 +++++++++
 .../commons/configuration/PropertyConverter.java   | 1062 +++++++++
 .../StrictConfigurationComparator.java             |   82 +
 .../configuration/SubnodeConfiguration.java        |  347 +++
 .../commons/configuration/SubsetConfiguration.java |  386 ++++
 .../commons/configuration/SystemConfiguration.java |   97 +
 .../commons/configuration/VFSFileSystem.java       |  409 ++++
 .../configuration/VerifiableOutputStream.java      |   31 +
 .../commons/configuration/XMLConfiguration.java    | 1650 ++++++++++++++
 .../configuration/XMLPropertiesConfiguration.java  |  425 ++++
 .../configuration/beanutils/BeanDeclaration.java   |  102 +
 .../configuration/beanutils/BeanFactory.java       |   78 +
 .../configuration/beanutils/BeanHelper.java        |  521 +++++
 .../beanutils/ConfigurationDynaBean.java           |  244 ++
 .../beanutils/ConfigurationDynaClass.java          |  156 ++
 .../beanutils/DefaultBeanFactory.java              |  110 +
 .../beanutils/XMLBeanDeclaration.java              |  428 ++++
 .../commons/configuration/beanutils/package.html   |   35 +
 .../event/ConfigurationErrorEvent.java             |   94 +
 .../event/ConfigurationErrorListener.java          |   51 +
 .../configuration/event/ConfigurationEvent.java    |  159 ++
 .../configuration/event/ConfigurationListener.java |   45 +
 .../commons/configuration/event/EventSource.java   |  364 +++
 .../commons/configuration/event/package.html       |   32 +
 .../interpol/ConfigurationInterpolator.java        |  389 ++++
 .../configuration/interpol/ConstantLookup.java     |  172 ++
 .../configuration/interpol/EnvironmentLookup.java  |   65 +
 .../commons/configuration/interpol/ExprLookup.java |  334 +++
 .../commons/configuration/interpol/package.html    |   31 +
 .../org/apache/commons/configuration/package.html  |   33 +
 .../plist/PropertyListConfiguration.java           |  713 ++++++
 .../plist/XMLPropertyListConfiguration.java        |  804 +++++++
 .../commons/configuration/plist/package.html       |   31 +
 .../reloading/FileChangedReloadingStrategy.java    |  226 ++
 .../reloading/InvariantReloadingStrategy.java      |   47 +
 .../reloading/ManagedReloadingStrategy.java        |   94 +
 .../reloading/ManagedReloadingStrategyMBean.java   |   32 +
 .../configuration/reloading/Reloadable.java        |   31 +
 .../configuration/reloading/ReloadingStrategy.java |   55 +
 .../reloading/VFSFileChangedReloadingStrategy.java |  208 ++
 .../commons/configuration/reloading/package.html   |   34 +
 .../configuration/resolver/CatalogResolver.java    |  521 +++++
 .../resolver/DefaultEntityResolver.java            |  130 ++
 .../configuration/resolver/EntityRegistry.java     |   63 +
 .../resolver/EntityResolverSupport.java            |   41 +
 .../commons/configuration/resolver/package.html    |   31 +
 .../configuration/tree/ConfigurationNode.java      |  269 +++
 .../tree/ConfigurationNodeVisitor.java             |   67 +
 .../tree/ConfigurationNodeVisitorAdapter.java      |   65 +
 .../tree/DefaultConfigurationKey.java              |  876 +++++++
 .../tree/DefaultConfigurationNode.java             |  717 ++++++
 .../tree/DefaultExpressionEngine.java              |  545 +++++
 .../configuration/tree/ExpressionEngine.java       |   92 +
 .../commons/configuration/tree/MergeCombiner.java  |  164 ++
 .../commons/configuration/tree/NodeAddData.java    |  196 ++
 .../commons/configuration/tree/NodeCombiner.java   |  124 +
 .../configuration/tree/OverrideCombiner.java       |  153 ++
 .../commons/configuration/tree/TreeUtils.java      |   82 +
 .../commons/configuration/tree/UnionCombiner.java  |  211 ++
 .../commons/configuration/tree/ViewNode.java       |  119 +
 .../apache/commons/configuration/tree/package.html |   31 +
 .../xpath/ConfigurationNodeIteratorAttribute.java  |   80 +
 .../tree/xpath/ConfigurationNodeIteratorBase.java  |  197 ++
 .../xpath/ConfigurationNodeIteratorChildren.java   |  144 ++
 .../tree/xpath/ConfigurationNodePointer.java       |  270 +++
 .../xpath/ConfigurationNodePointerFactory.java     |   89 +
 .../tree/xpath/XPathExpressionEngine.java          |  426 ++++
 .../commons/configuration/tree/xpath/package.html  |   32 +
 .../configuration/web/AppletConfiguration.java     |   65 +
 .../configuration/web/BaseWebConfiguration.java    |  113 +
 .../configuration/web/ServletConfiguration.java    |   75 +
 .../web/ServletContextConfiguration.java           |   76 +
 .../web/ServletFilterConfiguration.java            |   63 +
 .../web/ServletRequestConfiguration.java           |   91 +
 .../apache/commons/configuration/web/package.html  |   32 +
 src/main/javacc/PropertyListParser.jj              |  291 +++
 src/main/resources/PropertyList-1.0.dtd            |   35 +
 src/main/resources/digesterRules.xml               |   42 +
 src/main/resources/properties.dtd                  |   29 +
 src/site/resources/images/logo.png                 |  Bin 0 -> 15567 bytes
 src/site/resources/images/logo.xcf                 |  Bin 0 -> 27319 bytes
 src/site/resources/profile.cobertura               |    0
 src/site/site.xml                                  |   42 +
 src/site/xdoc/building.xml                         |  120 +
 src/site/xdoc/dependencies.xml                     |  173 ++
 src/site/xdoc/download_configuration.xml           |  138 ++
 src/site/xdoc/index.xml                            |  101 +
 src/site/xdoc/issue-tracking.xml                   |  102 +
 src/site/xdoc/javadocarchive.xml                   |   46 +
 src/site/xdoc/mail-lists.xml                       |  202 ++
 src/site/xdoc/userguide/howto_basicfeatures.xml    |  361 +++
 src/site/xdoc/userguide/howto_beans.xml            |  358 +++
 .../xdoc/userguide/howto_combinedconfiguration.xml |  758 ++++++
 .../userguide/howto_compositeconfiguration.xml     |  101 +
 .../xdoc/userguide/howto_configurationbuilder.xml  |  894 ++++++++
 src/site/xdoc/userguide/howto_events.xml           |  240 ++
 src/site/xdoc/userguide/howto_filebased.xml        |  244 ++
 src/site/xdoc/userguide/howto_filesystems.xml      |  131 ++
 src/site/xdoc/userguide/howto_multitenant.xml      |  171 ++
 src/site/xdoc/userguide/howto_properties.xml       |  445 ++++
 src/site/xdoc/userguide/howto_utilities.xml        |  279 +++
 src/site/xdoc/userguide/howto_xml.xml              | 1163 ++++++++++
 src/site/xdoc/userguide/overview.xml               |  213 ++
 src/site/xdoc/userguide/user_guide.xml             |  148 ++
 .../configuration/BaseNonStringProperties.java     |  191 ++
 .../commons/configuration/ConfigurationAssert.java |  136 ++
 .../ConfigurationErrorListenerImpl.java            |  104 +
 .../DatabaseConfigurationTestHelper.java           |  230 ++
 .../configuration/FileURLStreamHandler.java        |   64 +
 .../configuration/InterpolationTestHelper.java     |  257 +++
 .../org/apache/commons/configuration/Logging.java  |  271 +++
 .../configuration/MockInitialContextFactory.java   |  241 ++
 .../configuration/NonCloneableConfiguration.java   |   70 +
 .../commons/configuration/NonStringTestHolder.java |  160 ++
 .../configuration/TestAbstractConfiguration.java   |  195 ++
 .../TestAbstractConfigurationBasicFeatures.java    |  529 +++++
 .../configuration/TestBaseConfiguration.java       |  733 ++++++
 .../TestBaseConfigurationXMLReader.java            |  161 ++
 .../configuration/TestBaseNullConfiguration.java   |  434 ++++
 .../commons/configuration/TestCatalogResolver.java |  106 +
 .../configuration/TestCombinedConfiguration.java   |  999 ++++++++
 .../configuration/TestCompositeConfiguration.java  |  931 ++++++++
 ...tCompositeConfigurationNonStringProperties.java |   41 +
 .../commons/configuration/TestConfiguration.java   |   46 +
 .../configuration/TestConfigurationConverter.java  |  141 ++
 .../configuration/TestConfigurationFactory.java    |  407 ++++
 .../configuration/TestConfigurationKey.java        |  262 +++
 .../configuration/TestConfigurationMap.java        |   92 +
 .../configuration/TestConfigurationSet.java        |  104 +
 .../configuration/TestConfigurationUtils.java      |  459 ++++
 .../configuration/TestDataConfiguration.java       | 2402 ++++++++++++++++++++
 .../configuration/TestDatabaseConfiguration.java   |  551 +++++
 .../TestDefaultConfigurationBuilder.java           | 1279 +++++++++++
 .../TestDynamicCombinedConfiguration.java          |  371 +++
 .../TestEnvironmentConfiguration.java              |  103 +
 .../commons/configuration/TestEqualBehaviour.java  |  258 +++
 .../commons/configuration/TestEqualsProperty.java  |   42 +
 .../configuration/TestFileConfiguration.java       |  652 ++++++
 .../TestHierarchicalConfiguration.java             | 1252 ++++++++++
 .../TestHierarchicalConfigurationXMLReader.java    |   77 +
 .../TestHierarchicalINIConfiguration.java          |  971 ++++++++
 .../TestHierarchicalXMLConfiguration.java          |  296 +++
 .../configuration/TestINIConfiguration.java        |  256 +++
 .../configuration/TestJNDIConfiguration.java       |  375 +++
 .../configuration/TestJNDIEnvironmentValues.java   |  152 ++
 .../configuration/TestMapConfiguration.java        |  159 ++
 .../TestMapConfigurationRegression.java            |   39 +
 .../TestMultiFileHierarchicalConfiguration.java    |  316 +++
 .../configuration/TestNonStringProperties.java     |   38 +
 .../TestNullCompositeConfiguration.java            |  451 ++++
 .../TestNullJNDIEnvironmentValues.java             |  151 ++
 .../TestPatternSubtreeConfiguration.java           |   71 +
 .../configuration/TestPropertiesConfiguration.java | 1359 +++++++++++
 .../TestPropertiesConfigurationLayout.java         |  845 +++++++
 .../configuration/TestPropertiesSequence.java      |  146 ++
 .../configuration/TestPropertyConverter.java       |  390 ++++
 .../TestStrictConfigurationComparator.java         |   95 +
 .../configuration/TestSubnodeConfiguration.java    |  625 +++++
 .../configuration/TestSubsetConfiguration.java     |  352 +++
 .../configuration/TestSystemConfiguration.java     |   52 +
 .../TestSystemConfigurationRegression.java         |   35 +
 .../configuration/TestThreesomeConfiguration.java  |   67 +
 .../configuration/TestVFSConfigurationBuilder.java | 1200 ++++++++++
 .../TestWebdavConfigurationBuilder.java            | 1059 +++++++++
 .../configuration/TestXMLConfiguration.java        | 1948 ++++++++++++++++
 .../TestXMLPropertiesConfiguration.java            |  146 ++
 .../beanutils/BeanCreationTestBean.java            |   61 +
 .../BeanCreationTestBeanWithListChild.java         |   66 +
 .../configuration/beanutils/TestBeanHelper.java    |  613 +++++
 .../beanutils/TestConfigurationDynaBean.java       |  734 ++++++
 .../TestConfigurationDynaBeanXMLConfig.java        |   45 +
 .../beanutils/TestDefaultBeanFactory.java          |  108 +
 .../beanutils/TestXMLBeanDeclaration.java          |  425 ++++
 .../event/AbstractTestConfigurationEvents.java     |  189 ++
 .../event/AbstractTestFileConfigurationEvents.java |  148 ++
 .../event/ConfigurationListenerTestImpl.java       |  131 ++
 .../event/TestDatabaseConfigurationEvents.java     |   56 +
 .../configuration/event/TestEventSource.java       |  394 ++++
 .../event/TestHierarchicalConfigurationEvents.java |  133 ++
 .../event/TestMapConfigurationEvents.java          |   36 +
 .../event/TestPropertiesConfigurationEvents.java   |   50 +
 .../event/TestSubsetConfigurationEvents.java       |   37 +
 .../event/TestXMLConfigurationEvents.java          |   48 +
 .../interpol/TestConfigurationInterpolator.java    |  351 +++
 .../configuration/interpol/TestConstantLookup.java |  151 ++
 .../interpol/TestEnvironmentLookup.java            |   71 +
 .../configuration/interpol/TestExprLookup.java     |   92 +
 .../plist/AbstractTestPListEvents.java             |   67 +
 .../plist/TestPropertyListConfiguration.java       |  414 ++++
 .../plist/TestPropertyListConfigurationEvents.java |   49 +
 .../plist/TestPropertyListParser.java              |   81 +
 .../plist/TestXMLPropertyListConfiguration.java    |  409 ++++
 .../TestXMLPropertyListConfigurationEvents.java    |   48 +
 .../reloading/FileAlwaysReloadingStrategy.java     |   51 +
 .../reloading/FileRandomReloadingStrategy.java     |   86 +
 .../TestFileChangedReloadingStrategy.java          |  228 ++
 .../reloading/TestManagedReloadingStrategy.java    |   68 +
 .../TestVFSFileChangedReloadingStrategy.java       |  159 ++
 .../apache/commons/configuration/test/HsqlDB.java  |  123 +
 .../configuration/tree/AbstractCombinerTest.java   |   95 +
 .../tree/TestDefaultConfigurationKey.java          |  516 +++++
 .../tree/TestDefaultConfigurationNode.java         |  507 +++++
 .../tree/TestDefaultExpressionEngine.java          |  539 +++++
 .../configuration/tree/TestMergeCombiner.java      |  184 ++
 .../configuration/tree/TestNodeAddData.java        |  107 +
 .../configuration/tree/TestOverrideCombiner.java   |  178 ++
 .../configuration/tree/TestUnionCombiner.java      |  136 ++
 .../commons/configuration/tree/TestViewNode.java   |  144 ++
 .../tree/xpath/AbstractXPathTest.java              |  156 ++
 .../xpath/TestConfigurationIteratorAttributes.java |  103 +
 .../TestConfigurationNodeIteratorChildren.java     |  247 ++
 .../tree/xpath/TestConfigurationNodePointer.java   |  191 ++
 .../xpath/TestConfigurationNodePointerFactory.java |  178 ++
 .../tree/xpath/TestXPathExpressionEngine.java      |  449 ++++
 .../xpath/TestXPathExpressionEngineInConfig.java   |  118 +
 .../configuration/web/TestAppletConfiguration.java |  157 ++
 .../web/TestServletConfiguration.java              |   82 +
 .../web/TestServletContextConfiguration.java       |  119 +
 .../web/TestServletFilterConfiguration.java        |   99 +
 .../web/TestServletRequestConfiguration.java       |  146 ++
 .../resources/01/testMultiConfiguration_1001.xml   |   39 +
 src/test/resources/catalog.xml                     |   25 +
 src/test/resources/catalog2.xml                    |   26 +
 .../resources/config/deep/deepinclude.properties   |   15 +
 src/test/resources/config/deep/deeptest.properties |   17 +
 .../config/deep/deeptestinvalid.properties         |   19 +
 src/test/resources/config/deep/test.properties     |   15 +
 .../resources/config/deep/testEqualDeep.properties |   26 +
 .../config/deep/testFileFromClasspath.xml          |   23 +
 src/test/resources/config/test.properties          |   25 +
 src/test/resources/configA.xml                     |   19 +
 src/test/resources/configB.xml                     |   19 +
 src/test/resources/dataset.xml                     |   76 +
 src/test/resources/include-interpol.properties     |   18 +
 src/test/resources/include.properties              |   19 +
 src/test/resources/jndi.properties                 |   17 +
 src/test/resources/log4j-test.xml                  |   33 +
 src/test/resources/resolver.dtd                    |   19 +
 src/test/resources/sample.xml                      |   28 +
 src/test/resources/sample.xsd                      |   38 +
 src/test/resources/sample_1001.xml                 |   24 +
 src/test/resources/test.ini                        |   17 +
 src/test/resources/test.plist                      |   60 +
 src/test/resources/test.plist.xml                  |  103 +
 src/test/resources/test.properties                 |  126 +
 src/test/resources/test.properties.xml             |   24 +
 src/test/resources/test.xml                        |  119 +
 src/test/resources/test2.plist.xml                 |   29 +
 src/test/resources/test2.properties                |   59 +
 src/test/resources/testClasspath.properties        |   67 +
 src/test/resources/testComplexInitialization.xml   |   50 +
 .../testConfigurationInterpolatorUpdate.xml        |   54 +
 src/test/resources/testConfigurationProvider.xml   |   53 +
 .../resources/testConfigurationXMLDocument.xml     |   26 +
 src/test/resources/testDigesterBadXML.xml          |   26 +
 src/test/resources/testDigesterConfiguration.xml   |   23 +
 src/test/resources/testDigesterConfiguration2.xml  |   31 +
 src/test/resources/testDigesterConfiguration3.xml  |   30 +
 .../testDigesterConfigurationBasePath.xml          |   27 +
 .../testDigesterConfigurationInclude1.xml          |   53 +
 .../testDigesterConfigurationInclude2.properties   |   22 +
 .../testDigesterConfigurationNamespaceAware.xml    |   23 +
 .../testDigesterConfigurationOverwrite.properties  |   21 +
 .../testDigesterConfigurationReverseOrder.xml      |   22 +
 .../testDigesterConfigurationSysProps.xml          |   23 +
 .../testDigesterConfigurationWithProps.xml         |   21 +
 src/test/resources/testDigesterCreateObject.xml    |   26 +
 .../testDigesterOptionalConfiguration.xml          |   33 +
 .../testDigesterOptionalConfigurationEx.xml        |   26 +
 src/test/resources/testDtd.xml                     |   23 +
 src/test/resources/testEncoding.xml                |  Bin 0 -> 1818 bytes
 src/test/resources/testEqual.properties            |   26 +
 src/test/resources/testEqualDigester.xml           |   21 +
 src/test/resources/testExpression.xml              |   56 +
 src/test/resources/testExtendedClass.xml           |   50 +
 .../testExtendedXMLConfigurationProvider.xml       |   54 +
 .../resources/testFactoryPropertiesInclude.xml     |   24 +
 .../testFileReloadConfigurationBuilder.xml         |   51 +
 .../testFileReloadConfigurationBuilder2.xml        |   51 +
 src/test/resources/testFileSystem.xml              |   54 +
 src/test/resources/testGlobalLookup.xml            |   53 +
 .../resources/testHierarchicalXMLConfiguration.xml |   72 +
 .../testHierarchicalXMLConfiguration2.xml          |   47 +
 src/test/resources/testInterpolation.properties    |   22 +
 src/test/resources/testInterpolation.xml           |   31 +
 src/test/resources/testInterpolationBuilder.xml    |   31 +
 src/test/resources/testMultiConfiguration.xsd      |  113 +
 src/test/resources/testMultiConfiguration_1001.xml |   39 +
 src/test/resources/testMultiConfiguration_1002.xml |   31 +
 src/test/resources/testMultiConfiguration_1003.xml |   31 +
 src/test/resources/testMultiConfiguration_1004.xml |   26 +
 src/test/resources/testMultiConfiguration_2001.xml |   28 +
 src/test/resources/testMultiConfiguration_2002.xml |   33 +
 src/test/resources/testMultiConfiguration_3001.xml |   40 +
 src/test/resources/testMultiConfiguration_3002.xml |   32 +
 .../resources/testMultiConfiguration_default.xml   |   49 +
 src/test/resources/testMultiDynamic_default.xml    |   56 +
 src/test/resources/testMultiDynamic_default2.xml   |   57 +
 .../testMultiTenentConfigurationBuilder.xml        |   46 +
 .../testMultiTenentConfigurationBuilder2.xml       |   51 +
 .../testMultiTenentConfigurationBuilder3.xml       |   51 +
 .../testMultiTenentConfigurationBuilder4.xml       |   55 +
 .../testMultiTenentConfigurationBuilder5.xml       |   51 +
 src/test/resources/testPatternSubtreeConfig.xml    |   61 +
 src/test/resources/testResolver.xml                |   22 +
 src/test/resources/testSequence.properties         |   23 +
 src/test/resources/testSequenceDigester.xml        |   21 +
 src/test/resources/testSystemProperties.xml        |   32 +
 .../testVFSMultiTenentConfigurationBuilder1.xml    |   52 +
 .../testVFSMultiTenentConfigurationBuilder2.xml    |   52 +
 src/test/resources/testValidateInvalid.xml         |   48 +
 src/test/resources/testValidateValid.xml           |   46 +
 src/test/resources/testValidation.xml              |   32 +
 src/test/resources/testValidation2.xml             |   32 +
 src/test/resources/testValidation3.xml             |   41 +
 src/test/resources/test_invalid_date.plist.xml     |   32 +
 src/test/resources/testcombine1.xml                |   79 +
 src/test/resources/testcombine2.xml                |   78 +
 src/test/resources/testdb.script                   |   95 +
 src/test/resources/threesome.properties            |   22 +
 376 files changed, 90610 insertions(+)

diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/NOTICE.txt b/NOTICE.txt
new file mode 100644
index 0000000..2f528f9
--- /dev/null
+++ b/NOTICE.txt
@@ -0,0 +1,5 @@
+Apache Commons Configuration
+Copyright 2001-2013 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
new file mode 100644
index 0000000..d728742
--- /dev/null
+++ b/RELEASE-NOTES.txt
@@ -0,0 +1,100 @@
+$Id: RELEASE-NOTES.txt 1534410 2013-10-21 23:13:34Z henning $
+
+                           Commons Configuration Package
+                               Version 1.10
+                               Release Notes
+
+
+INTRODUCTION
+============
+
+This document contains the release notes for this version of the Commons
+Configuration component. It describes the changes since the previous version.
+The Commons Configuration software library provides a generic configuration
+interface which enables an application to read configuration data from a variety
+of sources.
+
+The 1.10 release contains a couple of minor bug fixes and improvements. There
+are no important new features. The idea is to release the current changes which
+have been applied to the 1.x branch, so that we can start with new development
+on an improved (and partly binary incompatible) 2.0 version.
+
+As there are small changes only, Commons Configuration 1.10 is fully binary
+compatible to the previous version. The minimum required Java version is 1.5.
+
+Please note that this release was compiled with the Java 1.6 compiler in 1.5
+mode. There is a very small chance that this introduced incompatibilities with
+the Java 1.5 runtime. Java 1.5 was EOLed in October 2009.
+
+Following is a complete list of all changes in the new 1.10 release:
+
+BUG FIXES IN 1.10
+=================
+
+* [CONFIGURATION-500] Attributes in xml config should apply to all entries of a list
+
+  XMLConfiguration now adds attributes of elements defining a list to
+  all list nodes.
+
+* [CONFIGURATION-546] ClassCastException in BeanHelper constructing beans with a list
+                     of child beans.
+
+  BeanHelper can now process BeanDefinitions initializing properties of
+  collection types of their target beans.
+
+* [CONFIGURATION-555] XMLConfiguration doesn't seem to be preserving whitespace
+                      for the current node where xml:space="preserve" is set.
+
+  Fixed a bug in the handling of the xml:space attribute in XMLConfiguration.
+  The attribute is now also applied to the current element, not only to sub elements.
+
+* [CONFIGURATION-556] Regression with SystemProperties in 1.8 and 1.9
+
+  In 1.7 and before, any change to the system properties was immediately reflected in a
+  SystemConfiguration object. This behaviour broke in 1.8 and 1.9. This has been fixed
+  for 1.10.
+
+* [CONFIGURATION-557] Regression: MapConfiguration no longer accepts a Map<String, String>
+
+  In 1.7 and before, it was possible to pass an arbitrary Map into the constructor of
+  MapConfiguration. With the generification in 1.8, this actually broke and it was no longer
+  possible to pass in e.g. a Map<String, String> because the signature now required a
+  Map<String, Object>. Changing the constructor to accept a Map<String, ?> restores this.
+
+  All of this is purely a compiler issue, the runtime itself does not see any of the generics
+  due to the Java type erasure.
+
+* [CONFIGURATION-558] Configuration no longer accepts List<String> as default for getList()
+
+  Similar to CONFIGURATION-557, the getList(String, List) method was generified to be
+  getList(String, List<Object>) but needs to be getList(String, List<?>) so that code that
+  used a more specific list (such as a List<String>) still compiles against the new API.
+
+
+IMPROVEMENTS AND NEW FEATURES IN 1.10
+=====================================
+
+* [CONFIGURATION-525] PropertiesConfigurationLayout does not preserve comments at bottom of a file
+
+  PropertiesConfiguration now keeps a comment at the bottom of a
+  properties file. A new footer property was added for reading and
+  writing this footer comment.
+
+* [CONFIGURATION-526] Support loading from and saving to DOM nodes
+
+  XMLPropertiesConfiguration now supports loading from and saving to DOM
+  nodes.
+
+* [CONFIGURATION-534] PropertyConfiguration's handling of includes depends on the
+                      existence of a base path
+
+  The includesAllowed property of PropertyConfiguration is now independent
+  from the existence of a base path.
+
+* [CONFIGURATION-550] Missing conversion to char
+
+  Conversion to Character is now supported.
+
+
+OTHER CHANGES
+=============
diff --git a/conf/CommonsConfiguration.xsd b/conf/CommonsConfiguration.xsd
new file mode 100644
index 0000000..1b88151
--- /dev/null
+++ b/conf/CommonsConfiguration.xsd
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- 
+	Apache Commons Configuration XML Schema for configuration.xml
+	version: 0.1
+	author : Borut Bolčina
+	date    : January 2nd, 2006
+-->
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
+	<xs:element name="configuration">
+		<xs:annotation>
+			<xs:documentation>Commons Configuration v0.1</xs:documentation>
+		</xs:annotation>
+		<xs:complexType>
+			<xs:complexContent>
+				<xs:extension base="sources">
+					<xs:sequence minOccurs="0">
+						<xs:element name="override" type="sources" minOccurs="0"/>
+						<xs:element name="additional" type="sources" minOccurs="0"/>
+					</xs:sequence>
+				</xs:extension>
+			</xs:complexContent>
+		</xs:complexType>
+	</xs:element>
+	<xs:complexType name="abstractConfiguration">
+		<xs:attribute name="delimiter" type="xs:string" use="optional"/>
+		<xs:attribute name="throwExceptionOnMissing" type="xs:boolean" use="optional"/>
+	</xs:complexType>
+	<xs:complexType name="hierarhicalConfiguration">
+		<xs:complexContent>
+			<xs:extension base="abstractConfiguration">
+				<xs:attribute name="root" type="xs:string" use="optional"/>
+			</xs:extension>
+		</xs:complexContent>
+	</xs:complexType>
+	<xs:complexType name="abstractHierarchicalFileConfiguration">
+		<xs:complexContent>
+			<xs:extension base="hierarhicalConfiguration">
+				<xs:attribute name="fileName" use="required">
+					<xs:simpleType>
+						<xs:restriction base="xs:string">
+							<xs:minLength value="1"/>
+							<xs:pattern value=".+\.properties"/>
+							<xs:pattern value=".+\.xml"/>
+							<xs:pattern value=".+\.plist"/>
+						</xs:restriction>
+					</xs:simpleType>
+				</xs:attribute>
+				<xs:attribute name="autosave" type="xs:boolean" use="optional"/>
+				<xs:attribute name="basePath" type="xs:string" use="optional"/>
+				<xs:attribute name="encoding" type="xs:string" use="optional"/>
+				<xs:attribute name="url" type="xs:anyURI" use="optional"/>
+			</xs:extension>
+		</xs:complexContent>
+	</xs:complexType>
+	<xs:complexType name="abstractFileConfiguration">
+		<xs:complexContent>
+			<xs:extension base="abstractConfiguration">
+				<xs:attribute name="fileName" use="required">
+					<xs:simpleType>
+						<xs:restriction base="xs:string">
+							<xs:minLength value="1"/>
+							<xs:pattern value=".+\.properties"/>
+							<xs:pattern value=".+\.xml"/>
+						</xs:restriction>
+					</xs:simpleType>
+				</xs:attribute>
+				<xs:attribute name="encoding" type="xs:string" use="optional"/>
+				<xs:attribute name="basePath" type="xs:string" use="optional"/>
+				<xs:attribute name="autosave" type="xs:boolean" use="optional"/>
+				<xs:attribute name="path" type="xs:string" use="optional"/>
+				<xs:attribute name="url" type="xs:anyURI" use="optional"/>
+			</xs:extension>
+		</xs:complexContent>
+	</xs:complexType>
+	<xs:complexType name="propertiesConfiguration">
+		<xs:complexContent>
+			<xs:extension base="abstractFileConfiguration">
+				<xs:attribute name="header" type="xs:string" use="optional"/>
+				<xs:attribute name="include" type="xs:string" use="optional"/>
+				<xs:attribute name="includesAllowed" type="xs:boolean" use="optional"/>
+			</xs:extension>
+		</xs:complexContent>
+	</xs:complexType>
+	<xs:complexType name="xmlPropertiesConfiguration">
+		<xs:complexContent>
+			<xs:extension base="propertiesConfiguration"/>
+		</xs:complexContent>
+	</xs:complexType>
+	<xs:complexType name="xmlConfiguration">
+		<xs:complexContent>
+			<xs:extension base="abstractHierarchicalFileConfiguration">
+				<xs:attribute name="rootElementName" type="xs:string" use="optional"/>
+				<xs:attribute name="validating" type="xs:boolean" use="optional"/>
+			</xs:extension>
+		</xs:complexContent>
+	</xs:complexType>
+	<xs:complexType name="jndiConfiguration">
+		<xs:complexContent>
+			<xs:extension base="abstractConfiguration">
+				<xs:attribute name="prefix" type="xs:string" use="required"/>
+			</xs:extension>
+		</xs:complexContent>
+	</xs:complexType>
+	<xs:complexType name="sources">
+		<xs:choice maxOccurs="unbounded">
+			<xs:element name="system" type="xs:string"/>
+			<xs:element name="properties" type="propertiesConfiguration"/>
+			<xs:element name="xml" type="xmlConfiguration"/>
+			<xs:element name="plist" type="abstractHierarchicalFileConfiguration"/>
+			<xs:element name="jndi" type="jndiConfiguration"/>
+		</xs:choice>
+	</xs:complexType>
+</xs:schema>
diff --git a/conf/HEADER.txt b/conf/HEADER.txt
new file mode 100644
index 0000000..2944f98
--- /dev/null
+++ b/conf/HEADER.txt
@@ -0,0 +1,16 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
diff --git a/conf/README b/conf/README
new file mode 100644
index 0000000..bd219a8
--- /dev/null
+++ b/conf/README
@@ -0,0 +1,4 @@
+DO NOT DELETE THESE FILES! 
+
+They're used by the unit tests for testing various Configuration implementations.
+
diff --git a/conf/checkstyle-suppressions.xml b/conf/checkstyle-suppressions.xml
new file mode 100644
index 0000000..8436ca3
--- /dev/null
+++ b/conf/checkstyle-suppressions.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+
+<!DOCTYPE suppressions PUBLIC
+    "-//Puppy Crawl//DTD Suppressions 1.0//EN"
+    "http://www.puppycrawl.com/dtds/suppressions_1_0.dtd">
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- ===================================================================== -->
+<!-- Exceptions for Checkstyle -->
+<!-- $Id: pom.xml 1157212 2011-08-12 18:54:24Z oheger $ -->
+<!-- ===================================================================== -->
+
+<suppressions>
+
+    <suppress checks="MissingSwitchDefault" files="PropertiesConfiguration.java"/>
+
+    <suppress checks="DoubleCheckedLocking" files="DynamicCombinedConfiguration.java"
+      lines="800-900"/>
+</suppressions>
diff --git a/conf/checkstyle.xml b/conf/checkstyle.xml
new file mode 100644
index 0000000..0fdb3f0
--- /dev/null
+++ b/conf/checkstyle.xml
@@ -0,0 +1,188 @@
+<?xml version="1.0"?>
+<!DOCTYPE module PUBLIC
+    "-//Puppy Crawl//DTD Check Configuration 1.1//EN"
+    "http://www.puppycrawl.com/dtds/configuration_1_1.dtd">
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- ===================================================================== -->
+<!-- Checkstyle configuration that checks the commons-configuration coding conventions -->
+<!-- $Id: checkstyle.xml 1361582 2012-07-14 20:35:03Z oheger $ -->
+<!-- ===================================================================== -->
+
+<module name="Checker">
+    <property name="localeLanguage" value="en"/>
+
+    <!-- Checks that a package.html file exists for each package.     -->
+    <!-- See http://checkstyle.sf.net/config_javadoc.html#PackageHtml -->
+    <module name="JavadocPackage">
+      <property name="allowLegacy" value="true"/>
+    </module>
+
+    <!-- Checks whether files end with a new line.                        -->
+    <!-- See http://checkstyle.sf.net/config_misc.html#NewlineAtEndOfFile -->
+    <module name="NewlineAtEndOfFile"/>
+
+    <!-- Checks that property files contain the same keys.         -->
+    <!-- See http://checkstyle.sf.net/config_misc.html#Translation -->
+    <module name="Translation"/>
+
+    <!-- Exceptions -->
+    <module name="SuppressionFilter">
+        <property name="file" value="${basedir}/conf/checkstyle-suppressions.xml"/>
+    </module>
+
+    <!-- Checks for Headers                              -->
+    <!-- See http://checkstyle.sf.net/config_header.html -->
+    <module name="Header">
+        <property name="headerFile" value="${basedir}/conf/HEADER.txt"/>
+        <property name="ignoreLines" value="2"/>
+    </module>
+
+    <module name="FileTabCharacter"/>
+    <module name="RegexpSingleline">
+        <!-- \s matches whitespace character, $ matches end of line. -->
+        <property name="format" value="\s+$"/>
+        <property name="message" value="Line has trailing spaces."/>
+    </module>
+    <module name="FileLength"/>
+
+    <module name="TreeWalker">
+
+        <property name="cacheFile" value="${checkstyle.cache.file}"/>
+
+        <!-- Checks for Javadoc comments.                     -->
+        <!-- See http://checkstyle.sf.net/config_javadoc.html -->
+        <module name="JavadocMethod">
+            <property name="scope" value="public"/>
+            <property name="allowUndeclaredRTE" value="true"/>
+	    <property name="allowMissingJavadoc" value="true"/>
+        </module>
+        <module name="JavadocType">
+            <property name="authorFormat" value="\S"/>
+        </module>
+        <module name="JavadocVariable"/>
+
+
+        <!-- Checks for Naming Conventions.                  -->
+        <!-- See http://checkstyle.sf.net/config_naming.html -->
+        <module name="ConstantName"/>
+        <module name="LocalFinalVariableName"/>
+        <module name="LocalVariableName"/>
+        <module name="MemberName"/>
+        <module name="MethodName"/>
+        <module name="PackageName"/>
+        <module name="ParameterName"/>
+        <module name="StaticVariableName"/>
+        <module name="TypeName"/>
+
+
+        <!-- Following interprets the header file as regular expressions. -->
+        <!-- <module name="RegexpHeader"/>                                -->
+
+
+        <!-- Checks for imports                              -->
+        <!-- See http://checkstyle.sf.net/config_import.html -->
+        <module name="AvoidStarImport"/>
+        <module name="IllegalImport"/> <!-- defaults to sun.* packages -->
+        <module name="RedundantImport"/>
+        <module name="UnusedImports"/>
+
+
+        <!-- Checks for Size Violations.                    -->
+        <!-- See http://checkstyle.sf.net/config_sizes.html -->
+        <module name="LineLength">
+            <property name="max" value="120"/>
+        </module>
+        <module name="MethodLength"/>
+        <module name="ParameterNumber"/>
+
+
+        <!-- Checks for whitespace                               -->
+        <!-- See http://checkstyle.sf.net/config_whitespace.html -->
+        <module name="EmptyForIteratorPad"/>
+        <module name="NoWhitespaceAfter"/>
+        <module name="NoWhitespaceBefore"/>
+        <module name="OperatorWrap"/>
+        <module name="ParenPad"/>
+        <module name="WhitespaceAfter"/>
+        <module name="WhitespaceAround"/>
+
+
+        <!-- Modifier Checks                                    -->
+        <!-- See http://checkstyle.sf.net/config_modifiers.html -->
+        <module name="ModifierOrder"/>
+        <module name="RedundantModifier"/>
+
+
+        <!-- Checks for blocks. You know, those {}'s         -->
+        <!-- See http://checkstyle.sf.net/config_blocks.html -->
+        <module name="AvoidNestedBlocks"/>
+        <module name="EmptyBlock"/>
+        <module name="LeftCurly">
+            <property name="option" value="nl"/>
+        </module>
+        <module name="NeedBraces"/>
+        <module name="RightCurly">
+            <property name="option" value="alone"/>
+        </module>
+
+
+        <!-- Checks for common coding problems               -->
+        <!-- See http://checkstyle.sf.net/config_coding.html -->
+        <module name="CovariantEquals"/>
+        <module name="DoubleCheckedLocking"/>
+        <module name="EqualsHashCode"/>
+        <module name="IllegalInstantiation"/>
+        <module name="InnerAssignment"/>
+        <module name="MagicNumber">
+            <property name="ignoreNumbers" value="-1,0,1,2,3"/>
+        </module>
+        <module name="RedundantThrows">
+            <property name="allowUnchecked" value="true"/>
+        </module>
+        <module name="SimplifyBooleanExpression"/>
+        <module name="SimplifyBooleanReturn"/>
+        <module name="StringLiteralEquality"/>
+        <module name="SuperClone"/>
+        <module name="SuperFinalize"/>
+        <module name="DeclarationOrder"/>
+        <module name="ExplicitInitialization"/>
+        <module name="DefaultComesLast"/>
+        <module name="FallThrough"/>
+        <module name="MultipleVariableDeclarations"/>
+        <module name="UnnecessaryParentheses"/>
+
+        <!-- Checks for class design                         -->
+        <!-- See http://checkstyle.sf.net/config_design.html -->
+        <module name="FinalClass"/>
+        <module name="HideUtilityClassConstructor"/>
+        <module name="InterfaceIsType"/>
+        <module name="VisibilityModifier">
+            <property name="protectedAllowed" value="true"/>
+        </module>
+
+
+
+        <!-- Miscellaneous other checks.                   -->
+        <!-- See http://checkstyle.sf.net/config_misc.html -->
+        <module name="ArrayTypeStyle"/>
+        <module name="TodoComment"/>
+        <module name="UpperEll"/>
+
+    </module>
+
+</module>
diff --git a/conf/findbugs-exclude-filter.xml b/conf/findbugs-exclude-filter.xml
new file mode 100644
index 0000000..ecd5027
--- /dev/null
+++ b/conf/findbugs-exclude-filter.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<!-- ===================================================================== -->
+<!-- $Id: findbugs-exclude-filter.xml 1197763 2011-11-04 20:48:02Z oheger $ -->
+<!-- ===================================================================== -->
+<FindBugsFilter>
+  <!-- Enable only high priority warnings -->
+  <Match>
+    <Priority value="2"/>
+  </Match>
+
+  <Match>
+    <Priority value="3"/>
+  </Match>
+</FindBugsFilter>
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..0018092
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,681 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- ===================================================================== -->
+<!-- $Id: pom.xml 1535307 2013-10-24 08:18:50Z henning $ -->
+<!-- ===================================================================== -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <parent>
+    <groupId>org.apache.commons</groupId>
+    <artifactId>commons-parent</artifactId>
+    <version>32</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>commons-configuration</groupId>
+  <artifactId>commons-configuration</artifactId>
+  <version>1.10</version>
+  <name>Apache Commons Configuration</name>
+
+  <inceptionYear>2001</inceptionYear>
+  <description>Tools to assist in the reading of configuration/preferences files in various formats.</description>
+
+  <url>http://commons.apache.org/configuration/</url>
+
+  <issueManagement>
+    <system>jira</system>
+    <url>http://issues.apache.org/jira/browse/CONFIGURATION</url>
+  </issueManagement>
+
+  <scm>
+    <connection>scm:svn:http://svn.apache.org/repos/asf/commons/proper/configuration/tags/CONFIGURATION_1_10RC2</connection>
+    <developerConnection>scm:svn:https://svn.apache.org/repos/asf/commons/proper/configuration/tags/CONFIGURATION_1_10RC2</developerConnection>
+    <url>http://svn.apache.org/viewvc/commons/proper/configuration/tags/CONFIGURATION_1_10RC2</url>
+  </scm>
+
+  <distributionManagement>
+    <site>
+      <id>apache.website</id>
+      <url>${commons.deployment.protocol}://people.apache.org/www/commons.apache.org/${commons.componentid}</url>
+    </site>
+  </distributionManagement>
+
+  <developers>
+    <developer>
+      <name>Daniel Rall</name>
+      <id>dlr</id>
+      <email>dlr at finemaltcoding.com</email>
+      <organization>CollabNet, Inc.</organization>
+      <roles>
+        <role>Java Developer</role>
+      </roles>
+    </developer>
+
+    <developer>
+      <name>Jason van Zyl</name>
+      <id>jvanzyl</id>
+      <email>jason at zenplex.com</email>
+      <organization>Zenplex</organization>
+      <roles>
+        <role>Java Developer</role>
+      </roles>
+    </developer>
+
+    <developer>
+      <name>Martin Poeschl</name>
+      <id>mpoeschl</id>
+      <email>mpoeschl at marmot.at</email>
+      <organization>tucana.at</organization>
+      <roles>
+        <role>Java Developer</role>
+      </roles>
+    </developer>
+
+    <developer>
+      <name>dIon Gillard</name>
+      <id>dion</id>
+      <email>dion at multitask.com.au</email>
+      <organization>Multitask Consulting</organization>
+      <roles>
+        <role>Java Developer</role>
+      </roles>
+    </developer>
+
+    <developer>
+      <name>Henning P. Schmiedehausen</name>
+      <id>henning</id>
+      <email>henning at schmiedehausen.org</email>
+      <timezone>-8</timezone>
+      <roles>
+        <role>Java Developer</role>
+      </roles>
+    </developer>
+
+    <developer>
+      <name>Eric Pugh</name>
+      <id>epugh</id>
+      <email>epugh at upstate.com</email>
+      <organization>upstate.com</organization>
+      <roles>
+        <role>Java Developer</role>
+      </roles>
+    </developer>
+
+    <developer>
+      <name>Brian E. Dunbar</name>
+      <id>bdunbar</id>
+      <email>bdunbar at dunbarconsulting.org</email>
+      <organization>dunbarconsulting.org</organization>
+      <roles>
+        <role>Java Developer</role>
+      </roles>
+    </developer>
+
+    <developer>
+      <name>Emmanuel Bourg</name>
+      <id>ebourg</id>
+      <email>ebourg at apache.org</email>
+      <organization>Ariane Software</organization>
+      <timezone>+1</timezone>
+      <roles>
+        <role>Java Developer</role>
+      </roles>
+    </developer>
+
+    <developer>
+      <name>Oliver Heger</name>
+      <id>oheger</id>
+      <email>oheger at apache.org</email>
+      <organization>Agfa HealthCare</organization>
+      <timezone>+1</timezone>
+      <roles>
+        <role>Java Developer</role>
+      </roles>
+    </developer>
+
+    <developer>
+      <name>Jörg Schaible</name>
+      <id>joehni</id>
+      <email>joerg.schaible at gmx.de</email>
+      <timezone>+1</timezone>
+      <roles>
+        <role>Java Developer</role>
+      </roles>
+    </developer>
+
+    <developer>
+      <name>Ralph Goers</name>
+      <id>rgoers</id>
+      <email>rgoers at apache.org</email>
+      <organization>Intuit</organization>
+      <timezone>-8</timezone>
+      <roles>
+        <role>Java Developer</role>
+      </roles>
+    </developer>
+  </developers>
+
+  <contributors>
+    <contributor>
+      <name>Konstantin Shaposhnikov</name>
+      <email>ksh at scand.com</email>
+      <organization>scand.com</organization>
+    </contributor>
+
+    <contributor>
+      <name>Jamie M. Guillemette</name>
+      <email>JMGuillemette at gmail.com</email>
+      <organization>TD Bank</organization>
+    </contributor>
+
+    <contributor>
+      <name>Jorge Ferrer</name>
+      <email>jorge.ferrer at gmail.com</email>
+      <organization />
+    </contributor>
+
+    <contributor>
+      <name>Gabriele Garuglieri</name>
+      <email>gabriele.garuglieri at infoblu.it</email>
+      <organization>Infoblu S.p.A</organization>
+    </contributor>
+
+    <contributor>
+      <name>Nicolas De Loof</name>
+      <email>nicolas.deloof at gmail.com</email>
+      <organization>Cap Gemini</organization>
+    </contributor>
+  </contributors>
+
+  <dependencies>
+    <dependency>
+      <groupId>commons-collections</groupId>
+      <artifactId>commons-collections</artifactId>
+      <version>3.2.1</version>
+      <optional>true</optional>
+    </dependency>
+
+    <dependency>
+      <groupId>commons-lang</groupId>
+      <artifactId>commons-lang</artifactId>
+      <version>2.6</version>
+    </dependency>
+
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging</artifactId>
+      <version>1.1.1</version>
+      <exclusions>
+        <exclusion>
+          <groupId>logkit</groupId>
+          <artifactId>logkit</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>avalon-framework</groupId>
+          <artifactId>avalon-framework</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+
+    <dependency>
+      <groupId>commons-digester</groupId>
+      <artifactId>commons-digester</artifactId>
+      <version>1.8.1</version>
+      <optional>true</optional>
+    </dependency>
+
+    <dependency>
+      <groupId>commons-beanutils</groupId>
+      <artifactId>commons-beanutils</artifactId>
+      <version>1.8.3</version>
+      <optional>true</optional>
+    </dependency>
+
+    <dependency>
+      <groupId>commons-codec</groupId>
+      <artifactId>commons-codec</artifactId>
+      <version>1.6</version>
+      <optional>true</optional>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-jexl</artifactId>
+      <version>2.1.1</version>
+      <optional>true</optional>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-vfs2</artifactId>
+      <version>2.0</version>
+      <optional>true</optional>
+    </dependency>
+
+    <dependency>
+      <groupId>commons-jxpath</groupId>
+      <artifactId>commons-jxpath</artifactId>
+      <version>1.3</version>
+      <optional>true</optional>
+      <exclusions>
+        <exclusion>
+          <groupId>xerces</groupId>
+          <artifactId>xerces</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>ant</groupId>
+          <artifactId>ant-optional</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+
+    <dependency>
+      <groupId>xml-resolver</groupId>
+      <artifactId>xml-resolver</artifactId>
+      <version>1.2</version>
+      <optional>true</optional>
+    </dependency>
+
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>servlet-api</artifactId>
+      <version>2.4</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>xerces</groupId>
+      <artifactId>xercesImpl</artifactId>
+      <version>2.6.2</version>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>xml-apis</groupId>
+      <artifactId>xml-apis</artifactId>
+      <version>1.0.b2</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <!-- Needed for testing -->
+
+    <dependency>
+      <groupId>commons-dbcp</groupId>
+      <artifactId>commons-dbcp</artifactId>
+      <version>1.2.2</version>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>commons-pool</groupId>
+      <artifactId>commons-pool</artifactId>
+      <version>1.4</version>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>hsqldb</groupId>
+      <artifactId>hsqldb</artifactId>
+      <version>1.7.2.2</version>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>dbunit</groupId>
+      <artifactId>dbunit</artifactId>
+      <version>2.1</version>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.11</version>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>junit-addons</groupId>
+      <artifactId>junit-addons</artifactId>
+      <version>1.4</version>
+      <scope>test</scope>
+      <exclusions>
+        <exclusion>
+          <groupId>xerces</groupId>
+          <artifactId>xmlParserAPIs</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>xerces</groupId>
+          <artifactId>xercesImpl</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+
+    <dependency>
+      <groupId>mockobjects</groupId>
+      <artifactId>mockobjects-core</artifactId>
+      <version>0.09</version>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>mockobjects</groupId>
+      <artifactId>mockobjects-jdk1.4-j2ee1.3</artifactId>
+      <version>0.09</version>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.easymock</groupId>
+      <artifactId>easymock</artifactId>
+      <version>3.2</version>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>javax.mail</groupId>
+      <artifactId>mail</artifactId>
+      <version>1.4</version>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>log4j</groupId>
+      <artifactId>log4j</artifactId>
+      <version>1.2.8</version>
+      <optional>true</optional>
+    </dependency>
+
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+      <version>1.5.6</version>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-ext</artifactId>
+      <version>1.5.6</version>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-log4j12</artifactId>
+      <version>1.5.6</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <properties>
+    <commons.componentid>configuration</commons.componentid>
+    <commons.release.version>1.10</commons.release.version>
+    <commons.rc.version>RC1</commons.rc.version>
+    <commons.jira.id>CONFIGURATION</commons.jira.id>
+    <maven.compiler.source>1.5</maven.compiler.source>
+    <maven.compiler.target>1.5</maven.compiler.target>
+
+    <!-- Explicitly declare optional dependencies for the OSGi manifest. -->
+    <commons.osgi.import>
+      org.apache.commons.beanutils.*;resolution:=optional,
+      org.apache.commons.digester.*;resolution:=optional,
+      org.apache.commons.collections.*;resolution:=optional,
+      org.apache.commons.codec.*;resolution:=optional,
+      org.apache.commons.jxpath.*;resolution:=optional,
+      org.apache.xml.resolver.*;resolution:=optional,
+      javax.servlet.*;resolution:=optional,
+      org.apache.commons.jexl2.*;resolution:=optional,
+      org.apache.commons.vfs2.*;resolution:=optional,
+      *
+    </commons.osgi.import>
+  </properties>
+
+  <build>
+    <testResources>
+      <testResource>
+        <directory>src/test/resources</directory>
+      </testResource>
+      <testResource>
+        <directory>src/main/resources</directory>
+        <includes>
+          <include>*.dtd</include>
+        </includes>
+      </testResource>
+      <!-- hack to ensure the N&L appear in jars -->
+      <testResource>
+        <directory>${basedir}</directory>
+        <targetPath>META-INF</targetPath>
+        <includes>
+          <include>NOTICE.txt</include>
+          <include>LICENSE.txt</include>
+        </includes>
+      </testResource>
+    </testResources>
+    <plugins>
+      <plugin>
+        <!-- create the source and binary assemblies -->
+        <artifactId>maven-assembly-plugin</artifactId>
+        <configuration>
+          <descriptors>
+            <descriptor>src/main/assembly/bin.xml</descriptor>
+            <descriptor>src/main/assembly/src.xml</descriptor>
+          </descriptors>
+          <tarLongFileMode>gnu</tarLongFileMode>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <!-- Uncomment to enable profiling unit tests -->
+          <!-- <argLine>-agentpath:"${yourkit.home}/bin/mac/libyjpagent.jnilib"</argLine> -->
+          <forkMode>once</forkMode>
+          <excludes>
+            <exclude>**/TestWebdavConfigurationBuilder.java</exclude>
+          </excludes>
+          <systemPropertyVariables>
+            <java.awt.headless>true</java.awt.headless>
+            <org.apache.commons.logging.Log>org.apache.commons.configuration.Logging</org.apache.commons.logging.Log>
+          </systemPropertyVariables>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-source-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>attach-sources</id>
+            <phase>install</phase>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>javacc-maven-plugin</artifactId>
+        <version>2.6</version>
+        <executions>
+          <execution>
+            <id>javacc</id>
+            <goals>
+              <goal>javacc</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-scm-publish-plugin</artifactId>
+          <configuration>
+            <ignorePathsToDelete>
+              <ignorePathToDelete>javadocs**</ignorePathToDelete>
+            </ignorePathsToDelete>
+          </configuration>
+        </plugin>
+        <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
+        <plugin>
+          <groupId>org.eclipse.m2e</groupId>
+          <artifactId>lifecycle-mapping</artifactId>
+          <version>1.0.0</version>
+          <configuration>
+            <lifecycleMappingMetadata>
+              <pluginExecutions>
+                <pluginExecution>
+                  <pluginExecutionFilter>
+                    <groupId>
+                      org.apache.maven.plugins
+                    </groupId>
+                    <artifactId>
+                      maven-antrun-plugin
+                    </artifactId>
+                    <versionRange>
+                      [0,)
+                    </versionRange>
+                    <goals>
+                      <goal>run</goal>
+                    </goals>
+                  </pluginExecutionFilter>
+                  <action>
+                    <ignore />
+                  </action>
+                </pluginExecution>
+                <pluginExecution>
+                  <pluginExecutionFilter>
+                    <groupId>
+                      org.codehaus.mojo
+                    </groupId>
+                    <artifactId>
+                      javacc-maven-plugin
+                    </artifactId>
+                    <versionRange>
+                      [0,)
+                    </versionRange>
+                    <goals>
+                      <goal>javacc</goal>
+                    </goals>
+                  </pluginExecutionFilter>
+                  <action>
+                    <ignore />
+                  </action>
+                </pluginExecution>
+              </pluginExecutions>
+            </lifecycleMappingMetadata>
+          </configuration>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
+  <profiles>
+    <profile>
+      <id>webdav</id>
+      <activation>
+        <activeByDefault>false</activeByDefault>
+      </activation>
+      <dependencies>
+        <dependency>
+          <groupId>org.apache.jackrabbit</groupId>
+          <artifactId>jackrabbit-webdav</artifactId>
+          <version>1.5.2</version>
+          <scope>test</scope>
+        </dependency>
+      </dependencies>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-surefire-plugin</artifactId>
+            <configuration>
+              <forkMode>once</forkMode>
+              <systemPropertyVariables>
+                <java.awt.headless>true</java.awt.headless>
+                <test.webdav.base>${test.webdav.base}</test.webdav.base>
+              </systemPropertyVariables>
+              <includes>
+                <include>**/TestWebdavConfigurationBuilder.java</include>
+              </includes>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+  <reporting>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-changes-plugin</artifactId>
+        <configuration>
+          <onlyCurrentVersion>true</onlyCurrentVersion>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+        <version>2.7</version>
+        <configuration>
+          <configLocation>${basedir}/conf/checkstyle.xml</configLocation>
+          <suppressionsLocation>${basedir}/conf/checkstyle-suppressions.xml</suppressionsLocation>
+          <enableRulesSummary>false</enableRulesSummary>
+          <propertyExpansion>basedir=${basedir}</propertyExpansion>
+        </configuration>
+        <reportSets>
+          <reportSet>
+            <reports>
+              <report>checkstyle</report>
+            </reports>
+          </reportSet>
+        </reportSets>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.rat</groupId>
+        <artifactId>apache-rat-plugin</artifactId>
+        <configuration>
+          <excludes combine.children="append">
+            <exclude>src/java/org/apache/commons/configuration/plist/*.java</exclude>
+            <exclude>velocity.log</exclude>
+          </excludes>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>findbugs-maven-plugin</artifactId>
+        <version>2.3.1</version>
+        <configuration>
+          <threshold>Normal</threshold>
+          <effort>Default</effort>
+          <excludeFilterFile>${basedir}/conf/findbugs-exclude-filter.xml</excludeFilterFile>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>clirr-maven-plugin</artifactId>
+        <version>${commons.clirr.version}</version>
+        <configuration>
+          <excludes>
+            <exclude>org/apache/commons/configuration/plist/PropertyListParser*</exclude>
+          </excludes>
+        </configuration>
+      </plugin>
+    </plugins>
+  </reporting>
+</project>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
new file mode 100644
index 0000000..7af0286
--- /dev/null
+++ b/src/changes/changes.xml
@@ -0,0 +1,1805 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- ===================================================================== -->
+<!-- $Id: changes.xml 1534402 2013-10-21 22:35:52Z henning $ -->
+<!-- ===================================================================== -->
+<document>
+  <properties>
+    <title>Changes</title>
+    <author email="epugh at upstate.com">Eric Pugh</author>
+  </properties>
+
+  <body>
+    <release version="1.10" date="in SVN"
+      description="Minor bug fixes and improvements">
+      <action dev="oheger" type="update" issue="CONFIGURATION-500">
+        XMLConfiguration now adds attributes of elements defining a list to
+        all list nodes.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-525">
+        PropertiesConfiguration now keeps a comment at the bottom of a
+        properties file. A new footer property was added for reading and
+        writing this footer comment.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-526" due-to="Oliver Kopp">
+        XMLPropertiesConfiguration now supports loading from and saving to DOM
+        nodes.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-534">
+        The includesAllowed property of PropertyConfiguration is now independent
+        from the existence of a base path.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-546" due-to="Justin Couch">
+        BeanHelper can now process BeanDefinitions initializing properties of
+        collection types of their target beans.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-550">
+        Conversion to Character is now supported.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-555">
+        Fixed a bug in the handling of the xml:space attribute in
+        XMLConfiguration. The attribute is now also applied to the current
+        element, not only to sub elements.
+      </action>
+      <action dev="henning" type="fix" issue="CONFIGURATION-556">
+        In 1.7 and before, any change to the system properties was
+        immediately reflected in a SystemConfiguration object. This
+        behaviour broke in 1.8 and 1.9. This has been fixed for 1.10.
+      </action>
+      <action dev="henning" type="fix" issue="CONFIGURATION-557">
+        In 1.7 and before, it was possible to pass an arbitrary Map
+        into the constructor of MapConfiguration. With the
+        generification in 1.8, this actually broke and it was no
+        longer possible to pass in e.g. a Map<String, String>
+        because the signature now required a Map<String,
+        Object>. Changing the constructor to accept a
+        Map<String, ?> restores this.
+      </action>
+      <action dev="henning" type="fix" issue="CONFIGURATION-558">
+        Similar to CONFIGURATION-557, the getList(String, List) method
+        was generified to be getList(String, List<Object>) but
+        needs to be getList(String, List<?>) so that code that
+        used a more specific list (such as a List<String>) still
+        compiles against the new API.
+      </action>
+    </release>
+    <release version="1.9" date="2012-08-08"
+      description="Minor bug fixes and improvements">
+      <action dev="oheger" type="update" issue="CONFIGURATION-503" due-to="Tino Sino">
+        Small changes in user guide documentation.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-502" due-to="Tino Sino">
+        Improvements of basic features and AbstractConfiguration documentation.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-501">
+        XMLPropertyListConfiguration no longer swallows exception caused by
+        invalid date properties. Now a warning message is logged.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-495">
+        List properties can now be set correctly on a HierarchicalConfiguration
+        if delimiter parsing is disabled.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-488">
+        Made static DateFormat fields in XMLPropertyListConfiguration.PListNode
+        final and added a note about proper synchronization.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-487">
+        DataConfiguration.get() now also works with String properties and if no
+        data type conversion is required.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-483">
+        DatabaseConfiguration now always closes the result set.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-482" due-to="Chris Seieroe">
+        The Import-Package section in the OSGi manifest now uses the
+        resolution:=optional directive for optional dependencies.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-481">
+        Variable substitution in configuration sources declared in a definition
+        file for DefaultConfigurationBuilder now works across multiple sources.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-477">
+        PropertyListConfiguration now can deal with C-style comments in plist
+        configuration files. Both block and single-line comments are supported.
+      </action>
+    </release>
+
+    <release version="1.8" date="2012-02-04"
+      description="Support for Java 1.5">
+      <action dev="oheger" type="fix" issue="CONFIGURATION-476">
+        Fixed possible ClassCastExceptions in CompositeConfiguration related to
+        special in-memory configurations.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-475">
+        Class ConfigurationKey was deprecated in favor of DefaultConfigurationKey.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-474">
+        Implemented delimiter parsing in HierarchicalINIConfiguration to be
+        consistent with other Configuration implementations. Note that this can
+        impact existing code. To switch back to the old behavior, call
+        setDelimiterParsingDisabled(true) before loading the configuration.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-471">
+        CompositeConfiguration now provides better support for child
+        configurations that are used as in-memory configuration.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-470">
+        Classes generated by JavaCC are now created dynamically during the
+        build process.
+      </action>
+      <action dev="ebourg" type="add">
+        Commons Configuration now requires Java 5 or later.
+      </action>
+      <action dev="ebourg" type="add" issue="CONFIGURATION-466">
+        Binary literals are now supported (i.e Ob11010001).
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-465">
+        Updated the dependency to Commons Jexl to version 2.1.1. This version
+        provides correct OSGi manifest headers.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-463">
+        Improved documentation of AbstractFileConfiguration related to load()
+        methods and their impact on the base path.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-461">
+        The project now uses standard Maven directory layout.
+      </action>
+    </release>
+
+    <release version="1.7" date="2011-09-07"
+      description="Many bugfixes, some new features.">
+      <action dev="oheger" type="fix" issue="CONFIGURATION-460">
+        Reloading now also works for configuration sources declared in the
+        additional section of a configuration definition file for
+        DefaultConfigurationBuilder.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-459">
+        ConfigurationFactory has been deprecated. The user guide was updated to
+        no more mention this class.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-458">
+        HierarchicalConfiguration now provides a specific implementation of the
+        clear() method. This is more efficient and also solves some other
+        problems related to clearing a SubnodeConfiguration.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-456">
+        Improved Javadocs of getKeys(String) method for some configuration
+        classes.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-455">
+        HierarchicalINIConfiguration.getSection() now creates a section if it
+        does not exist. The SubnodeConfiguration returned by this method is now
+        always connected to the parent ini configuration.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-452">
+        XPathExpressionEngine now provides better support for the setProperty()
+        method.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-448">
+        The parsing of ini files has been improved for property definitions
+        containing multiple separator characters.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-447">
+        DefaultConfigurationBuilder now supports including environment properties
+        using the "env" tag.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-446">
+        XMLConfiguration now handles attributes correctly whose value is an
+        empty string.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-445">
+        Transforming a CombinedConfiguration with ViewNodes to an
+        XMLConfiguration could cause problems with attributes. This has been
+        fixed.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-439">
+        Child configuration builders created for a <configuration> element
+        in a configuration definition file now inherit the configuration and
+        error listeners from the original DefaultConfigurationBuilder. This
+        makes it possible to suppress log output created for optional
+        configurations.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-438" due-to="Mike Noordermeer">
+        JNDIConfiguration.getKeys() no more logs an exception if the prefix does
+        not exist.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-437">
+        Child configuration builders created for a <configuration> element
+        in a configuration definition file now inherit some of their properties
+        from the builder object which processed the file.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-436">
+        The optional dependency to Apache Ant has been changed to the new
+        groupId org.apache.ant. The version was updated to the most recent
+        version 1.8.2 (older versions should still work).
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-434">
+        HierarchicalINIConfiguration now recognizes comment characters in
+        property definitions only if they are preceded by whitespace. Thus
+        comment characters can now be part of the property value. This is for
+        instance required for the definition of file paths which use the
+        semicolon as path separator.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-433">
+        Minor improvements of the support for indexed properties in
+        ConfigurationDynaBean.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-432">
+        The methods getList() and getStringArray() of AbstractConfiguration can
+        now handle single-valued properties of primitive types.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-428">
+        XMLConfiguration no longer escapes backslashs in the values of
+        XML elements.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-424">
+        HierarchicalINIConfiguration now works correctly with configurations
+        that contain only properties in the global section.
+      </action>
+      <action dev="rgoers" type="fix" issue="CONFIGURATION-423" due-to="William Buckley">
+        testFromClassPath() can fail when it should not because of inconsistent escaping of output from
+        PropertiesConfiguration.getURL() and FileChangedReloadingStrategy.getFile().toURL().
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-418">
+        A bug related to the interpretation of escape sequences for backslashes
+        has been fixed. The user guide has also been improved in this area.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-415">
+        Files with a plus character in their names are now handled correctly.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-413" due-to="Alexander Prishchepov">
+        SubsetConfiguration now produces correct events.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-412">
+        DatabaseConfiguration can now be instructed to perform a commit after an
+        update of the managed database table. This makes it usable in
+        environments where the connections do not use auto-commit mode.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-410">
+        Added a refresh() method to AbstractFileConfiguration and
+        AbstractHierarchicalFileConfiguration.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-409">
+        HierarchicalINIConfiguration now correctly saves sections whose name
+        contains delimiter characters.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-408">
+        PropertiesConfiguration.save() escaped slashes in properties values.
+        This was caused by a bug in commons-lang 2.4. Updating to the new
+        version commons-lang 2.5 fixed this problem.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-407">
+        Fixed a potential IllegalStateException in HierarchicalINIConfiguration
+        that can be thrown when the global section is requested concurrently.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-405">
+        XMLPropertyListConfiguration no longer throws a ConfigurationException
+        if the file to be loaded does not have an outer dict element.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-404" due-to="Rob Walker">
+        The default expression engine used by hierarchical configurations used to
+        throw a NumberFormatException if invalid indices were used in property
+        keys. This has been fixed. As a side effect brackets can now be used in
+        property keys.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-403">
+        When an empty XMLConfiguration was saved and reloaded the root element
+        was assigned an empty text value. Because of this isEmpty() returned
+        false for this configuration. This has been fixed.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-399">
+        Default variable interpolation now supports the env: prefix for
+        referencing environment variables.
+      </action>
+      <action dev="rgoers" type="fix" issue="CONFIGURATION-397">
+        Schema violation exceptions are now propagated back to the caller.
+      </action>
+       <action dev="rgoers" type="fix" issue="CONFIGURATION-390">
+        XMLConfiguration and CombinedConfiguraton are now synchronized to fix problems
+        caused by reloading in a multithreaded environment.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-396">
+        HierarchicalConfiguration.NodeVisitor is now passed the correct key to
+        its visitAfterChildren() method.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-393">
+        BaseConfiguration.clone() now also clones collections stored in the
+        internal map. This causes list properties to be handled correctly.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-389">
+        DefaultConfigurationBuilder now supports defining ini files in its
+        configuration definition file.
+      </action>
+      <action dev="rgoers" type="fix" issue="CONFIGURATION-388">
+        Attribute or element values will not be escaped when attribute or element splitting are disabled.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-363">
+        When using Commons Lang 2.6 or higher as dependency nested interpolation
+        in variable names is supported.
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-362">
+        Empty dictionaries in a PropertyList configuration are now preserved when the configuration is saved.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-385">
+        DatabaseConfiguration now generates correct events for the clear() and
+        clearProperty() methods.
+      </action>
+      <action dev="rgoers" type="add" issue="CONFIGURATION-380">
+        Add ExprLookup to allow expressions to be evaluated in configurations. When
+        used, this requires that Apache Commons Jexl be added as a dependency to
+        projects using Commons Configuration.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-374">
+        MapConfiguration now provides a way of controlling the trimming
+        behavior.
+      </action>
+      <action dev="rgoers" type="add" issue="CONFIGURATION-378">
+        Added MergeCombiner to allow elements in two configurations to be merged when the
+        element and attributes in the first file match those in the second file.
+      </action>
+      <action dev="rgoers" type="add" issue="CONFIGURATION-340">
+        File system access has been abstracted to a FileSystem interface. Two implementations
+        are provided, DefaultFileSystem that behaves in a backward compatible manner and
+        VFSFileSystem which uses Commons VFS to retreive and store files.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-314">
+        PropertiesConfigurationLayout now allows setting the line separator to
+        be used when writing the properties file.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-371">
+        PropertiesConfigurationLayout now also stores the property separators used for
+        the single properties. It is also possible to change them for specific
+        properties or set a global properties separator. In earlier versions
+        the separator was hard-coded to " = ".
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-370">
+        PropertiesConfiguration now defines a nested interface IOFactory. Using
+        this interface it is possible to inject custom PropertiesReader and
+        PropertiesWriter implementations.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-368">
+        SubnodeConfiguration now fires an event of type EVENT_SUBNODE_CHANGED
+        if a structural change of the parent configuration was detected. If the
+        SubnodeConfiguration is contained in a CombinedConfiguration, the
+        CombinedConfiguration receives this event and can update itself.
+      </action>
+      <action dev="rgoers" type="fix" issue="CONFIGURATION-361">
+        MultiFileHierarchicalConfiguration was not using basepath to
+        construct the file url. It also threw an exception if the
+        file pattern resolved to a non-existent file. This is now
+        configurable.
+      </action>
+      <action dev="joehni" type="update" issue="CONFIGURATION-375">
+        Align interpolation functionality of SubnodeConfiguration and
+        SubsetConfiguration. SubsetConfiguration will now also interpolate
+        keys of the parent configuration or use the local lookups of its
+        parent. SubnodeConfiguration is in turn now able to lookup local
+        keys as well.
+      </action>
+      <action dev="joehni" type="update" issue="CONFIGURATION-376">
+        Align interpolation functionality of SubnodeConfiguration and
+        SubsetConfiguration.
+      </action>
+      <action dev="joehni" type="update" issue="CONFIGURATION-377">
+        Align interpolation functionality of SubnodeConfiguration and
+        SubsetConfiguration.
+      </action>
+      <action dev="joehni" type="fix" issue="CONFIGURATION-369">
+        SubsetConfiguration did not use locally registered lookups of its
+        interpolator.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-359">
+        Fixed broken links to the API documentation in the user's guide.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-358">
+        Improvements of the user's guide for hierarchical configurations.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-357">
+        The message of the ConversionException thrown by
+        AbstractConfiguration.getBigInteger() is now correct.
+      </action>
+      <action dev="rgoers" type="add" issue="CONFIGURATION-356">
+        Added getConfigurations and getConfigurationNameList.
+      </action>
+      <action dev="rgoers" type="add" issue="CONFIGURATION-257">
+        Allow configurations to be validated using XML Schemas.
+      </action>
+      <action dev="rgoers" type="add" issue="CONFIGURATION-355">
+        Allow configurations to be validated using XML Schemas.
+      </action>
+    </release>
+
+    <release version="1.6" date="2008-12-25"
+      description="Another set of smaller bug fixes">
+      <action dev="oheger" type="update">
+        Some dependencies to other Commons components have been updated to the
+        recent versions. Affected are Commons Lang, Commons Collections,
+        Commons Logging, Commons BeanUtils, and Commons JXPath. The older
+        versions should still work.
+      </action>
+      <action dev="rgoers" type="add" issue="CONFIGURATION-353">
+        Allow system properties to be set from a configuration file.
+      </action>
+      <action dev="rgoers" type="add" issue="CONFIGURATION-351">
+        Allow variable resolvers to be defined configured in
+        DefaultConfigurationBuilder.
+      </action>
+      <action dev="rgoers" type="add" issue="CONFIGURATION-350">
+        Added MultiFileHierarchicalConfiguration, DynamicCombinedConfiguration
+        and PatternSubtreeConfigurationWrapper.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-349" due-to="Ralph Goers">
+        The visibility of DefaultConfigurationBuilder.XMLConfigurationProvider
+        was changed from package local to public. This makes it easier to
+        implement providers that create configuration classes derived from
+        XMLConfiguration.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-348">
+        AbstractHierarchicalFileConfiguration.getKeys() now also checks whether
+        a reload is required.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-347">
+        AbstractFileConfiguration.getKeys() now returns an iterator that points
+        to a snapshot of the keys of the configuration. This prevents
+        ConcurrentModificationExceptions during iteration when a reload is
+        performed.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-346">
+        ConfigurationUtils.convertToHierarchical() now creates multiple
+        configuration nodes for properties with multiple values. This
+        improves compatibility with queries.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-345">
+        PropertiesConfiguration now per default uses the encoding "ISO-8859-1"
+        for loading properties files.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-344">
+        CombinedConfiguration could cause a deadlock when it was accessed while
+        concurrently a reload of one of its child configuration happened. This
+        was fixed by reducing synchronization where it is not strictly
+        necessary.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-341">
+        The "force reload check" mechanism of CombinedConfiguration now also
+        works with sub configurations created by configurationAt().
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-339">
+        When performing interpolation the methods getList() and getStringArray()
+        of CompositeConfiguration did not take the order of child configurations
+        into account. This could lead to wrong interpolated values when the key
+        was contained in multiple child configuration. Interpolation is now
+        always done in the correct order.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-338" due-to="David Donn">
+        PropertiesConfiguration now also performs interpolation when searching
+        for include files. This means that the name of a file to include can be
+        determined by another property.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-337" due-to="Ralph Goers">
+        DefaultConfigurationBuilder now supports defining new configuration
+        providers in the configuration definition file.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-336">
+        When converting a flat configuration to a hierarchical one it is now
+        possible to specify the expression engine to be used for this purpose.
+        This may be necessary if the flat configuration contains keys with
+        special characters interpreted by the expression engine.
+        CombinedConfiguration defines the new setConversionExpressionEngine()
+        method. The expression engine passed to this method will be used when
+        converting flat child configurations to hierarchical ones.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-335">
+        XMLConfiguration now allows disabling the attribute splitting mechanism
+        introduced in the 1.5 release (as part of the fix for CONFIGURATION-268).
+        This may be necessary for correctly processing attributes containing
+        both the list delimiter and the attribute delimiter character. The new
+        property "disableAttributeSplitting" was added for this
+        purpose.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-334">
+        Made handling of parent nodes more consistent when setRoot() or
+        setRootNode() of HierarchicalConfiguration are involved.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-332">
+        Properties written through a DataConfiguration to a wrapped
+        PropertiesConfiguration got lost when the PropertiesConfiguration was
+        saved. This has been fixed.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-331">
+        XMLBeanDeclaration now defines a factory method createBeanDeclaration()
+        for creating the declarations for complex nested properties. This
+        method can be overridden by derived classes for injecting custom
+        BeanDeclaration implementations.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-328">
+        A bug in XMLConfiguration.addNodes() made it impossible to add
+        attribute nodes using this method. This has been fixed.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-327">
+        INIConfiguration misinterpreted variables in the global section with
+        a dot in their name as section names. HierarchicalINIConfiguration fixes
+        this problem.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-326">
+        INIConfiguration does not support obtaining a subset for the global
+        section. HierarchicalINIConfiguration provides the getSection() method
+        that returns the content of the global section if null is passed in as
+        section name.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-325">
+        INIConfiguration does not return the global section in its getSections()
+        method. HierarchicalINIConfiguration fixes this problem.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-324">
+        HierarchicalINIConfiguration adds support for line continuation.
+      </action>
+      <action dev="oheger" type="update">
+        INIConfiguration has been deprecated. Its functionality is now available
+        through the new HierarchicalINIConfiguration class.
+      </action>
+      <action dev="oheger" type="add">
+        With HierarchicalINIConfiguration a complete new Configuration
+        implementation for parsing Windows INI files is available. This new
+        class is a full replacement of INIConfiguration and addresses some of its
+        shortcomings. Being derived from HierarchicalConfiguration it offers
+        the enhanced functionality of hierarchical configurations.
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-322">
+        ConfigurationDynaBean now works properly with indexed properties
+        stored internally in the underlying configuration as arrays.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-321">
+        The iterator returned by HierarchicalConfiguration.getKeys(String prefix)
+        now also contains the prefix if this key is contained in the
+        configuration.
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-320">
+        XMLPropertyListConfiguration is no longer limited to 32 bits integers.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-318">
+        When an XMLConfiguration is created using the copy constructor, the name
+        of the root element is now preserved.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-316">
+        Changing the text of the root element of an XMLConfiguration had no
+        effect when the configuration was saved. This has been fixed.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-315">
+        CombinedConfiguration used to send two EVENT_COMBINED_INVALIDATE events
+        for each modified child configuration. Now this event is sent only
+        once after the affected child configuration was updated.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-307">
+        XMLConfiguration now supports the xml:space attribute. This attribute
+        can be used to preserve whitespace in the content of XML elements.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-306">
+        INIConfiguration now preserves whitespace in quoted values.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-302">
+        If a change has been detected by FileChangedReloadingStrategy, the
+        reloadingRequired() method will now return true until
+        reloadingPerformed() has been called.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-301">
+        Fixed a NullPointerException that could be thrown under certain
+        circumstances when saving an XMLConfiguration that was created using
+        the constructor that takes a HierarchicalConfiguration.
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-309">
+        Instantiating an XMLPropertyListConfiguration no longer fails
+        if the DTD is missing from the classpath.
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-300">
+        It's now possible to read a configuration file containing
+        a '#' in its name (requires Java 1.4 or above).
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-260">
+        Fixed the date format for XMLPropertyListConfiguration.
+      </action>
+    </release>
+
+    <release version="1.5" date="2007-11-24" description="Many smaller bugfixes">
+      <action dev="oheger" type="update" due-to="J&#xF6;rg Schaible">
+        Some of the dependencies in the m2 pom have been updated to be more
+        consistent.
+      </action>
+      <action dev="oheger" type="update" due-to="J&#xF6;rg Schaible">
+        The dependency to commons-logging was updated to the current version
+        1.1. Older versions of commons-logging will still work.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-273">
+        A new method interpolatedConfiguration() was added to AbstractConfiguration.
+        This method returns a configuration with the same type and
+        content as the original configuration, however all variables have been
+        resolved.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-299">
+        Resolving of variables with the prefix const (constant fields) caused
+        a ClassCastException under certain circumstances if non-String fields
+        were involved. This has been fixed.
+      </action>
+      <action dev="oheger" type="update" due-to="Nicolas De Loof">
+        The dependencies to commons-codec and commons-jxpath have been marked
+        as optional. They are not required by the core classes.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-284" due-to="Nicolas De Loof">
+        There is a new configuration implementation EnvironmentConfiguration,
+        which provides access to (OS) environment variables. On Java >= 1.5
+        this class can be directly used; on earlier versions a dependency to ant
+        is required.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-296">
+        A bug in XMLConfiguration caused that attributes of the root element
+        could not be changed. This has been fixed.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-290">
+        A new method registerEntityId() was added to XMLConfiguration, which
+        allows to register URLs for entities. A new default implementation of
+        the EntityResolver interface handles these entities automatically.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-295">
+        The subset() method of HierarchicalConfiguration now takes the value of
+        the subset's root node into account if it is not ambigous.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-294">
+        Nodes added to a XMLConfiguration using the addNodes() method could
+        lose their value when the configuration was saved. This is now fixed.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-272">
+        New copy() and append() methods have been added to AbstractConfiguration.
+        They replace the methods with the same names in ConfigurationUtils,
+        which do not handle all features of AbstractConfiguration properly (e.g.
+        list delimiters in property values are incorrectly treated). To avoid
+        such problems, the new methods should be used.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-291">
+        The addNodes() method of hierarchical file-based configurations now
+        correctly triggers an auto save.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-287">
+        HierarchicalConfiguration.addNodes() now resets the reference property
+        of all nodes to be added. This fixes a problem with XMLConfiguration,
+        which now detects the added nodes as new and treats them correctly when
+        the configuration is saved.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-285">
+        DefaultConfigurationBuilder will now notify registered error listeners
+        about optional configuration sources that could not be created. Before
+        exceptions thrown by optional configurations were swallowed
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-283">
+        ConfigurationUtils.convertToHierarchical() now correctly deals with
+        property values containing escaped list delimiters. This also affects
+        CombinedConfiguration when sub configurations with such property values
+        are contained.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-275">
+        AbstractConfiguration.addProperty() now correctly deals with list and
+        array properties if delimiter parsing is disabled.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-282">
+        The default expression engine used by HierarchicalConfiguration
+        instances is now lazily initialized. This avoids NullPointerExceptions
+        in certain server environments after a redeploy.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-281">
+        Cycles in the JNDI tree no longer cause a stack overflow in
+        JNDIConfiguration.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-277">
+        The base implementation of clear() in AbstractConfiguration now checks
+        for a potential UnsupportedOperationException when iterating over the
+        existing properties.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-280"
+        due-to="Roman Kurmanowytsch">
+        Using file-based configurations in auto-save mode together with a
+        reloading strategy could cause data loss. This has been fixed.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-279">
+        A PropertiesConfiguration that was created from a non existing file
+        lost its content when it was saved. This problem has been solved.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-215">
+        A new getSource() method was added to CompositeConfiguration and
+        CombinedConfiguration, which returns the child configuration, in which
+        a given property is defined.
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-274">
+        PropertiesConfiguration now supports escaping the escape character for
+        list delimiters.
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-269">
+        PropertiesConfiguration no longer escapes the list delimiter on saving
+        if the list delimiter has been disabled.
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-270">
+        List properties and properties containing interpolated variables
+        are now properly saved by INIConfiguration.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-268">
+        When delimiter parsing was disabled for XMLConfiguration, saving and
+        loading the configuration accidently added escape characters to properties
+        containing the list delimiter character. This has been fixed. It is now
+        also possible to escape the escape character itself.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-253">
+        The return value of FileConfiguration.getFile() is now always
+        consistent with the result of getURL().
+      </action>
+      <action dev="ebourg" type="update">
+        INIConfiguration uses the platform's specific line separator instead
+        of the Windows line separator.
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-267">
+        INIConfiguration flushes the output at the end of a save operation.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-265">
+        For hierarchical file-based configurations the auto-save mechanism is
+        now also triggered if a subnode configuration is changed. In such a case
+        the new event type EVENT_SUBNODE_CHANGED will be sent to registered
+        listeners.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-266" due-to="Tobias Noebel">
+        ConfigurationInterpolator now also invokes the default lookup object for
+        variables with a prefix that could not be resolved by their associated
+        lookup object.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-264">
+        A SubnodeConfiguration per default does not see certain changes of its
+        parent configuration (e.g. reloads). With a new boolean parameter of
+        HierarchicalConfiguration's configurationAt() method a mode can be
+        enabled, in which the subnode configuration checks for such changes and
+        reconstructs itself if necessary.
+      </action>
+      <action dev="ebourg" type="fix">
+        byte[] properties are properly saved as data fields in the plist
+        configurations (PropertyListConfiguration and XMLPropertyListConfiguration).
+      </action>
+      <action dev="ebourg" type="add">
+        DataConfiguration now supports java.net.InetAddress,
+        javax.mail.internet.InternetAddress, and Java 5 enumeration types.
+        Properties are converted to these types using the new generic getters.
+      </action>
+      <action dev="ebourg" type="fix">
+        The object getters in DataConfiguration with no default value
+        (i.e getURL(key)) now throw a NoSuchElementException if the flag
+        throwExceptionOnMissing is set.
+      </action>
+      <action dev="ebourg" type="add">
+        Generic get methods have been added to DataConfiguration (get(),
+        getArray() and getList())
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-263">
+        XMLConfiguration used to drop attributes when an element's value was a
+        list. This has been fixed.
+      </action>
+      <action dev="ebourg" type="add" issue="CONFIGURATION-249">
+        File configurations can now be saved to FTP URLs, or any other URL
+        protocol supporting data output.
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-180">
+        Fixed a potential issue in DatabaseConfiguration where an error on
+        closing a statement would prevent the connection from being closed.
+      </action>
+      <action dev="ebourg" type="add" issue="CONFIGURATION-261">
+        Date objects are now supported in ASCII plist files.
+      </action>
+      <action dev="ebourg" type="update">
+        XMLPropertyListConfiguration no longer requires commons-digester and
+        commons-beanutils to work.
+      </action>
+      <action dev="ebourg" type="update">
+        Fixed INIConfiguration to handle the quoted values and the lines
+        containing a value and a comment.
+      </action>
+    </release>
+
+    <release version="1.4" date="2007-04-08" description="Improved interpolation, configuration for INI files, reloading strategy triggered with JMX, bug fixes.">
+      <action dev="oheger" type="update" issue="CONFIGURATION-256">
+        MapConfiguration and the web-based configurations now treat strings
+        that contain an escaped list delimiter correctly: The escape character
+        will be removed, so that for instance "foo\,bar" becomes "foo,bar".
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-255">
+        DatabaseConfiguration now handles list delimiters in property values
+        correctly.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-254" due-to="Carsten Kaiser">
+        After cloning a XMLConfiguration there was still a connection to the
+        original configuration. So when the clone was modified and then saved
+        the content of the original configuration was written. This has now
+        been fixed.
+      </action>
+      <action dev="oheger" type="update">
+        Class loading in BeanHelper is now done using ClassUtils of Commons
+        Lang.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-237" due-to="Nicolas de Loof">
+        With ManagedReloadingStrategy a new reloading strategy for file-based
+        configurations was added that can be triggered through JMX.
+      </action>
+      <action dev="oheger" type="update">
+        The dependencies to Commons Lang, Commons Collections, and Commons Digester
+        are updated to use the recent available version. However older versions
+        will still work.
+      </action>
+      <action dev="oheger" type="add">
+        A pom for maven 2 was added.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-252">
+        ConfigurationUtils.getFile() now always checks first whether the passed
+        in file name is absolute. If it is, this file will be returned. This
+        prevents that on Unix under certain circumstances absolute file names
+        are interpreted as relative ones.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-251">
+        The dependency to xml-apis was changed to the version 1.0.b2. The so
+        far used version 2.0.2 is reported to be bogus.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-245">
+        In addition to configuration event listeners now so-called configuration
+        error listeners are supported. These listeners are notified about
+        internal errors that had been logged and swallowed by privious versions.
+        The new enableRuntimeExceptions() method of ConfigurationUtils
+        registers a special error listener at the passed in configuration that
+        generates a runtime exception when an error event is received.
+      </action>
+      <action dev="oheger" type="add">
+        AbstractConfiguration now allows to set an instance specific logger
+        using the setLogger() method. This gives clients more control over a
+        configuration's logging behavior.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-155">
+        SubsetConfiguration and CompositeConfiguration were updated to fully
+        support an instance specific list delimiter. Concerning splitting of
+        string properties that contain a list delimiter character, these
+        classes now behave like a "normal" configuration.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-192">
+        Variable interpolation features have been improved. A variable can now
+        have the form ${prefix:variableName} where the prefix
+        defines the type of the variable. The standard types sys for
+        system properties and const for constants are supported.
+        Variables without a prefix are treated as references to other
+        configuration properties (which is compatible to earlier versions).
+      </action>
+      <action dev="oheger" type="update">
+        Commons Configuration now depends on Commons Lang 2.2. Some features
+        of Lang's new text package are used.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-244">
+        The number of dependencies needed for DefaultConfigurationBuilder was
+        reduced by letting some of the default configuration providers resolve
+        their classes per reflection.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-240">
+        File-based configurations with a reloading strategy did not work well
+        together with CombinedConfiguration because the reloading strategy is
+        only checked when its associated configuration is accessed (which does
+        not happen when only the combined configuration is queried).
+        As a workaround CombinedConfiguration now provides the boolean
+        forceReloadCheck property. If this is set to true, all contained
+        configurations will be triggered when a property is queried. This will
+        cause a reload if necessary.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-243">
+        Configuration declarations in the configuration definition file for
+        DefaultConfigurationBuilder that are marked as optional now support a
+        new attribute config-forceCreate. If this attribute is set
+        to true and the initialization of the configuration fails,
+        DefaultConfigurationBuilder tries to add an empty configuration of the
+        correct type to the resulting combined configuration. Before this
+        change optional configurations that caused errors were never added to
+        the combined configuration.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-241">
+        CompositeConfiguration.clearProperty() now generates the correct
+        update events.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-242">
+        The configuration returned by HierarchicalConfiguration.subset()
+        performed variable interpolation only in the keys that belong to the
+        subset. Now the parent configuration is searched, too, to resolve the
+        value of the referenced property. This is consistent with the way
+        SubnodeConfiguration works.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-234">
+        DefaultConfigurationBuilder now internally uses the standard expression
+        engine for hierarchical configurations. So the dependency to Commons
+        JXPath is no more needed when this class is used. Note that this change
+        has some impact on existing code that manually sets properties before
+        the combined configuration is created; this code must now be adapted to
+        the changed syntax of property keys.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-236">
+        HierarchicalConfiguration and some of its sub classes now define a
+        copy constructor.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-197" due-to="Trevor Charles Miller">
+        A new configuration class for windows ini files was added.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-229">
+        For file-based configurations loaded by ConfigurationFactory the load()
+        method was called before all of the properties specified by attributes
+        of the XML element have been initialized. Now load() is called after
+        property initialization.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-235">
+        Interpolation of non string values did not work when SubsetConfiguration
+        was involved. This has now been fixed.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-227">
+        The compatibility of ConfigurationDynaBean with other configuration types
+        than those that inherit from BaseConfiguration was improved.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-233" due-to="Rainer Jung">
+        The getList() method of CompositeConfiguration does now fully support
+        variable interpolation. So it is possible to refer to a variable in
+        one (sub) configuration that is defined in another configuration.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-230">
+        XPathExpressionEngine used to create wrong keys for attribute nodes.
+        This caused some operations on XMLConfiguration to fail when such an
+        expression engine was set (e.g. reloading). Now correct keys for
+        attributes are constructed.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-228">
+        Some of the methods of file-based hierarchical configurations (e.g.
+        subset() or configurationAt()) did not take an eventually set reloading
+        strategy into account. This is now fixed by overriding the internal
+        fetchNodeList() method in AbstractHierarchicalFileConfiguration and
+        letting it always check for a reload.
+      </action>
+    </release>
+
+    <release version="1.3" date="2006-09-24">
+    </release>
+
+    <release version="1.3-rc2" date="2006-09-03">
+      <action dev="oheger" type="update" issue="CONFIGURATION-223" due-to="Gabriele Garuglieri">
+        AbstractFileConfiguration now overrides addProperty() and setProperty()
+        instead of addPropertyDirect() to implement the auto save feature.
+        This was necessary to properly integrate PropertiesConfigurationLayout.
+        It has also the advantage that an auto save is triggered only once if
+        multi-valued properties are involved (before a save operation was
+        performed for each property value).
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-222" due-to="Gabriele Garuglieri">
+        The new PropertiesConfigurationLayout class broke the save() operation
+        of PropertiesConfiguration when an instance was newly created and
+        populated in memory. This is fixed now by ensuring that a layout object
+        is immediately created and registered as event listener at the
+        configuration.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-221" due-to="Rainer Jung">
+        ConfigurationFactory now supports variables in its configuration
+        definition files. These variables are resolved using system properties
+        and have the typical ${} syntax.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-216" due-to="Gabriele Garuglieri">
+        There were still some problems with resolving relative paths when
+        configuration files are loaded from classpath. This fix addresses these
+        issues.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-220">
+        DataConfiguration.getDateArray() used to ignore the format argument.
+        This was fixed.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-219">
+        PropertiesConfiguration now defines its own clone() method to handle
+        the associated PropertiesConfigurationLayout object correctly.
+      </action>
+    </release>
+
+    <release version="1.3-rc1" date="2006-07-30">
+      <action dev="oheger" type="update" issue="CONFIGURATION-217">
+        The dependency to servletapi was updated from version 2.3 to version
+        2.4, but version 2.3 will still work.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-104">
+        A new class PropertiesConfigurationLayout was introduced whose task is
+        to preserve the structure (e.g. comments, blanc lines) of a file
+        loaded by PropertiesConfiguration. Each PropertiesConfiguration
+        object is now associated with such a layout object. A saved properties
+        file will look very similar to its original.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-145">
+        clone() methods have been added to BaseConfiguration, AbstractFileConfiguration,
+        MapConfiguration, CompositeConfiguration, and CombinedConfiguration.
+        So the most important Configuration implementations now support
+        cloning. To ConfigurationUtils an utility method cloneConfiguration()
+        was added that allows to conveniently clone a configuration.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-216">
+        If a configuration file was to be loaded from classpath, the
+        constructor of AbstractFileConfiguration dropped the file's path. The
+        path is now taken into account.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-214">
+        The getter methods for numeric data types in AbstractConfiguration now
+        support conversions between different Number types, e.g. you can now
+        call getLong(key) when key points to an Integer value.
+      </action>
+      <action dev="oheger" type="add">
+        The new class DefaultConfigurationBuilder was added as an alternative to
+        ConfigurationFactory. It provides some more features and creates a
+        CombinedConfiguration object
+      </action>
+      <action dev="oheger" type="add">
+        The new class CombinedConfiguration was added as a hierarchical
+        alternative to CompositeConfiguration.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-143">
+        Support for low-level configuration events was added to all classes
+        derived from AbstractConfiguration. The major part of this is handled
+        by the new super class EventSource of AbstractConfiguration.
+      </action>
+      <action dev="oheger" type="add">
+        A new method convertToHierarchical() was added to ConfigurationUtils,
+        which is able to convert an arbitrary configuration object into a
+        hierarchical configuration.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-63">
+        Loading of file-based configurations no longer throws a NullPointerException
+        in setups where the thread context class loader is not set.
+      </action>
+      <action dev="oheger" type="update">
+        The dependency to dom4j was removed; it was only used by two test classes,
+        which have been re-written.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-100">
+        XMLConfiguration used to drop the DOCTYPE declaration when saving the
+        configuration. It is now able to extract the DTD's public and system ID
+        and write them back (more complex DOCTYPE declarations are still not supported).
+        With the new methods setSystemID() and setPublicID(), the DOCTYPE
+        declaration can be configured.
+      </action>
+      <action dev="ebourg" type="add" issue="CONFIGURATION-178">
+        Added two new constructors in CompositeConfiguration accepting a
+        collection of configurations as a parameter.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-186">
+        (Basic) Support for declaring beans in configuration files was added.
+        Some new classes in the beanutils package allow to create instances from
+        these declarations.
+      </action>
+      <action dev="oheger" type="update">
+        The implementation of the interpolation features have been extracted out
+        off AbstractConfiguration and moved to PropertyConverter. The
+        interpolateHelper() method of AbstractConfiguration is now deprectated
+        and will not be called any more during interpolation.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-182">
+        A new method configurationsAt() was added to HierarchicalConfiguration
+        that provides a convenient way of iterating over complex list-like
+        structures without the need of manually constructing configuration keys
+        with indices.
+      </action>
+      <action dev="oheger" type="add">
+        A new class SubnodeConfiguration was introduced that wraps a configuration
+        node of a HierarchicalConfiguration. All operations performed on this
+        configuration use this wrapped node as root. The new configurationAt()
+        method of HierarchicalConfiguration returns such a SubnodeConfiguration
+        for a specified sub node.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-173">
+        With XPathExpressionEngine an expression engine for hierarchical
+        configurations is now available that can evaluate XPATH expressions in
+        property keys. This expression engine implementation is based on
+        Commons JXPath, which is now declared as a new dependency (but at
+        runtime it is only needed if the XPathExpressionEngine class is used).
+      </action>
+      <action dev="oheger" type="add">
+        The code for interpreting property keys was refactored out off
+        HierarchicalConfiguration. Instead this class now supports pluggable
+        expression engines (using the setExpressionEngine() method). So it is
+        possible to plug in different expression languages. A default expression
+        engine is provided that understands the native expression language used
+        by hierarchical configurations in older versions. During the process of
+        this refactoring some methods of HierarchicalConfiguration have been
+        deprecated; they will not be called any more when searching or adding
+        properties. These are the following: createAddPath(), fetchAddNode(),
+        findLastPathNode(), findPropertyNodes().
+      </action>
+      <action dev="oheger" type="update">
+        A larger refactoring was performed on the inner Node class of
+        HierarchicalConfiguration: A ConfigurationNode interface was extracted
+        for which a default implementation (DefaultConfigurationNode) is
+        provided. HierarchicalConfiguration.Node now extends this default
+        implementation. The new ConfigurationNode interface defines some more
+        methods than the Node class did originally for conveniently dealing with
+        sub nodes and attributes. HierarchicalConfiguration now uses the new
+        type ConfigurationNode whereever possible. Some methods dealing with
+        Node objects have been deprecated and replaced by versions operating on
+        ConfigurationNode objects instead.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-155" due-to="Jorge Ferrer">
+        All configuration classes derived from AbstractConfiguration now allow
+        to set an instance specific list delimiter. This can be done through
+        the new method setListDelimiter(). As before it is possible to define
+        a default list delimiter, which will be used if no instance specific
+        delimiter is set. This can be done using the new setDefaultListDelimiter()
+        method (the methods get/setDelimiter() have been deprecated). With the
+        new setDelimiterParsingDisabled() method parsing of lists can be
+        disabled at all.
+      </action>
+    </release>
+
+    <release version="1.2" date="2005-12-17">
+    </release>
+
+    <release version="1.2-rc3" date="2005-12-07">
+      <action dev="oheger" type="update">
+        Commons Configuration now declares a dependency to Xalan. As with
+        Xerces this dependency is only needed for JDK 1.3. It was introduced
+        in a process of making Configuration buildable on a JDK 1.3. Documentation
+        about the build process was also added.
+      </action>
+      <action dev="oheger" type="update">
+        The dependency to Commons Beanutils Collections was unnecessary and
+        thus removed.
+      </action>
+      <action dev="oheger" type="update">
+        Commons Configuration now depends on Commons Digester 1.6 instead of 1.5.
+        (This was done only to pick up the latest available release of digester.)
+      </action>
+    </release>
+
+    <release version="1.2-rc2" date="2005-11-23">
+      <action dev="oheger" type="update" issue="CONFIGURATION-2">
+        ConfigurationDynaBean now implements the java.util.Map interface (as
+        was stated in the javadocs). This was done by deriving the class from
+        ConfigurationMap.
+      </action>
+    </release>
+
+    <release version="1.2-rc1" date="2005-11-11">
+      <action dev="oheger" type="update" issue="CONFIGURATION-33">
+        The reload() method in AbstractFileConfiguration was updated to prevent
+        reentrant invocation, which may be caused by some methods when they
+        are called during a reloading operation.
+      </action>
+      <action dev="ebourg, oheger" type="update">
+        AbstractHierarchicalFileConfiguration, a new base class for file based
+        hierarchical configurations, was introduced. XMLConfiguration now
+        extends this class.
+      </action>
+      <action dev="oheger" type="update" due-to="Kay Doebl" issue="CONFIGURATION-41">
+        XMLConfiguration now prints the used encoding in the xml declaration of
+        generated files. In earlier versions always the default encoding was
+        written. PropertiesConfiguration now always uses the platform specific
+        line separator when saving files.
+      </action>
+      <action dev="ebourg" type="update" issue="CONFIGURATION-8">
+        PropertiesConfiguration now translates properly the escaped unicode
+        characters (like \u1234) used in the property keys. This complies with
+        the specification of java.util.Properties.
+      </action>
+      <action dev="ebourg" type="update" issue="CONFIGURATION-123">
+        ConfigurationConverter.getProperties() now uses the delimiter of the
+        specified configuration to convert the list properties into strings.
+      </action>
+      <action dev="ebourg" type="update" issue="CONFIGURATION-123">
+        The interpolation of variables (${foo}) is now performed in all property
+        getters of AbstractConfiguration and DataConfiguration. As a side effect
+        the Properties object returned by ConfigurationConverter.getProperties()
+        contains only interpolated values.
+      </action>
+      <action dev="ebourg" type="update" issue="CONFIGURATION-35">
+        PropertiesConfiguration now uses the ISO-8859-1 encoding by default
+        instead of the system encoding to comply with the specification of
+        java.util.Properties.
+      </action>
+      <action dev="ebourg" type="update" issue="CONFIGURATION-44">
+        JNDIConfiguration no longer logs an error when attempting to get
+        a property that doesn't exist in the configuration.
+      </action>
+      <action dev="ebourg" type="update" issue="CONFIGURATION-99">
+        Attempting to load a configuration from a directory instead of a file
+        will now throw a ConfigurationException.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-28">
+        If a multi-valued property was involved in an interpolation operation,
+        AbstractConfiguration created a string representation of the list of all
+        values. This was changed to only use the first value, which makes more
+        sense in this context and is consistent with other getters for single
+        valued properties.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-83">
+        If an include file with a relative path cannot be found in the base
+        path, PropertiesConfiguration now also tries to resolve it based on its
+        own location.
+      </action>
+      <action dev="ebourg" type="update" issue="CONFIGURATION-117" due-to="Steve Bate">
+        Fixed MapConfiguration to store the list of values added under a same
+        key instead of the last value added.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-80">
+        Fixed a bug in the handling of relative file names in ConfigurationFactory:
+        In most cases relative file names were not resolved relative to the
+        location of the configuration definition file as stated in the documentation.
+        This behavior was now changed to always be in sync with the documentation.
+        This may have an impact on existing code which uses workarounds for
+        the erroneous resolving mechanism.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-6">
+        Empty elements or elements whose content consists only of comments or
+        whitespace are now taken into account by XMLConfiguration. They are
+        added to the configuration; their value is an empty string.
+      </action>
+      <action dev="oheger" type="add">
+        XMLConfiguration now sets a valid system id in the InputSource used for
+        loading files. This enables XML parsers to correctly resolve relative
+        files, e.g. DTDs.
+      </action>
+      <action dev="ebourg" type="update" issue="CONFIGURATION-74">
+        getKeys() in HierarchicalConfiguration now returns the keys in the same order the properties were inserted.
+      </action>
+      <action dev="ebourg" type="update">
+        Commons Configuration now depends on Commons Collections 3.1 instead of 3.0
+      </action>
+      <action dev="ebourg" type="add" issue="CONFIGURATION-195">
+        New configurations implementing the "property list" format used in
+        NeXT/OpenStep and its XML variant used in Mac OS X.
+        (PropertyListConfiguration and XMLPropertyListConfiguration)
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-97">
+        Resolved some issues with XMLConfiguration and properties containing
+        the delimiter character. These properties are now correctly treated,
+        escaping the delimiter will work, too.
+      </action>
+      <action dev="ebourg" type="add">
+        Added support for XMLPropertiesConfiguration in ConfigurationFactory.
+        A <properties> element will generate a XMLPropertiesConfiguration
+        if the filename ends with ".xml".
+      </action>
+      <action dev="ebourg" type="add" issue="CONFIGURATION-184">
+        PropertiesConfiguration now supports escaped key/value separators in the keys
+        (i.e foo\:key = bar).
+      </action>
+      <action dev="ebourg" type="add" issue="CONFIGURATION-166">
+        PropertiesConfiguration now supports all key/value separators supported by java.util.Properties
+        ('=', ':' and white space characters).
+      </action>
+      <action dev="ebourg" type="update">
+        Commons Configuration now depends on Commons Lang 2.1 instead of 2.0
+      </action>
+      <action dev="ebourg" type="update" issue="CONFIGURATION-207">
+        Comment lines for PropertiesConfiguration can start with the '!' char (compatibility with java.util.Properties).
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-84">
+        Because ConfigurationUtils.copy() does not fully support hierarchical
+        configurations a clone() method was added to HierarchicalConfiguration
+        that can be used instead.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-206">
+        XMLConfiguration now provides some support for validating XML
+        documents. With the setValidating() method DTD validation can be
+        enabled. It is also possible to set a custom DocumentBuilder allowing
+        a caller to perform enhanced configuration of the XML loading process.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-121">
+        AbstractFileConfiguration now always sets a valid base path if the
+        configuration file could be located. This allows PropertiesConfiguration
+        to resolve include files even when loaded from class path.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-85">
+        Updated XMLConfiguration to correctly deal with properties containing
+        dots in their names. Such properties could not be accessed before.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-9">
+        PropertiesConfiguration's handling of backslash characters at the end
+        of line was incorrect when there was an even number of trailing
+        backslashes. This is now fixed.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-130">
+        Fixed a problem related to file based configurations that are loaded
+        from a URL which is application/x-www-form-urlencoded: the save() method
+        would store such files at a wrong location.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-50">
+        Updated FileChangedReloadingStrategy to use the file based configuration's
+        source URL to find the file to watch. Before that it was possible that
+        the strategy checked the wrong file. For configuration files loaded
+        from a jar FileChangedReloadingStrategy now checks the jar file itself
+        for changes. Finally a bug was fixed which caused the strategy to
+        check the watched file's last change date on every invocation of its
+        reloadingRequired() method ignoring its refresh delay. Thanks to Jorge
+        Ferrer.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-62">
+        Fixed a bug in the collaboration between XMLConfiguration and its
+        reloading strategy: The configuration did not check its reloading
+        strategy, so no reload was performed.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-119">
+        Disabled auto save mode during PropertiesConfiguration.load(). Prior
+        it was possible that the properties file to be loaded was immideately
+        overwritten.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-89">
+        Under certain circumstances it was possible that a reloading strategy
+        set for PropertiesConfiguration interferred with the save() method causing
+        the configuration file to be erased. This has now been fixed.
+      </action>
+      <action dev="oheger" type="update" due-to="Jamie M. Guillemette" issue="CONFIGURATION-94">
+        AbstractFileConfiguration now stores the URL of the config file in the
+        load() methods. This URL is reused by the save() method to ensure that
+        the same file is written.
+      </action>
+      <action dev="ebourg" type="update" due-to="Alistair Young">
+        XMLPropertiesConfiguration no longer depends on Digester to parse the
+        configuration file, it's now implemented with a pure SAX parser.
+      </action>
+      <action dev="oheger" type="update" due-to="Mi Zhang" issue="CONFIGURATION-49">
+        Fixed a bug which causes XMLConfiguration.save to lose attribute values
+        under some circumstances. The clear() method now also ensures that the
+        associated DOM document is always cleared.
+      </action>
+      <action dev="ebourg" type="update" due-to="Kunihara Tetsuya" issue="CONFIGURATION-13">
+        XMLConfiguration now parse the configuration using the encoding
+        declared in the XML header instead of the OS default encoding.
+      </action>
+      <action dev="ebourg" type="update" due-to="Zsolt Koppany">
+        XMLConfiguration now uses the delimiter set by setDelimiter(char).
+      </action>
+    </release>
+
+    <release version="1.1" date="2005-04-02">
+      <action dev="ebourg" type="update" issue="CONFIGURATION-134">
+        Fixed a ConcurrentModificationException thrown when calling clear()
+        on a SubsetConfiguration applied to a BaseConfiguration.
+      </action>
+      <action dev="ebourg" type="update" issue="CONFIGURATION-81">
+        The resolveContainerStore() method in AbstractConfiguration now works
+        properly with arrays of objects and arrays of primitives. This means
+        it is possible to store an array of value in the configuration and
+        retrieve the first element with the getString(), getInt()... methods.
+      </action>
+    </release>
+
+    <release version="1.1-rc2" date="2005-03-06">
+      <action dev="oheger" type="update" issue="CONFIGURATION-118">
+        Updated documentation for FileConfiguration's load() methods. Fixed a
+        problem in XMLConfiguration with the output of the save() method when
+        multiple files were loaded.
+      </action>
+      <action dev="ebourg" type="update">
+        Fixed a bug in FileChangedReloadingStrategy preventing the detection
+        of a file change in some cases.
+      </action>
+      <action dev="ebourg" type="update">
+        Changed getXXXArray() and getXXXList() in DataConfiguration to return
+        an empty array/list for empty values.
+      </action>
+      <action dev="ebourg" type="update" issue="CONFIGURATION-58">
+        Fixed getLongArray(), getFloatArray() and getDoubleArray() in DataConfiguration,
+        the values were cast into integers.
+      </action>
+    </release>
+
+    <release version="1.1-rc1" date="2005-02-13">
+      <action dev="oheger" type="add" issue="CONFIGURATION-88">
+        ConfigurationFactory now always configures digester to use the context
+        classloader. This avoids problems in application server environments,
+        which use their own version of digester. Thanks to Mike Colbert for the
+        patch!
+      </action>
+      <action dev="ebourg" type="add" issue="CONFIGURATION-148">
+        Added a new configuration, XMLPropertiesConfiguration, supporting the
+        new XML format for java.util.Properties introduced in Java 1.5.
+        A 1.5 runtime is not required to use this class.
+      </action>
+      <action dev="ebourg" type="add" issue="CONFIGURATION-190">
+        Added a comment header to PropertiesConfiguration. The header is not
+        parsed when the file is loaded yet.
+      </action>
+      <action dev="ebourg" type="add">
+        Added the setEncoding(String) and the getEncoding() methods to the
+        FileConfiguration interface to control the encoding of the
+        configuration file.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-210">
+        Access to the top level element of the XML document is now provided. For
+        newly created configurations this element can be changed before the
+        document is written.
+      </action>
+      <action dev="oheger" type="update" issue="CONFIGURATION-168">
+        Merged the two XML related configuration classes into one new class
+        XMLConfiguration. This new class should provide the best of its
+        ancestors.
+      </action>
+      <action dev="ebourg" type="update">
+        Replaced the PropertyTokenizer inner class in AbstractConfiguration
+        with the split method in PropertyConverter. Also moved the method
+        building an iterator on the elements of a composite value in
+        PropertyConverter as toIterator().
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-15">
+        Some cleanup of the handling of the base path in file based configurations.
+        The base path is now always taken into account.
+      </action>
+      <action dev="ebourg" type="fix">
+        Calling getProperties on a JNDIConfiguration no longer throws an
+        UnsupportedOperationException.
+      </action>
+      <action dev="ebourg" type="remove">
+        Removed the getPropertyDirect method from AbstractConfiguration,
+        concrete configurations now implement directly the getProperty method
+        from the Configuration interface.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-187">
+        Added implementation of a save() method for HierarchicalXMLConfiguration.
+      </action>
+      <action dev="ebourg" type="update">
+        Constructing a file based configuration with a File no longer throws
+        an exception when the file doesn't exist.
+      </action>
+      <action dev="ebourg" type="add">
+        Saving a configuration now creates the path to the file if it doesn't exist.
+      </action>
+      <action dev="ebourg" type="update" issue="CONFIGURATION-45">
+        AbstractFileConfiguration.save(File) no longer fails silently when
+        an error occurs, a ConfigurationException is thrown instead.
+      </action>
+      <action dev="ebourg" type="fix">
+        ConfigurationUtils.locate() now checks if the URL based resources exist.
+        This fixes a bug preventing configuration files from being found if
+        the configuration descriptor is in a JAR file (reported by Grant Ingersoll).
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-96">
+        Fixed NPE that were caused in the constructors of file based
+        configurations if an invalid file name was specified.
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-162">
+        Added support for optional configuration sources in definition files for
+        ConfigurationFactory. A new optional attribute allows to specify whether a
+        configuration source is mandatory or optional.
+      </action>
+      <action dev="ebourg" type="fix">
+        JNDIConfiguration.getKeys() now returns an empty iterator instead of
+        throwing a ConfigurationRuntimeException when a NamingException occurs.
+        The NamingExceptions are now logged.
+      </action>
+      <action dev="ebourg" type="fix">
+        DatabaseConfiguration.isEmpty() now returns true if an SQLException occurs.
+      </action>
+      <action dev="ebourg" type="add">
+        Added two methods copy(Configuration, Configuration) and
+        append(Configuration, Configuration) in ConfigurationUtils to copy
+        properties between configurations.
+      </action>
+      <action dev="ebourg" type="update">
+        Moved the constructors implementations from PropertiesConfiguration and
+        XMLConfiguration to AbstractFileConfiguration.
+      </action>
+      <action dev="epugh" type="remove">
+        Remove deprecated getVector() implementations.
+      </action>
+      <action dev="ebourg" type="add" issue="CONFIGURATION-147">
+        File based configurations can now be automatically reloaded when the
+        underlying file is modified.
+      </action>
+      <action dev="ebourg" type="add" issue="CONFIGURATION-156">
+        Added a clear() method to the Configuration interface to remove
+        all properties.
+      </action>
+      <action dev="ebourg" type="add" issue="CONFIGURATION-208">
+        Added a SystemConfiguration wrapping the system properties.
+        ConfigurationFactory recognizes the corresponding <system/>
+        element.
+      </action>
+      <action dev="ebourg" type="add">
+        Added a MapConfiguration to turn any Map into a Configuration. The
+        getConfiguration() methods in ConfigurationConverter now use
+        MapConfiguration, as a result the Configuration returned is always
+        synchronized with the underlying Properties or ExtendedProperties,
+        changes made to the Configuration are available in the Properties,
+        and reciprocally.
+      </action>
+      <action dev="ebourg" type="add" issue="CONFIGURATION-146">
+        The "autoSave" feature of XMLConfiguration has been generalized
+        to all file based configurations.
+      </action>
+      <action dev="ebourg" type="add" issue="CONFIGURATION-191">
+        Numeric properties can now be specified in hexadecimal format,
+        for example "number = 0xC5F0".
+      </action>
+      <action dev="oheger" type="fix" issue="CONFIGURATION-36">
+        Fixed HierarchicalConfiguration.getKeys(String), it returned an empty
+        iterator if the prefix string contained indices.
+      </action>
+      <action dev="ebourg" type="add">
+        Added a DataConfiguration decorator providing getters for all useful
+        types found in a configuration (URL, Locale, Date, Calendar, Color,
+        lists and arrays)
+      </action>
+      <action dev="ebourg" type="add">
+        Added 5 new configurations to be used in a web environment:
+        AppletConfiguration, ServletConfiguration, ServletContextConfiguration,
+        ServletRequestConfiguration, ServletFilterConfiguration.
+      </action>
+    </release>
+
+    <release version="1.0" date="2004-10-11" description="First official release">
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-66">
+        The getStringArray() method in CompositeConfiguration now interpolates
+        the strings.
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-23">
+        SubsetConfiguration now shares the "throwExceptionOnMissing" property
+        with its parent.
+      </action>
+      <action dev="ebourg" type="fix">
+        Removed "file:" at the beginning of the base path when calling
+        setFile() on a FileConfiguration. This prevented auto saving an
+        XMLConfiguration loaded from a File (issue reported by Mark Roth).
+      </action>
+      <action dev="ebourg" type="update">
+        All NamingEnumerations in JNDIConfiguraiton are now properly closed (Suggested
+        by Eric Jung).
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-90">
+        Properties added to an XMLConfiguration are no longer duplicated in the
+        resulting XML file.
+      </action>
+    </release>
+
+    <release version="1.0-rc2" date="2004-09-24">
+      <action dev="ebourg" type="update">
+        Unified the mechanisms for loading and saving file based configurations.
+        PropertiesConfiguration, XMLConfiguration and HierarchicalXMLConfiguration
+        now implement the same FileConfiguration interface. BasePathLoader,
+        BasePathConfiguration, ClassPropertiesConfiguration and
+        BasePropertiesConfiguration have been removed.
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-22">
+        Replaced the calls to Boolean.booleanValue(boolean) in
+        AbstractConfiguration and ConfigurationDynaBean to be Java 1.3
+        compatible.
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-112">
+        Changing the prefix of a JNDIConfiguration will now reset the base context used.
+      </action>
+      <action dev="ebourg" type="add" due-to="Eric Jung">
+        The context used by JNDIConfiguration can be specified in its
+        constructor or through the setContext() method. The context can be
+        accessed with the getContext() method which is now public.
+      </action>
+      <action dev="henning" type="add">
+        Make the behaviour on missing properties for the get methods that
+        return objects configurable. A property throwExceptionOnMissing
+        can be set and then the getters throw an NoSuchElementException.
+        The old default behaviour of returning a null value has
+        been restored.
+      </action>
+      <action dev="epugh" type="add" issue="CONFIGURATION-151">
+        Allow configurations extending AbstractConfiguration to change the
+        delimiter used from "," to something else.
+      </action>
+      <action dev="epugh" type="fix">
+        PropertiesConfiguration.save() method has issues with preserving the filename
+      </action>
+      <action dev="epugh" type="fix" issue="CONFIGURATION-132" due-to="Mark Woodman">
+        Test cases for HierarchicalConfigurationXMLReader stores comments as text nodes.
+      </action>
+      <action dev="epugh" type="fix" issue="CONFIGURATION-183" due-to="Ricardo Gladwell">
+        Clarify for ConfigurationDynaBean that the get method should throw an
+        illegalArgumentException if there is no property specified.
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-25">
+        Fixed a ClassCastException when adding a non String property to an XMLConfiguration.
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-138" due-to="Oliver Heger">
+        Fixed the handling of attribute properties by HierarchicalConfigurationConverter.
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-125">
+        Fixed a ClassCastException thrown on adding a non string property
+        in a DatabaseConfiguration.
+      </action>
+      <action dev="henning" type="add">
+        Bring back the getVector() methods in the Configuration interface.
+        These methods are needed for "drop-on" replacement of the
+        various pre-1.0 commons-configuration snapshots and are already
+        deprecated. These methods will be removed for 1.1.
+      </action>
+    </release>
+
+    <release version="1.0-rc1" date="2004-08-14">
+      <action dev="epugh" type="add" issue="CONFIGURATION-132" due-to="Oliver Heger">
+        HierarchicalConfigurationXMLReader stores comments as text nodes.
+      </action>
+      <action dev="epugh" type="add" issue="CONFIGURATION-122" due-to="Ricardo Gladwell">
+        project.xml contains bad dependencies.
+      </action>
+      <action dev="epugh" type="add" issue="CONFIGURATION-64" due-to="Brent Worden">
+        clearXmlProperty doesn't remove list properties completely.
+      </action>
+      <action dev="epugh" type="add" issue="CONFIGURATION-183" due-to="Ricardo Gladwell">
+        new ConfigurationDynaBean.
+      </action>
+      <action dev="epugh" type="add" issue="CONFIGURATION-185" due-to="Ricardo Gladwell">
+        new ConfigurationMap and ConfigurationSet.
+      </action>
+      <action dev="epugh" type="fix" issue="CONFIGURATION-91" due-to="Ricardo Gladwell">
+        Problem adding property XMLConfiguration.
+      </action>
+      <action dev="epugh" type="remove">
+        ConfigurationXMLDocument removed until post 1.0.
+      </action>
+      <action dev="epugh" type="fix" issue="CONFIGURATION-18">
+        DatabaseConfiguration doesn't support List properties.
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-137">
+        Fixed a bug related to XMLConfiguration. Can't add a new property as an
+        attribute in XMLConfiguration.
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-116">
+        Fixed a bug related to XMLConfiguration. XMLConfiguration doesn't
+        support attribute names with a dot.
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-4">
+        Fixed a bug related to XMLConfiguration. XMLConfiguration doesn't ignore
+        comments.
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-32">
+        Fixed a bug related to XMLConfiguration. XMLConfiguration.save() doesn't
+        escape reserved characters.
+      </action>
+      <action dev="ebourg" type="add" issue="CONFIGURATION-114">
+        Added save methods in XMLConfiguration similar to PropertiesConfiguration
+        to save the configuration to another file.
+      </action>
+      <action dev="ebourg" type="update">
+        Removed the DOM4J implementations in favor of the DOM ones.
+        DOMConfiguration has been renamed to XMLConfiguration, and
+        HierarchicalDOMConfiguration to HierarchicalXMLConfiguration. The
+        elements parsed by the ConfigurationFactory have been changed
+        accordingly.
+      </action>
+      <action dev="ebourg" type="add">
+        Added a save() method to PropertiesConfiguration and save(Writer out),
+        save(OutputStream out), save(OutputStream out, String encoding) to
+        BasePropertiesConfiguration.
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-98">
+        List values are now properly stored as comma separated values in the
+        Properties object returned by ConfigurationConverter.getProperties()
+      </action>
+      <action dev="ebourg" type="update">
+        Introduced a ConversionException thrown when the value of a property is
+        not compatible the type requested. It replaces the ClassCastException
+        and the NumberFormatException thrown previously.
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-174">
+        Tokens like ${ref} in a PropertyConfiguration are now properly saved.
+      </action>
+      <action dev="ebourg" type="fix" issue="CONFIGURATION-127">
+        The getList() method of a CompositeConfiguration now returns the list
+        composed of the elements in the first matching configuration and the
+        additional elements found in the in memory configuration.
+      </action>
+      <action dev="epugh" type="fix">
+        SubsetConfiguration returns a List on getList(). AbstractConfiguration
+        wouldn't properly deal with a List, only with a Container for getList()!
+        Thanks to jschaible for the unit test.
+      </action>
+      <action dev="jschaible" type="add">
+        Direct support of XML via DOM. New classes DOMConfiguration and HierarchicalDOMConfiguration.
+      </action>
+      <action dev="jschaible" type="update">
+        Update build to not include test configuration files in resulting jar.
+      </action>
+      <action dev="ebourg" type="update">
+        Refactored JNDIConfiguration to use AbstractConfiguration.
+      </action>
+      <action dev="ebourg" type="update" issue="CONFIGURATION-76">
+        Fixed invalid subsets by refactoring out the subset logic into a SubsetConfiguration.
+      </action>
+      <action dev="oheger" type="fix">
+        Reapply the ConfigurationXMLDocument that went missing during migration out of sandbox.
+      </action>
+      <action dev="epugh" type="update">
+        Apply ASL 2.0 license.  Thanks to Jeff Painter for scripting the conversion!
+      </action>
+      <action dev="epugh" type="add">
+        Changed CompositeConfiguration to extend from AbstractConfiuration. This means that the behavior of
+        CompositeConfiguration is much similar to others like PropertiesConfiguration in handling of missing
+        keys, interpolation, etc.. Previously CompositeConfiguration had quite a few differences.
+      </action>
+      <action dev="epugh" type="update">
+        Removed "defaults" from BaseConfiguration. Defaults are now done via using a CompositeConfiguration, either
+        directly or via a ConfigurationFactory. if you want to save changes made to a Configuration, then you use
+        a CompositeConfiguration and get back the inMemoryConfiguration that has the delta of changes. Added a
+        bit of documentation on this.
+      </action>
+      <action dev="epugh" type="update" issue="CONFIGURATION-154">
+        Enhancement: Configuration Comparator.
+      </action>
+      <action dev="epugh" type="update" issue="CONFIGURATION-54">
+        BaseConfiguration: containsKey ignores default properties.
+        I have changed it so that now the defaults are paid attention to.
+      </action>
+      <action dev="ebourg" type="add">
+        The Configuration interface now supports BigDecimal and BigInteger numbers.
+      </action>
+      <action dev="epugh" type="add">
+        ConfigurationException is now thrown by public methods instead of Exception or
+        IOException or whatnot.
+      </action>
+      <action dev="ebourg" type="add">
+        For configuration based on properties files, allow characters like \n etc
+        to be escaped and unescaped.
+      </action>
+      <action dev="ebourg" type="add">
+        New DatabaseConfiguration that uses a database to store the properties.
+        It supports 2 table structures: one table per configuration (2 colums
+        key/value), one table for multiple configurations (2 columns key/value +
+        1 column for the name of the configuration).
+      </action>
+      <action dev="oheger" type="add">
+        ConfigurationFactory now supports the hierarchicalDom4j element in configuration
+        definition file
+      </action>
+      <action dev="ebourg" type="update">
+        Change all Vector objects to List objects.
+      </action>
+      <action dev="oheger" type="add">
+        ConfigurationFactory now supports two types of properties files, additional and
+        override.  Additional properties add each other together.  Override override each
+        other.  This allows you to have a single property that is either aggregated from a
+        number of sources, or have a property that is overridden according to a specific
+        order of sources.
+      </action>
+      <action dev="oheger" type="update">
+        AbstractConfiguration addProperty now delegates to an abstract addPropertyDirect
+        implemented by BaseConfiguration.
+      </action>
+      <action dev="kshaposhnikov" type="update">
+        Changed getString() method to throw a NoSuchElementException instead of "" if the
+        configuration property doesn't exist.
+      </action>
+      <action dev="kshaposhnikov" type="add">
+        Added AbstractConfiguration to make it easier to create subclasses by only
+        having to implement the methods required.
+      </action>
+      <action dev="bdunbar" type="fix">
+        ClassPropertiesConfiguration Additions:
+        Use the classloader of class that is provided by the constructor.
+        Add a constructor that indicates whether to use relative or absolute.
+        Change getPropertyStream to utilize the relative or absolute flag.
+        Add a test case that checks that absolute paths work.
+      </action>
+      <action dev="epugh" type="fix">
+        JNDIConfiguration.getKeys() Addition:
+        The JNDIConfiguration.getKeys() method was returning an unsupported
+        operation error.  However, this is an important method to have
+        supported.
+      </action>
+      <action dev="epugh" type="fix">
+        CompositeConfiguration.getKeys() Fix
+        The CompositeConfiguration.getKeys() method was returning an
+        unordered list of configuration values.  However, many apps
+        expect the order that keys are returned to be the order they
+        are added into the properties file.
+      </action>
+    </release>
+  </body>
+</document>
diff --git a/src/main/assembly/bin.xml b/src/main/assembly/bin.xml
new file mode 100644
index 0000000..6c02436
--- /dev/null
+++ b/src/main/assembly/bin.xml
@@ -0,0 +1,45 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<assembly>
+  <id>bin</id>
+  <formats>
+    <format>tar.gz</format>
+    <format>zip</format>
+  </formats>
+  <baseDirectory>${artifactId}-${commons.release.version}</baseDirectory>
+  <fileSets>
+    <fileSet>
+      <includes>
+        <include>LICENSE*</include>
+        <include>NOTICE*</include>
+        <include>RELEASE-NOTES*</include>
+        <include>pom.xml</include>
+      </includes>
+    </fileSet>
+    <fileSet>
+      <directory>target</directory>
+      <outputDirectory></outputDirectory>
+      <includes>
+        <include>*.jar</include>
+      </includes>
+    </fileSet>
+    <fileSet>
+      <directory>target/site/apidocs</directory>
+      <outputDirectory>apidocs</outputDirectory>
+    </fileSet>
+  </fileSets>
+</assembly>
diff --git a/src/main/assembly/src.xml b/src/main/assembly/src.xml
new file mode 100644
index 0000000..c47df19
--- /dev/null
+++ b/src/main/assembly/src.xml
@@ -0,0 +1,40 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<assembly>
+  <id>src</id>
+  <formats>
+    <format>tar.gz</format>
+    <format>zip</format>
+  </formats>
+  <baseDirectory>${artifactId}-${commons.release.version}-src</baseDirectory>
+  <fileSets>
+    <fileSet>
+      <includes>
+        <include>LICENSE*</include>
+        <include>NOTICE*</include>
+        <include>RELEASE-NOTES*</include>
+        <include>pom.xml</include>
+      </includes>
+    </fileSet>
+    <fileSet>
+      <directory>conf</directory>
+    </fileSet>
+    <fileSet>
+      <directory>src</directory>
+    </fileSet>
+  </fileSets>
+</assembly>
diff --git a/src/main/java/org/apache/commons/configuration/AbstractConfiguration.java b/src/main/java/org/apache/commons/configuration/AbstractConfiguration.java
new file mode 100644
index 0000000..eb4fa8a
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/AbstractConfiguration.java
@@ -0,0 +1,1309 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.lang.reflect.Array;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Properties;
+
+import org.apache.commons.configuration.event.ConfigurationErrorEvent;
+import org.apache.commons.configuration.event.ConfigurationErrorListener;
+import org.apache.commons.configuration.event.EventSource;
+import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
+import org.apache.commons.lang.BooleanUtils;
+import org.apache.commons.lang.ClassUtils;
+import org.apache.commons.lang.ObjectUtils;
+import org.apache.commons.lang.text.StrLookup;
+import org.apache.commons.lang.text.StrSubstitutor;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.impl.NoOpLog;
+
+/**
+ * <p>Abstract configuration class. Provides basic functionality but does not
+ * store any data.</p>
+ * <p>If you want to write your own Configuration class then you should
+ * implement only abstract methods from this class. A lot of functionality
+ * needed by typical implementations of the {@code Configuration}
+ * interface is already provided by this base class. Following is a list of
+ * features implemented here:
+ * <ul><li>Data conversion support. The various data types required by the
+ * {@code Configuration} interface are already handled by this base class.
+ * A concrete sub class only needs to provide a generic {@code getProperty()}
+ * method.</li>
+ * <li>Support for variable interpolation. Property values containing special
+ * variable tokens (like <code>${var}</code>) will be replaced by their
+ * corresponding values.</li>
+ * <li>Support for string lists. The values of properties to be added to this
+ * configuration are checked whether they contain a list delimiter character. If
+ * this is the case and if list splitting is enabled, the string is split and
+ * multiple values are added for this property. (With the
+ * {@code setListDelimiter()} method the delimiter character can be
+ * specified; per default a comma is used. The
+ * {@code setDelimiterParsingDisabled()} method can be used to disable
+ * list splitting completely.)</li>
+ * <li>Allows to specify how missing properties are treated. Per default the
+ * get methods returning an object will return <b>null</b> if the searched
+ * property key is not found (and no default value is provided). With the
+ * {@code setThrowExceptionOnMissing()} method this behavior can be
+ * changed to throw an exception when a requested property cannot be found.</li>
+ * <li>Basic event support. Whenever this configuration is modified registered
+ * event listeners are notified. Refer to the various {@code EVENT_XXX}
+ * constants to get an impression about which event types are supported.</li>
+ * </ul></p>
+ *
+ * @author <a href="mailto:ksh at scand.com">Konstantin Shaposhnikov </a>
+ * @author <a href="mailto:hps at intermeta.de">Henning P. Schmiedehausen </a>
+ * @version $Id: AbstractConfiguration.java 1534064 2013-10-21 08:44:33Z henning $
+ */
+public abstract class AbstractConfiguration extends EventSource implements Configuration
+{
+    /**
+     * Constant for the add property event type.
+     * @since 1.3
+     */
+    public static final int EVENT_ADD_PROPERTY = 1;
+
+    /**
+     * Constant for the clear property event type.
+     * @since 1.3
+     */
+    public static final int EVENT_CLEAR_PROPERTY = 2;
+
+    /**
+     * Constant for the set property event type.
+     * @since 1.3
+     */
+    public static final int EVENT_SET_PROPERTY = 3;
+
+    /**
+     * Constant for the clear configuration event type.
+     * @since 1.3
+     */
+    public static final int EVENT_CLEAR = 4;
+
+    /**
+     * Constant for the get property event type. This event type is used for
+     * error events.
+     * @since 1.4
+     */
+    public static final int EVENT_READ_PROPERTY = 5;
+
+    /** start token */
+    protected static final String START_TOKEN = "${";
+
+    /** end token */
+    protected static final String END_TOKEN = "}";
+
+    /**
+     * Constant for the disabled list delimiter. This character is passed to the
+     * list parsing methods if delimiter parsing is disabled. So this character
+     * should not occur in string property values.
+     */
+    private static final char DISABLED_DELIMITER = '\0';
+
+    /** The default value for listDelimiter */
+    private static char defaultListDelimiter = ',';
+
+    /** Delimiter used to convert single values to lists */
+    private char listDelimiter = defaultListDelimiter;
+
+    /**
+     * When set to true the given configuration delimiter will not be used
+     * while parsing for this configuration.
+     */
+    private boolean delimiterParsingDisabled;
+
+    /**
+     * Whether the configuration should throw NoSuchElementExceptions or simply
+     * return null when a property does not exist. Defaults to return null.
+     */
+    private boolean throwExceptionOnMissing;
+
+    /** Stores a reference to the object that handles variable interpolation.*/
+    private StrSubstitutor substitutor;
+
+    /** Stores the logger.*/
+    private Log log;
+
+    /**
+     * Creates a new instance of {@code AbstractConfiguration}.
+     */
+    public AbstractConfiguration()
+    {
+        setLogger(null);
+    }
+
+    /**
+     * For configurations extending AbstractConfiguration, allow them to change
+     * the listDelimiter from the default comma (","). This value will be used
+     * only when creating new configurations. Those already created will not be
+     * affected by this change
+     *
+     * @param delimiter The new listDelimiter
+     */
+    public static void setDefaultListDelimiter(char delimiter)
+    {
+        AbstractConfiguration.defaultListDelimiter = delimiter;
+    }
+
+    /**
+     * Sets the default list delimiter.
+     *
+     * @param delimiter the delimiter character
+     * @deprecated Use AbstractConfiguration.setDefaultListDelimiter(char)
+     * instead
+     */
+    @Deprecated
+    public static void setDelimiter(char delimiter)
+    {
+        setDefaultListDelimiter(delimiter);
+    }
+
+    /**
+     * Retrieve the current delimiter. By default this is a comma (",").
+     *
+     * @return The delimiter in use
+     */
+    public static char getDefaultListDelimiter()
+    {
+        return AbstractConfiguration.defaultListDelimiter;
+    }
+
+    /**
+     * Returns the default list delimiter.
+     *
+     * @return the default list delimiter
+     * @deprecated Use AbstractConfiguration.getDefaultListDelimiter() instead
+     */
+    @Deprecated
+    public static char getDelimiter()
+    {
+        return getDefaultListDelimiter();
+    }
+
+    /**
+     * Change the list delimiter for this configuration.
+     *
+     * Note: this change will only be effective for new parsings. If you
+     * want it to take effect for all loaded properties use the no arg constructor
+     * and call this method before setting the source.
+     *
+     * @param listDelimiter The new listDelimiter
+     */
+    public void setListDelimiter(char listDelimiter)
+    {
+        this.listDelimiter = listDelimiter;
+    }
+
+    /**
+     * Retrieve the delimiter for this configuration. The default
+     * is the value of defaultListDelimiter.
+     *
+     * @return The listDelimiter in use
+     */
+    public char getListDelimiter()
+    {
+        return listDelimiter;
+    }
+
+    /**
+     * Determine if this configuration is using delimiters when parsing
+     * property values to convert them to lists of values. Defaults to false
+     * @return true if delimiters are not being used
+     */
+    public boolean isDelimiterParsingDisabled()
+    {
+        return delimiterParsingDisabled;
+    }
+
+    /**
+     * Set whether this configuration should use delimiters when parsing
+     * property values to convert them to lists of values. By default delimiter
+     * parsing is enabled
+     *
+     * Note: this change will only be effective for new parsings. If you
+     * want it to take effect for all loaded properties use the no arg constructor
+     * and call this method before setting source.
+     * @param delimiterParsingDisabled a flag whether delimiter parsing should
+     * be disabled
+     */
+    public void setDelimiterParsingDisabled(boolean delimiterParsingDisabled)
+    {
+        this.delimiterParsingDisabled = delimiterParsingDisabled;
+    }
+
+    /**
+     * Allows to set the {@code throwExceptionOnMissing} flag. This
+     * flag controls the behavior of property getter methods that return
+     * objects if the requested property is missing. If the flag is set to
+     * <b>false</b> (which is the default value), these methods will return
+     * <b>null</b>. If set to <b>true</b>, they will throw a
+     * {@code NoSuchElementException} exception. Note that getter methods
+     * for primitive data types are not affected by this flag.
+     *
+     * @param throwExceptionOnMissing The new value for the property
+     */
+    public void setThrowExceptionOnMissing(boolean throwExceptionOnMissing)
+    {
+        this.throwExceptionOnMissing = throwExceptionOnMissing;
+    }
+
+    /**
+     * Returns true if missing values throw Exceptions.
+     *
+     * @return true if missing values throw Exceptions
+     */
+    public boolean isThrowExceptionOnMissing()
+    {
+        return throwExceptionOnMissing;
+    }
+
+    /**
+     * Returns the object that is responsible for variable interpolation.
+     *
+     * @return the object responsible for variable interpolation
+     * @since 1.4
+     */
+    public synchronized StrSubstitutor getSubstitutor()
+    {
+        if (substitutor == null)
+        {
+            substitutor = new StrSubstitutor(createInterpolator());
+        }
+        return substitutor;
+    }
+
+    /**
+     * Returns the {@code ConfigurationInterpolator} object that manages
+     * the lookup objects for resolving variables. <em>Note:</em> If this
+     * object is manipulated (e.g. new lookup objects added), synchronization
+     * has to be manually ensured. Because
+     * {@code ConfigurationInterpolator} is not thread-safe concurrent
+     * access to properties of this configuration instance (which causes the
+     * interpolator to be invoked) may cause race conditions.
+     *
+     * @return the {@code ConfigurationInterpolator} associated with this
+     * configuration
+     * @since 1.4
+     */
+    public ConfigurationInterpolator getInterpolator()
+    {
+        return (ConfigurationInterpolator) getSubstitutor()
+                .getVariableResolver();
+    }
+
+    /**
+     * Creates the interpolator object that is responsible for variable
+     * interpolation. This method is invoked on first access of the
+     * interpolation features. It creates a new instance of
+     * {@code ConfigurationInterpolator} and sets the default lookup
+     * object to an implementation that queries this configuration.
+     *
+     * @return the newly created interpolator object
+     * @since 1.4
+     */
+    protected ConfigurationInterpolator createInterpolator()
+    {
+        ConfigurationInterpolator interpol = new ConfigurationInterpolator();
+        interpol.setDefaultLookup(new StrLookup()
+        {
+            @Override
+            public String lookup(String var)
+            {
+                Object prop = resolveContainerStore(var);
+                return (prop != null) ? prop.toString() : null;
+            }
+        });
+        return interpol;
+    }
+
+    /**
+     * Returns the logger used by this configuration object.
+     *
+     * @return the logger
+     * @since 1.4
+     */
+    public Log getLogger()
+    {
+        return log;
+    }
+
+    /**
+     * Allows to set the logger to be used by this configuration object. This
+     * method makes it possible for clients to exactly control logging behavior.
+     * Per default a logger is set that will ignore all log messages. Derived
+     * classes that want to enable logging should call this method during their
+     * initialization with the logger to be used.
+     *
+     * @param log the new logger
+     * @since 1.4
+     */
+    public void setLogger(Log log)
+    {
+        this.log = (log != null) ? log : new NoOpLog();
+    }
+
+    /**
+     * Adds a special
+     * {@link org.apache.commons.configuration.event.ConfigurationErrorListener}
+     * object to this configuration that will log all internal errors. This
+     * method is intended to be used by certain derived classes, for which it is
+     * known that they can fail on property access (e.g.
+     * {@code DatabaseConfiguration}).
+     *
+     * @since 1.4
+     */
+    public void addErrorLogListener()
+    {
+        addErrorListener(new ConfigurationErrorListener()
+        {
+            public void configurationError(ConfigurationErrorEvent event)
+            {
+                getLogger().warn("Internal error", event.getCause());
+            }
+        });
+    }
+
+    public void addProperty(String key, Object value)
+    {
+        fireEvent(EVENT_ADD_PROPERTY, key, value, true);
+        addPropertyValues(key, value,
+                isDelimiterParsingDisabled() ? DISABLED_DELIMITER
+                        : getListDelimiter());
+        fireEvent(EVENT_ADD_PROPERTY, key, value, false);
+    }
+
+    /**
+     * Adds a key/value pair to the Configuration. Override this method to
+     * provide write access to underlying Configuration store.
+     *
+     * @param key key to use for mapping
+     * @param value object to store
+     */
+    protected abstract void addPropertyDirect(String key, Object value);
+
+    /**
+     * Adds the specified value for the given property. This method supports
+     * single values and containers (e.g. collections or arrays) as well. In the
+     * latter case, {@code addPropertyDirect()} will be called for each
+     * element.
+     *
+     * @param key the property key
+     * @param value the value object
+     * @param delimiter the list delimiter character
+     */
+    private void addPropertyValues(String key, Object value, char delimiter)
+    {
+        Iterator<?> it = PropertyConverter.toIterator(value, delimiter);
+        while (it.hasNext())
+        {
+            addPropertyDirect(key, it.next());
+        }
+    }
+
+    /**
+     * interpolate key names to handle ${key} stuff
+     *
+     * @param base string to interpolate
+     *
+     * @return returns the key name with the ${key} substituted
+     */
+    protected String interpolate(String base)
+    {
+        Object result = interpolate((Object) base);
+        return (result == null) ? null : result.toString();
+    }
+
+    /**
+     * Returns the interpolated value. Non String values are returned without change.
+     *
+     * @param value the value to interpolate
+     *
+     * @return returns the value with variables substituted
+     */
+    protected Object interpolate(Object value)
+    {
+        return PropertyConverter.interpolate(value, this);
+    }
+
+    /**
+     * Recursive handler for multple levels of interpolation.
+     *
+     * When called the first time, priorVariables should be null.
+     *
+     * @param base string with the ${key} variables
+     * @param priorVariables serves two purposes: to allow checking for loops,
+     * and creating a meaningful exception message should a loop occur. It's
+     * 0'th element will be set to the value of base from the first call. All
+     * subsequent interpolated variables are added afterward.
+     *
+     * @return the string with the interpolation taken care of
+     * @deprecated Interpolation is now handled by
+     * {@link PropertyConverter}; this method will no longer be
+     * called
+     */
+    @Deprecated
+    protected String interpolateHelper(String base, List<?> priorVariables)
+    {
+        return base; // just a dummy implementation
+    }
+
+    public Configuration subset(String prefix)
+    {
+        return new SubsetConfiguration(this, prefix, ".");
+    }
+
+    public void setProperty(String key, Object value)
+    {
+        fireEvent(EVENT_SET_PROPERTY, key, value, true);
+        setDetailEvents(false);
+        try
+        {
+            clearProperty(key);
+            addProperty(key, value);
+        }
+        finally
+        {
+            setDetailEvents(true);
+        }
+        fireEvent(EVENT_SET_PROPERTY, key, value, false);
+    }
+
+    /**
+     * Removes the specified property from this configuration. This
+     * implementation performs some preparations and then delegates to
+     * {@code clearPropertyDirect()}, which will do the real work.
+     *
+     * @param key the key to be removed
+     */
+    public void clearProperty(String key)
+    {
+        fireEvent(EVENT_CLEAR_PROPERTY, key, null, true);
+        clearPropertyDirect(key);
+        fireEvent(EVENT_CLEAR_PROPERTY, key, null, false);
+    }
+
+    /**
+     * Removes the specified property from this configuration. This method is
+     * called by {@code clearProperty()} after it has done some
+     * preparations. It should be overridden in sub classes. This base
+     * implementation is just left empty.
+     *
+     * @param key the key to be removed
+     */
+    protected void clearPropertyDirect(String key)
+    {
+        // override in sub classes
+    }
+
+    public void clear()
+    {
+        fireEvent(EVENT_CLEAR, null, null, true);
+        setDetailEvents(false);
+        boolean useIterator = true;
+        try
+        {
+            Iterator<String> it = getKeys();
+            while (it.hasNext())
+            {
+                String key = it.next();
+                if (useIterator)
+                {
+                    try
+                    {
+                        it.remove();
+                    }
+                    catch (UnsupportedOperationException usoex)
+                    {
+                        useIterator = false;
+                    }
+                }
+
+                if (useIterator && containsKey(key))
+                {
+                    useIterator = false;
+                }
+
+                if (!useIterator)
+                {
+                    // workaround for Iterators that do not remove the property
+                    // on calling remove() or do not support remove() at all
+                    clearProperty(key);
+                }
+            }
+        }
+        finally
+        {
+            setDetailEvents(true);
+        }
+        fireEvent(EVENT_CLEAR, null, null, false);
+    }
+
+    /**
+     * {@inheritDoc} This implementation returns keys that either match the
+     * prefix or start with the prefix followed by a dot ('.'). So the call
+     * {@code getKeys("db");} will find the keys {@code db},
+     * {@code db.user}, or {@code db.password}, but not the key
+     * {@code dbdriver}.
+     */
+    public Iterator<String> getKeys(String prefix)
+    {
+        return new PrefixedKeysIterator(getKeys(), prefix);
+    }
+
+    public Properties getProperties(String key)
+    {
+        return getProperties(key, null);
+    }
+
+    /**
+     * Get a list of properties associated with the given configuration key.
+     *
+     * @param key The configuration key.
+     * @param defaults Any default values for the returned
+     * {@code Properties} object. Ignored if {@code null}.
+     *
+     * @return The associated properties if key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an object that
+     * is not a String/List of Strings.
+     *
+     * @throws IllegalArgumentException if one of the tokens is malformed (does
+     * not contain an equals sign).
+     */
+    public Properties getProperties(String key, Properties defaults)
+    {
+        /*
+         * Grab an array of the tokens for this key.
+         */
+        String[] tokens = getStringArray(key);
+
+        /*
+         * Each token is of the form 'key=value'.
+         */
+        Properties props = defaults == null ? new Properties() : new Properties(defaults);
+        for (String token : tokens)
+        {
+            int equalSign = token.indexOf('=');
+            if (equalSign > 0)
+            {
+                String pkey = token.substring(0, equalSign).trim();
+                String pvalue = token.substring(equalSign + 1).trim();
+                props.put(pkey, pvalue);
+            }
+            else if (tokens.length == 1 && "".equals(token))
+            {
+                // Semantically equivalent to an empty Properties
+                // object.
+                break;
+            }
+            else
+            {
+                throw new IllegalArgumentException('\'' + token + "' does not contain an equals sign");
+            }
+        }
+        return props;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @see PropertyConverter#toBoolean(Object)
+     */
+    public boolean getBoolean(String key)
+    {
+        Boolean b = getBoolean(key, null);
+        if (b != null)
+        {
+            return b.booleanValue();
+        }
+        else
+        {
+            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * @see PropertyConverter#toBoolean(Object)
+     */
+    public boolean getBoolean(String key, boolean defaultValue)
+    {
+        return getBoolean(key, BooleanUtils.toBooleanObject(defaultValue)).booleanValue();
+    }
+
+    /**
+     * Obtains the value of the specified key and tries to convert it into a
+     * {@code Boolean} object. If the property has no value, the passed
+     * in default value will be used.
+     *
+     * @param key the key of the property
+     * @param defaultValue the default value
+     * @return the value of this key converted to a {@code Boolean}
+     * @throws ConversionException if the value cannot be converted to a
+     * {@code Boolean}
+     * @see PropertyConverter#toBoolean(Object)
+     */
+    public Boolean getBoolean(String key, Boolean defaultValue)
+    {
+        Object value = resolveContainerStore(key);
+
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        else
+        {
+            try
+            {
+                return PropertyConverter.toBoolean(interpolate(value));
+            }
+            catch (ConversionException e)
+            {
+                throw new ConversionException('\'' + key + "' doesn't map to a Boolean object", e);
+            }
+        }
+    }
+
+    public byte getByte(String key)
+    {
+        Byte b = getByte(key, null);
+        if (b != null)
+        {
+            return b.byteValue();
+        }
+        else
+        {
+            throw new NoSuchElementException('\'' + key + " doesn't map to an existing object");
+        }
+    }
+
+    public byte getByte(String key, byte defaultValue)
+    {
+        return getByte(key, new Byte(defaultValue)).byteValue();
+    }
+
+    public Byte getByte(String key, Byte defaultValue)
+    {
+        Object value = resolveContainerStore(key);
+
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        else
+        {
+            try
+            {
+                return PropertyConverter.toByte(interpolate(value));
+            }
+            catch (ConversionException e)
+            {
+                throw new ConversionException('\'' + key + "' doesn't map to a Byte object", e);
+            }
+        }
+    }
+
+    public double getDouble(String key)
+    {
+        Double d = getDouble(key, null);
+        if (d != null)
+        {
+            return d.doubleValue();
+        }
+        else
+        {
+            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
+        }
+    }
+
+    public double getDouble(String key, double defaultValue)
+    {
+        return getDouble(key, new Double(defaultValue)).doubleValue();
+    }
+
+    public Double getDouble(String key, Double defaultValue)
+    {
+        Object value = resolveContainerStore(key);
+
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        else
+        {
+            try
+            {
+                return PropertyConverter.toDouble(interpolate(value));
+            }
+            catch (ConversionException e)
+            {
+                throw new ConversionException('\'' + key + "' doesn't map to a Double object", e);
+            }
+        }
+    }
+
+    public float getFloat(String key)
+    {
+        Float f = getFloat(key, null);
+        if (f != null)
+        {
+            return f.floatValue();
+        }
+        else
+        {
+            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
+        }
+    }
+
+    public float getFloat(String key, float defaultValue)
+    {
+        return getFloat(key, new Float(defaultValue)).floatValue();
+    }
+
+    public Float getFloat(String key, Float defaultValue)
+    {
+        Object value = resolveContainerStore(key);
+
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        else
+        {
+            try
+            {
+                return PropertyConverter.toFloat(interpolate(value));
+            }
+            catch (ConversionException e)
+            {
+                throw new ConversionException('\'' + key + "' doesn't map to a Float object", e);
+            }
+        }
+    }
+
+    public int getInt(String key)
+    {
+        Integer i = getInteger(key, null);
+        if (i != null)
+        {
+            return i.intValue();
+        }
+        else
+        {
+            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
+        }
+    }
+
+    public int getInt(String key, int defaultValue)
+    {
+        Integer i = getInteger(key, null);
+
+        if (i == null)
+        {
+            return defaultValue;
+        }
+
+        return i.intValue();
+    }
+
+    public Integer getInteger(String key, Integer defaultValue)
+    {
+        Object value = resolveContainerStore(key);
+
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        else
+        {
+            try
+            {
+                return PropertyConverter.toInteger(interpolate(value));
+            }
+            catch (ConversionException e)
+            {
+                throw new ConversionException('\'' + key + "' doesn't map to an Integer object", e);
+            }
+        }
+    }
+
+    public long getLong(String key)
+    {
+        Long l = getLong(key, null);
+        if (l != null)
+        {
+            return l.longValue();
+        }
+        else
+        {
+            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
+        }
+    }
+
+    public long getLong(String key, long defaultValue)
+    {
+        return getLong(key, new Long(defaultValue)).longValue();
+    }
+
+    public Long getLong(String key, Long defaultValue)
+    {
+        Object value = resolveContainerStore(key);
+
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        else
+        {
+            try
+            {
+                return PropertyConverter.toLong(interpolate(value));
+            }
+            catch (ConversionException e)
+            {
+                throw new ConversionException('\'' + key + "' doesn't map to a Long object", e);
+            }
+        }
+    }
+
+    public short getShort(String key)
+    {
+        Short s = getShort(key, null);
+        if (s != null)
+        {
+            return s.shortValue();
+        }
+        else
+        {
+            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
+        }
+    }
+
+    public short getShort(String key, short defaultValue)
+    {
+        return getShort(key, new Short(defaultValue)).shortValue();
+    }
+
+    public Short getShort(String key, Short defaultValue)
+    {
+        Object value = resolveContainerStore(key);
+
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        else
+        {
+            try
+            {
+                return PropertyConverter.toShort(interpolate(value));
+            }
+            catch (ConversionException e)
+            {
+                throw new ConversionException('\'' + key + "' doesn't map to a Short object", e);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * @see #setThrowExceptionOnMissing(boolean)
+     */
+    public BigDecimal getBigDecimal(String key)
+    {
+        BigDecimal number = getBigDecimal(key, null);
+        if (number != null)
+        {
+            return number;
+        }
+        else if (isThrowExceptionOnMissing())
+        {
+            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    public BigDecimal getBigDecimal(String key, BigDecimal defaultValue)
+    {
+        Object value = resolveContainerStore(key);
+
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        else
+        {
+            try
+            {
+                return PropertyConverter.toBigDecimal(interpolate(value));
+            }
+            catch (ConversionException e)
+            {
+                throw new ConversionException('\'' + key + "' doesn't map to a BigDecimal object", e);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * @see #setThrowExceptionOnMissing(boolean)
+     */
+    public BigInteger getBigInteger(String key)
+    {
+        BigInteger number = getBigInteger(key, null);
+        if (number != null)
+        {
+            return number;
+        }
+        else if (isThrowExceptionOnMissing())
+        {
+            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    public BigInteger getBigInteger(String key, BigInteger defaultValue)
+    {
+        Object value = resolveContainerStore(key);
+
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        else
+        {
+            try
+            {
+                return PropertyConverter.toBigInteger(interpolate(value));
+            }
+            catch (ConversionException e)
+            {
+                throw new ConversionException('\'' + key + "' doesn't map to a BigInteger object", e);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * @see #setThrowExceptionOnMissing(boolean)
+     */
+    public String getString(String key)
+    {
+        String s = getString(key, null);
+        if (s != null)
+        {
+            return s;
+        }
+        else if (isThrowExceptionOnMissing())
+        {
+            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    public String getString(String key, String defaultValue)
+    {
+        Object value = resolveContainerStore(key);
+
+        if (value instanceof String)
+        {
+            return interpolate((String) value);
+        }
+        else if (value == null)
+        {
+            return interpolate(defaultValue);
+        }
+        else
+        {
+            throw new ConversionException('\'' + key + "' doesn't map to a String object");
+        }
+    }
+
+    /**
+     * Get an array of strings associated with the given configuration key.
+     * If the key doesn't map to an existing object, an empty array is returned.
+     * If a property is added to a configuration, it is checked whether it
+     * contains multiple values. This is obvious if the added object is a list
+     * or an array. For strings it is checked whether the string contains the
+     * list delimiter character that can be specified using the
+     * {@code setListDelimiter()} method. If this is the case, the string
+     * is split at these positions resulting in a property with multiple
+     * values.
+     *
+     * @param key The configuration key.
+     * @return The associated string array if key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a String/List of Strings.
+     * @see #setListDelimiter(char)
+     * @see #setDelimiterParsingDisabled(boolean)
+     */
+    public String[] getStringArray(String key)
+    {
+        Object value = getProperty(key);
+
+        String[] array;
+
+        if (value instanceof String)
+        {
+            array = new String[1];
+
+            array[0] = interpolate((String) value);
+        }
+        else if (value instanceof List)
+        {
+            List<?> list = (List<?>) value;
+            array = new String[list.size()];
+
+            for (int i = 0; i < array.length; i++)
+            {
+                array[i] = interpolate(ObjectUtils.toString(list.get(i), null));
+            }
+        }
+        else if (value == null)
+        {
+            array = new String[0];
+        }
+        else if (isScalarValue(value))
+        {
+            array = new String[1];
+            array[0] = value.toString();
+        }
+        else
+        {
+            throw new ConversionException('\'' + key + "' doesn't map to a String/List object");
+        }
+        return array;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @see #getStringArray(String)
+     */
+    public List<Object> getList(String key)
+    {
+        return getList(key, new ArrayList<Object>());
+    }
+
+    public List<Object> getList(String key, List<?> defaultValue)
+    {
+        Object value = getProperty(key);
+        List<Object> list;
+
+        if (value instanceof String)
+        {
+            list = new ArrayList<Object>(1);
+            list.add(interpolate((String) value));
+        }
+        else if (value instanceof List)
+        {
+            list = new ArrayList<Object>();
+            List<?> l = (List<?>) value;
+
+            // add the interpolated elements in the new list
+            for (Object elem : l)
+            {
+                list.add(interpolate(elem));
+            }
+        }
+        else if (value == null)
+        {
+            list = (List<Object>) defaultValue;
+        }
+        else if (value.getClass().isArray())
+        {
+            return Arrays.asList((Object[]) value);
+        }
+        else if (isScalarValue(value))
+        {
+            return Collections.singletonList((Object) value.toString());
+        }
+        else
+        {
+            throw new ConversionException('\'' + key + "' doesn't map to a List object: " + value + ", a "
+                    + value.getClass().getName());
+        }
+        return list;
+    }
+
+    /**
+     * Returns an object from the store described by the key. If the value is a
+     * Collection object, replace it with the first object in the collection.
+     *
+     * @param key The property key.
+     *
+     * @return value Value, transparently resolving a possible collection dependency.
+     */
+    protected Object resolveContainerStore(String key)
+    {
+        Object value = getProperty(key);
+        if (value != null)
+        {
+            if (value instanceof Collection)
+            {
+                Collection<?> collection = (Collection<?>) value;
+                value = collection.isEmpty() ? null : collection.iterator().next();
+            }
+            else if (value.getClass().isArray() && Array.getLength(value) > 0)
+            {
+                value = Array.get(value, 0);
+            }
+        }
+
+        return value;
+    }
+
+    /**
+     * Checks whether the specified object is a scalar value. This method is
+     * called by {@code getList()} and {@code getStringArray()} if the
+     * property requested is not a string, a list, or an array. If it returns
+     * <b>true</b>, the calling method transforms the value to a string and
+     * returns a list or an array with this single element. This implementation
+     * returns <b>true</b> if the value is of a wrapper type for a primitive
+     * type.
+     *
+     * @param value the value to be checked
+     * @return a flag whether the value is a scalar
+     * @since 1.7
+     */
+    protected boolean isScalarValue(Object value)
+    {
+        return ClassUtils.wrapperToPrimitive(value.getClass()) != null;
+    }
+
+    /**
+     * Copies the content of the specified configuration into this
+     * configuration. If the specified configuration contains a key that is also
+     * present in this configuration, the value of this key will be replaced by
+     * the new value. <em>Note:</em> This method won't work well when copying
+     * hierarchical configurations because it is not able to copy information
+     * about the properties' structure (i.e. the parent-child-relationships will
+     * get lost). So when dealing with hierarchical configuration objects their
+     * {@link HierarchicalConfiguration#clone() clone()} methods
+     * should be used.
+     *
+     * @param c the configuration to copy (can be <b>null</b>, then this
+     * operation will have no effect)
+     * @since 1.5
+     */
+    public void copy(Configuration c)
+    {
+        if (c != null)
+        {
+            for (Iterator<String> it = c.getKeys(); it.hasNext();)
+            {
+                String key = it.next();
+                Object value = c.getProperty(key);
+                fireEvent(EVENT_SET_PROPERTY, key, value, true);
+                setDetailEvents(false);
+                try
+                {
+                    clearProperty(key);
+                    addPropertyValues(key, value, DISABLED_DELIMITER);
+                }
+                finally
+                {
+                    setDetailEvents(true);
+                }
+                fireEvent(EVENT_SET_PROPERTY, key, value, false);
+            }
+        }
+    }
+
+    /**
+     * Appends the content of the specified configuration to this configuration.
+     * The values of all properties contained in the specified configuration
+     * will be appended to this configuration. So if a property is already
+     * present in this configuration, its new value will be a union of the
+     * values in both configurations. <em>Note:</em> This method won't work
+     * well when appending hierarchical configurations because it is not able to
+     * copy information about the properties' structure (i.e. the
+     * parent-child-relationships will get lost). So when dealing with
+     * hierarchical configuration objects their
+     * {@link HierarchicalConfiguration#clone() clone()} methods
+     * should be used.
+     *
+     * @param c the configuration to be appended (can be <b>null</b>, then this
+     * operation will have no effect)
+     * @since 1.5
+     */
+    public void append(Configuration c)
+    {
+        if (c != null)
+        {
+            for (Iterator<String> it = c.getKeys(); it.hasNext();)
+            {
+                String key = it.next();
+                Object value = c.getProperty(key);
+                fireEvent(EVENT_ADD_PROPERTY, key, value, true);
+                addPropertyValues(key, value, DISABLED_DELIMITER);
+                fireEvent(EVENT_ADD_PROPERTY, key, value, false);
+            }
+        }
+    }
+
+    /**
+     * Returns a configuration with the same content as this configuration, but
+     * with all variables replaced by their actual values. This method tries to
+     * clone the configuration and then perform interpolation on all properties.
+     * So property values of the form <code>${var}</code> will be resolved as
+     * far as possible (if a variable cannot be resolved, it remains unchanged).
+     * This operation is useful if the content of a configuration is to be
+     * exported or processed by an external component that does not support
+     * variable interpolation.
+     *
+     * @return a configuration with all variables interpolated
+     * @throws ConfigurationRuntimeException if this configuration cannot be
+     * cloned
+     * @since 1.5
+     */
+    public Configuration interpolatedConfiguration()
+    {
+        // first clone this configuration
+        AbstractConfiguration c = (AbstractConfiguration) ConfigurationUtils
+                .cloneConfiguration(this);
+
+        // now perform interpolation
+        c.setDelimiterParsingDisabled(true);
+        for (Iterator<String> it = getKeys(); it.hasNext();)
+        {
+            String key = it.next();
+            c.setProperty(key, getList(key));
+        }
+
+        c.setDelimiterParsingDisabled(isDelimiterParsingDisabled());
+        return c;
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/AbstractFileConfiguration.java b/src/main/java/org/apache/commons/configuration/AbstractFileConfiguration.java
new file mode 100644
index 0000000..67d0578
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/AbstractFileConfiguration.java
@@ -0,0 +1,1092 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.net.URL;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.commons.configuration.reloading.InvariantReloadingStrategy;
+import org.apache.commons.configuration.reloading.ReloadingStrategy;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <p>Partial implementation of the {@code FileConfiguration} interface.
+ * Developers of file based configuration may want to extend this class,
+ * the two methods left to implement are {@link FileConfiguration#load(Reader)}
+ * and {@link FileConfiguration#save(Writer)}.</p>
+ * <p>This base class already implements a couple of ways to specify the location
+ * of the file this configuration is based on. The following possibilities
+ * exist:
+ * <ul><li>URLs: With the method {@code setURL()} a full URL to the
+ * configuration source can be specified. This is the most flexible way. Note
+ * that the {@code save()} methods support only <em>file:</em> URLs.</li>
+ * <li>Files: The {@code setFile()} method allows to specify the
+ * configuration source as a file. This can be either a relative or an
+ * absolute file. In the former case the file is resolved based on the current
+ * directory.</li>
+ * <li>As file paths in string form: With the {@code setPath()} method a
+ * full path to a configuration file can be provided as a string.</li>
+ * <li>Separated as base path and file name: This is the native form in which
+ * the location is stored. The base path is a string defining either a local
+ * directory or a URL. It can be set using the {@code setBasePath()}
+ * method. The file name, non surprisingly, defines the name of the configuration
+ * file.</li></ul></p>
+ * <p>The configuration source to be loaded can be specified using one of the
+ * methods described above. Then the parameterless {@code load()} method can be
+ * called. Alternatively, one of the {@code load()} methods can be used which is
+ * passed the source directly. These methods typically do not change the
+ * internally stored file; however, if the configuration is not yet associated
+ * with a configuration source, the first call to one of the {@code load()}
+ * methods sets the base path and the source URL. This fact has to be taken
+ * into account when calling {@code load()} multiple times with different file
+ * paths.</p>
+ * <p>Note that the {@code load()} methods do not wipe out the configuration's
+ * content before the new configuration file is loaded. Thus it is very easy to
+ * construct a union configuration by simply loading multiple configuration
+ * files, e.g.</p>
+ * <p><pre>
+ * config.load(configFile1);
+ * config.load(configFile2);
+ * </pre></p>
+ * <p>After executing this code fragment, the resulting configuration will
+ * contain both the properties of configFile1 and configFile2. On the other
+ * hand, if the current configuration file is to be reloaded, {@code clear()}
+ * should be called first. Otherwise the properties are doubled. This behavior
+ * is analogous to the behavior of the {@code load(InputStream)} method
+ * in {@code java.util.Properties}.</p>
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: AbstractFileConfiguration.java 1234118 2012-01-20 20:36:04Z oheger $
+ * @since 1.0-rc2
+ */
+public abstract class AbstractFileConfiguration
+extends BaseConfiguration
+implements FileConfiguration, FileSystemBased
+{
+    /** Constant for the configuration reload event.*/
+    public static final int EVENT_RELOAD = 20;
+
+    /** Constant fro the configuration changed event. */
+    public static final int EVENT_CONFIG_CHANGED = 21;
+
+    /** The root of the file scheme */
+    private static final String FILE_SCHEME = "file:";
+
+    /** Stores the file name.*/
+    protected String fileName;
+
+    /** Stores the base path.*/
+    protected String basePath;
+
+    /** The auto save flag.*/
+    protected boolean autoSave;
+
+    /** Holds a reference to the reloading strategy.*/
+    protected ReloadingStrategy strategy;
+
+    /** A lock object for protecting reload operations.*/
+    protected Object reloadLock = new Lock("AbstractFileConfiguration");
+
+    /** Stores the encoding of the configuration file.*/
+    private String encoding;
+
+    /** Stores the URL from which the configuration file was loaded.*/
+    private URL sourceURL;
+
+    /** A counter that prohibits reloading.*/
+    private int noReload;
+
+    /** The FileSystem being used for this Configuration */
+    private FileSystem fileSystem = FileSystem.getDefaultFileSystem();
+
+    /**
+     * Default constructor
+     *
+     * @since 1.1
+     */
+    public AbstractFileConfiguration()
+    {
+        initReloadingStrategy();
+        setLogger(LogFactory.getLog(getClass()));
+        addErrorLogListener();
+    }
+
+    /**
+     * Creates and loads the configuration from the specified file. The passed
+     * in string must be a valid file name, either absolute or relativ.
+     *
+     * @param fileName The name of the file to load.
+     *
+     * @throws ConfigurationException Error while loading the file
+     * @since 1.1
+     */
+    public AbstractFileConfiguration(String fileName) throws ConfigurationException
+    {
+        this();
+
+        // store the file name
+        setFileName(fileName);
+
+        // load the file
+        load();
+    }
+
+    /**
+     * Creates and loads the configuration from the specified file.
+     *
+     * @param file The file to load.
+     * @throws ConfigurationException Error while loading the file
+     * @since 1.1
+     */
+    public AbstractFileConfiguration(File file) throws ConfigurationException
+    {
+        this();
+
+        // set the file and update the url, the base path and the file name
+        setFile(file);
+
+        // load the file
+        if (file.exists())
+        {
+            load();
+        }
+    }
+
+    /**
+     * Creates and loads the configuration from the specified URL.
+     *
+     * @param url The location of the file to load.
+     * @throws ConfigurationException Error while loading the file
+     * @since 1.1
+     */
+    public AbstractFileConfiguration(URL url) throws ConfigurationException
+    {
+        this();
+
+        // set the URL and update the base path and the file name
+        setURL(url);
+
+        // load the file
+        load();
+    }
+
+    public void setFileSystem(FileSystem fileSystem)
+    {
+        if (fileSystem == null)
+        {
+            throw new NullPointerException("A valid FileSystem must be specified");
+        }
+        this.fileSystem = fileSystem;
+    }
+
+    public void resetFileSystem()
+    {
+        this.fileSystem = FileSystem.getDefaultFileSystem();
+    }
+
+    public FileSystem getFileSystem()
+    {
+        return this.fileSystem;
+    }
+
+    public Object getReloadLock()
+    {
+        return reloadLock;
+    }
+
+
+    /**
+     * Load the configuration from the underlying location.
+     *
+     * @throws ConfigurationException if loading of the configuration fails
+     */
+    public void load() throws ConfigurationException
+    {
+        if (sourceURL != null)
+        {
+            load(sourceURL);
+        }
+        else
+        {
+            load(getFileName());
+        }
+    }
+
+    /**
+     * Locate the specified file and load the configuration. If the configuration is
+     * already associated with a source, the current source is not changed.
+     * Otherwise (i.e. this is the first load operation), the source URL and
+     * the base path are set now based on the source to be loaded.
+     *
+     * @param fileName the name of the file to be loaded
+     * @throws ConfigurationException if an error occurs
+     */
+    public void load(String fileName) throws ConfigurationException
+    {
+        try
+        {
+            URL url = ConfigurationUtils.locate(this.fileSystem, basePath, fileName);
+
+            if (url == null)
+            {
+                throw new ConfigurationException("Cannot locate configuration source " + fileName);
+            }
+            load(url);
+        }
+        catch (ConfigurationException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new ConfigurationException("Unable to load the configuration file " + fileName, e);
+        }
+    }
+
+    /**
+     * Load the configuration from the specified file. If the configuration is
+     * already associated with a source, the current source is not changed.
+     * Otherwise (i.e. this is the first load operation), the source URL and
+     * the base path are set now based on the source to be loaded.
+     *
+     * @param file the file to load
+     * @throws ConfigurationException if an error occurs
+     */
+    public void load(File file) throws ConfigurationException
+    {
+        try
+        {
+            load(ConfigurationUtils.toURL(file));
+        }
+        catch (ConfigurationException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new ConfigurationException("Unable to load the configuration file " + file, e);
+        }
+    }
+
+    /**
+     * Load the configuration from the specified URL. If the configuration is
+     * already associated with a source, the current source is not changed.
+     * Otherwise (i.e. this is the first load operation), the source URL and
+     * the base path are set now based on the source to be loaded.
+     *
+     * @param url the URL of the file to be loaded
+     * @throws ConfigurationException if an error occurs
+     */
+    public void load(URL url) throws ConfigurationException
+    {
+        if (sourceURL == null)
+        {
+            if (StringUtils.isEmpty(getBasePath()))
+            {
+                // ensure that we have a valid base path
+                setBasePath(url.toString());
+            }
+            sourceURL = url;
+        }
+
+        InputStream in = null;
+
+        try
+        {
+            in = fileSystem.getInputStream(url);
+            load(in);
+        }
+        catch (ConfigurationException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new ConfigurationException("Unable to load the configuration from the URL " + url, e);
+        }
+        finally
+        {
+            // close the input stream
+            try
+            {
+                if (in != null)
+                {
+                    in.close();
+                }
+            }
+            catch (IOException e)
+            {
+                getLogger().warn("Could not close input stream", e);
+            }
+        }
+    }
+
+    /**
+     * Load the configuration from the specified stream, using the encoding
+     * returned by {@link #getEncoding()}.
+     *
+     * @param in the input stream
+     *
+     * @throws ConfigurationException if an error occurs during the load operation
+     */
+    public void load(InputStream in) throws ConfigurationException
+    {
+        load(in, getEncoding());
+    }
+
+    /**
+     * Load the configuration from the specified stream, using the specified
+     * encoding. If the encoding is null the default encoding is used.
+     *
+     * @param in the input stream
+     * @param encoding the encoding used. {@code null} to use the default encoding
+     *
+     * @throws ConfigurationException if an error occurs during the load operation
+     */
+    public void load(InputStream in, String encoding) throws ConfigurationException
+    {
+        Reader reader = null;
+
+        if (encoding != null)
+        {
+            try
+            {
+                reader = new InputStreamReader(in, encoding);
+            }
+            catch (UnsupportedEncodingException e)
+            {
+                throw new ConfigurationException(
+                        "The requested encoding is not supported, try the default encoding.", e);
+            }
+        }
+
+        if (reader == null)
+        {
+            reader = new InputStreamReader(in);
+        }
+
+        load(reader);
+    }
+
+    /**
+     * Save the configuration. Before this method can be called a valid file
+     * name must have been set.
+     *
+     * @throws ConfigurationException if an error occurs or no file name has
+     * been set yet
+     */
+    public void save() throws ConfigurationException
+    {
+        if (getFileName() == null)
+        {
+            throw new ConfigurationException("No file name has been set!");
+        }
+
+        if (sourceURL != null)
+        {
+            save(sourceURL);
+        }
+        else
+        {
+            save(fileName);
+        }
+        strategy.init();
+    }
+
+    /**
+     * Save the configuration to the specified file. This doesn't change the
+     * source of the configuration, use setFileName() if you need it.
+     *
+     * @param fileName the file name
+     *
+     * @throws ConfigurationException if an error occurs during the save operation
+     */
+    public void save(String fileName) throws ConfigurationException
+    {
+        try
+        {
+            URL url = this.fileSystem.getURL(basePath, fileName);
+
+            if (url == null)
+            {
+                throw new ConfigurationException("Cannot locate configuration source " + fileName);
+            }
+            save(url);
+            /*File file = ConfigurationUtils.getFile(basePath, fileName);
+            if (file == null)
+            {
+                throw new ConfigurationException("Invalid file name for save: " + fileName);
+            }
+            save(file); */
+        }
+        catch (ConfigurationException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new ConfigurationException("Unable to save the configuration to the file " + fileName, e);
+        }
+    }
+
+    /**
+     * Save the configuration to the specified URL.
+     * This doesn't change the source of the configuration, use setURL()
+     * if you need it.
+     *
+     * @param url the URL
+     *
+     * @throws ConfigurationException if an error occurs during the save operation
+     */
+    public void save(URL url) throws ConfigurationException
+    {
+        OutputStream out = null;
+        try
+        {
+            out = fileSystem.getOutputStream(url);
+            save(out);
+            if (out instanceof VerifiableOutputStream)
+            {
+                ((VerifiableOutputStream) out).verify();
+            }
+        }
+        catch (IOException e)
+        {
+            throw new ConfigurationException("Could not save to URL " + url, e);
+        }
+        finally
+        {
+            closeSilent(out);
+        }
+    }
+
+    /**
+     * Save the configuration to the specified file. The file is created
+     * automatically if it doesn't exist. This doesn't change the source
+     * of the configuration, use {@link #setFile} if you need it.
+     *
+     * @param file the target file
+     *
+     * @throws ConfigurationException if an error occurs during the save operation
+     */
+    public void save(File file) throws ConfigurationException
+    {
+        OutputStream out = null;
+
+        try
+        {
+            out = fileSystem.getOutputStream(file);
+            save(out);
+        }
+        finally
+        {
+            closeSilent(out);
+        }
+    }
+
+    /**
+     * Save the configuration to the specified stream, using the encoding
+     * returned by {@link #getEncoding()}.
+     *
+     * @param out the output stream
+     *
+     * @throws ConfigurationException if an error occurs during the save operation
+     */
+    public void save(OutputStream out) throws ConfigurationException
+    {
+        save(out, getEncoding());
+    }
+
+    /**
+     * Save the configuration to the specified stream, using the specified
+     * encoding. If the encoding is null the default encoding is used.
+     *
+     * @param out the output stream
+     * @param encoding the encoding to use
+     * @throws ConfigurationException if an error occurs during the save operation
+     */
+    public void save(OutputStream out, String encoding) throws ConfigurationException
+    {
+        Writer writer = null;
+
+        if (encoding != null)
+        {
+            try
+            {
+                writer = new OutputStreamWriter(out, encoding);
+            }
+            catch (UnsupportedEncodingException e)
+            {
+                throw new ConfigurationException(
+                        "The requested encoding is not supported, try the default encoding.", e);
+            }
+        }
+
+        if (writer == null)
+        {
+            writer = new OutputStreamWriter(out);
+        }
+
+        save(writer);
+    }
+
+    /**
+     * Return the name of the file.
+     *
+     * @return the file name
+     */
+    public String getFileName()
+    {
+        return fileName;
+    }
+
+    /**
+     * Set the name of the file. The passed in file name can contain a
+     * relative path.
+     * It must be used when referring files with relative paths from classpath.
+     * Use {@link AbstractFileConfiguration#setPath(String)
+     * setPath()} to set a full qualified file name.
+     *
+     * @param fileName the name of the file
+     */
+    public void setFileName(String fileName)
+    {
+        if (fileName != null && fileName.startsWith(FILE_SCHEME) && !fileName.startsWith("file://"))
+        {
+            fileName = "file://" + fileName.substring(FILE_SCHEME.length());
+        }
+
+        sourceURL = null;
+        this.fileName = fileName;
+        getLogger().debug("FileName set to " + fileName);
+    }
+
+    /**
+     * Return the base path.
+     *
+     * @return the base path
+     * @see FileConfiguration#getBasePath()
+     */
+    public String getBasePath()
+    {
+        return basePath;
+    }
+
+    /**
+     * Sets the base path. The base path is typically either a path to a
+     * directory or a URL. Together with the value passed to the
+     * {@code setFileName()} method it defines the location of the
+     * configuration file to be loaded. The strategies for locating the file are
+     * quite tolerant. For instance if the file name is already an absolute path
+     * or a fully defined URL, the base path will be ignored. The base path can
+     * also be a URL, in which case the file name is interpreted in this URL's
+     * context. Because the base path is used by some of the derived classes for
+     * resolving relative file names it should contain a meaningful value. If
+     * other methods are used for determining the location of the configuration
+     * file (e.g. {@code setFile()} or {@code setURL()}), the
+     * base path is automatically set.
+     *
+     * @param basePath the base path.
+     */
+    public void setBasePath(String basePath)
+    {
+        if (basePath != null && basePath.startsWith(FILE_SCHEME) && !basePath.startsWith("file://"))
+        {
+            basePath = "file://" + basePath.substring(FILE_SCHEME.length());
+        }
+        sourceURL = null;
+        this.basePath = basePath;
+        getLogger().debug("Base path set to " + basePath);
+    }
+
+    /**
+     * Return the file where the configuration is stored. If the base path is a
+     * URL with a protocol different than "file", or the configuration
+     * file is within a compressed archive, the return value
+     * will not point to a valid file object.
+     *
+     * @return the file where the configuration is stored; this can be <b>null</b>
+     */
+    public File getFile()
+    {
+        if (getFileName() == null && sourceURL == null)
+        {
+            return null;
+        }
+        else if (sourceURL != null)
+        {
+            return ConfigurationUtils.fileFromURL(sourceURL);
+        }
+        else
+        {
+            return ConfigurationUtils.getFile(getBasePath(), getFileName());
+        }
+    }
+
+    /**
+     * Set the file where the configuration is stored. The passed in file is
+     * made absolute if it is not yet. Then the file's path component becomes
+     * the base path and its name component becomes the file name.
+     *
+     * @param file the file where the configuration is stored
+     */
+    public void setFile(File file)
+    {
+        sourceURL = null;
+        setFileName(file.getName());
+        setBasePath((file.getParentFile() != null) ? file.getParentFile()
+                .getAbsolutePath() : null);
+    }
+
+    /**
+     * Returns the full path to the file this configuration is based on. The
+     * return value is a valid File path only if this configuration is based on
+     * a file on the local disk.
+     * If the configuration was loaded from a packed archive the returned value
+     * is the string form of the URL from which the configuration was loaded.
+     *
+     * @return the full path to the configuration file
+     */
+    public String getPath()
+    {
+        return fileSystem.getPath(getFile(), sourceURL, getBasePath(), getFileName());
+    }
+
+    /**
+     * Sets the location of this configuration as a full or relative path name.
+     * The passed in path should represent a valid file name on the file system.
+     * It must not be used to specify relative paths for files that exist
+     * in classpath, either plain file system or compressed archive,
+     * because this method expands any relative path to an absolute one which
+     * may end in an invalid absolute path for classpath references.
+     *
+     * @param path the full path name of the configuration file
+     */
+    public void setPath(String path)
+    {
+        setFile(new File(path));
+    }
+
+    URL getSourceURL()
+    {
+        return sourceURL;
+    }
+
+    /**
+     * Return the URL where the configuration is stored.
+     *
+     * @return the configuration's location as URL
+     */
+    public URL getURL()
+    {
+        return (sourceURL != null) ? sourceURL
+                : ConfigurationUtils.locate(this.fileSystem, getBasePath(), getFileName());
+    }
+
+    /**
+     * Set the location of this configuration as a URL. For loading this can be
+     * an arbitrary URL with a supported protocol. If the configuration is to
+     * be saved, too, a URL with the "file" protocol should be
+     * provided.
+     *
+     * @param url the location of this configuration as URL
+     */
+    public void setURL(URL url)
+    {
+        setBasePath(ConfigurationUtils.getBasePath(url));
+        setFileName(ConfigurationUtils.getFileName(url));
+        sourceURL = url;
+        getLogger().debug("URL set to " + url);
+    }
+
+    public void setAutoSave(boolean autoSave)
+    {
+        this.autoSave = autoSave;
+    }
+
+    public boolean isAutoSave()
+    {
+        return autoSave;
+    }
+
+    /**
+     * Save the configuration if the automatic persistence is enabled
+     * and if a file is specified.
+     */
+    protected void possiblySave()
+    {
+        if (autoSave && fileName != null)
+        {
+            try
+            {
+                save();
+            }
+            catch (ConfigurationException e)
+            {
+                throw new ConfigurationRuntimeException("Failed to auto-save", e);
+            }
+        }
+    }
+
+    /**
+     * Adds a new property to this configuration. This implementation checks if
+     * the auto save mode is enabled and saves the configuration if necessary.
+     *
+     * @param key the key of the new property
+     * @param value the value
+     */
+    @Override
+    public void addProperty(String key, Object value)
+    {
+        synchronized (reloadLock)
+        {
+            super.addProperty(key, value);
+            possiblySave();
+        }
+    }
+
+    /**
+     * Sets a new value for the specified property. This implementation checks
+     * if the auto save mode is enabled and saves the configuration if
+     * necessary.
+     *
+     * @param key the key of the affected property
+     * @param value the value
+     */
+    @Override
+    public void setProperty(String key, Object value)
+    {
+        synchronized (reloadLock)
+        {
+            super.setProperty(key, value);
+            possiblySave();
+        }
+    }
+
+    @Override
+    public void clearProperty(String key)
+    {
+        synchronized (reloadLock)
+        {
+            super.clearProperty(key);
+            possiblySave();
+        }
+    }
+
+    public ReloadingStrategy getReloadingStrategy()
+    {
+        return strategy;
+    }
+
+    public void setReloadingStrategy(ReloadingStrategy strategy)
+    {
+        this.strategy = strategy;
+        strategy.setConfiguration(this);
+        strategy.init();
+    }
+
+    /**
+     * Performs a reload operation if necessary. This method is called on each
+     * access of this configuration. It asks the associated reloading strategy
+     * whether a reload should be performed. If this is the case, the
+     * configuration is cleared and loaded again from its source. If this
+     * operation causes an exception, the registered error listeners will be
+     * notified. The error event passed to the listeners is of type
+     * {@code EVENT_RELOAD} and contains the exception that caused the
+     * event.
+     */
+    public void reload()
+    {
+        reload(false);
+    }
+
+    public boolean reload(boolean checkReload)
+    {
+        synchronized (reloadLock)
+        {
+            if (noReload == 0)
+            {
+                try
+                {
+                    enterNoReload(); // avoid reentrant calls
+
+                    if (strategy.reloadingRequired())
+                    {
+                        if (getLogger().isInfoEnabled())
+                        {
+                            getLogger().info("Reloading configuration. URL is " + getURL());
+                        }
+                        refresh();
+
+                        // notify the strategy
+                        strategy.reloadingPerformed();
+                    }
+                }
+                catch (Exception e)
+                {
+                    fireError(EVENT_RELOAD, null, null, e);
+                    // todo rollback the changes if the file can't be reloaded
+                    if (checkReload)
+                    {
+                        return false;
+                    }
+                }
+                finally
+                {
+                    exitNoReload();
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Reloads the associated configuration file. This method first clears the
+     * content of this configuration, then the associated configuration file is
+     * loaded again. Updates on this configuration which have not yet been saved
+     * are lost. Calling this method is like invoking {@code reload()}
+     * without checking the reloading strategy.
+     *
+     * @throws ConfigurationException if an error occurs
+     * @since 1.7
+     */
+    public void refresh() throws ConfigurationException
+    {
+        fireEvent(EVENT_RELOAD, null, getURL(), true);
+        setDetailEvents(false);
+        boolean autoSaveBak = this.isAutoSave(); // save the current state
+        this.setAutoSave(false); // deactivate autoSave to prevent information loss
+        try
+        {
+            clear();
+            load();
+        }
+        finally
+        {
+            this.setAutoSave(autoSaveBak); // set autoSave to previous value
+            setDetailEvents(true);
+        }
+        fireEvent(EVENT_RELOAD, null, getURL(), false);
+    }
+
+    /**
+     * Send notification that the configuration has changed.
+     */
+    public void configurationChanged()
+    {
+        fireEvent(EVENT_CONFIG_CHANGED, null, getURL(), true);
+    }
+
+    /**
+     * Enters the "No reloading mode". As long as this mode is active
+     * no reloading will be performed. This is necessary for some
+     * implementations of {@code save()} in derived classes, which may
+     * cause a reload while accessing the properties to save. This may cause the
+     * whole configuration to be erased. To avoid this, this method can be
+     * called first. After a call to this method there always must be a
+     * corresponding call of {@link #exitNoReload()} later! (If
+     * necessary, {@code finally} blocks must be used to ensure this.
+     */
+    protected void enterNoReload()
+    {
+        synchronized (reloadLock)
+        {
+            noReload++;
+        }
+    }
+
+    /**
+     * Leaves the "No reloading mode".
+     *
+     * @see #enterNoReload()
+     */
+    protected void exitNoReload()
+    {
+        synchronized (reloadLock)
+        {
+            if (noReload > 0) // paranoia check
+            {
+                noReload--;
+            }
+        }
+    }
+
+    /**
+     * Sends an event to all registered listeners. This implementation ensures
+     * that no reloads are performed while the listeners are invoked. So
+     * infinite loops can be avoided that can be caused by event listeners
+     * accessing the configuration's properties when they are invoked.
+     *
+     * @param type the event type
+     * @param propName the name of the property
+     * @param propValue the value of the property
+     * @param before the before update flag
+     */
+    @Override
+    protected void fireEvent(int type, String propName, Object propValue, boolean before)
+    {
+        enterNoReload();
+        try
+        {
+            super.fireEvent(type, propName, propValue, before);
+        }
+        finally
+        {
+            exitNoReload();
+        }
+    }
+
+    @Override
+    public Object getProperty(String key)
+    {
+        synchronized (reloadLock)
+        {
+            reload();
+            return super.getProperty(key);
+        }
+    }
+
+    @Override
+    public boolean isEmpty()
+    {
+        reload();
+        synchronized (reloadLock)
+        {
+            return super.isEmpty();
+        }
+    }
+
+    @Override
+    public boolean containsKey(String key)
+    {
+        reload();
+        synchronized (reloadLock)
+        {
+            return super.containsKey(key);
+        }
+    }
+
+    /**
+     * Returns an {@code Iterator} with the keys contained in this
+     * configuration. This implementation performs a reload if necessary before
+     * obtaining the keys. The {@code Iterator} returned by this method
+     * points to a snapshot taken when this method was called. Later changes at
+     * the set of keys (including those caused by a reload) won't be visible.
+     * This is because a reload can happen at any time during iteration, and it
+     * is impossible to determine how this reload affects the current iteration.
+     * When using the iterator a client has to be aware that changes of the
+     * configuration are possible at any time. For instance, if after a reload
+     * operation some keys are no longer present, the iterator will still return
+     * those keys because they were found when it was created.
+     *
+     * @return an {@code Iterator} with the keys of this configuration
+     */
+    @Override
+    public Iterator<String> getKeys()
+    {
+        reload();
+        List<String> keyList = new LinkedList<String>();
+        enterNoReload();
+        try
+        {
+            for (Iterator<String> it = super.getKeys(); it.hasNext();)
+            {
+                keyList.add(it.next());
+            }
+
+            return keyList.iterator();
+        }
+        finally
+        {
+            exitNoReload();
+        }
+    }
+
+    public String getEncoding()
+    {
+        return encoding;
+    }
+
+    public void setEncoding(String encoding)
+    {
+        this.encoding = encoding;
+    }
+
+    /**
+     * Creates a copy of this configuration. The new configuration object will
+     * contain the same properties as the original, but it will lose any
+     * connection to a source file (if one exists); this includes setting the
+     * source URL, base path, and file name to <b>null</b>. This is done to
+     * avoid race conditions if both the original and the copy are modified and
+     * then saved.
+     *
+     * @return the copy
+     * @since 1.3
+     */
+    @Override
+    public Object clone()
+    {
+        AbstractFileConfiguration copy = (AbstractFileConfiguration) super.clone();
+        copy.setBasePath(null);
+        copy.setFileName(null);
+        copy.initReloadingStrategy();
+        return copy;
+    }
+
+    /**
+     * Helper method for initializing the reloading strategy.
+     */
+    private void initReloadingStrategy()
+    {
+        setReloadingStrategy(new InvariantReloadingStrategy());
+    }
+
+    /**
+     * A helper method for closing an output stream. Occurring exceptions will
+     * be ignored.
+     *
+     * @param out the output stream to be closed (may be <b>null</b>)
+     * @since 1.5
+     */
+    protected void closeSilent(OutputStream out)
+    {
+        try
+        {
+            if (out != null)
+            {
+                out.close();
+            }
+        }
+        catch (IOException e)
+        {
+            getLogger().warn("Could not close output stream", e);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/AbstractHierarchicalFileConfiguration.java b/src/main/java/org/apache/commons/configuration/AbstractHierarchicalFileConfiguration.java
new file mode 100644
index 0000000..7986776
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/AbstractHierarchicalFileConfiguration.java
@@ -0,0 +1,579 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.configuration.event.ConfigurationErrorEvent;
+import org.apache.commons.configuration.event.ConfigurationErrorListener;
+import org.apache.commons.configuration.event.ConfigurationEvent;
+import org.apache.commons.configuration.event.ConfigurationListener;
+import org.apache.commons.configuration.reloading.Reloadable;
+import org.apache.commons.configuration.reloading.ReloadingStrategy;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+
+/**
+ * <p>Base class for implementing file based hierarchical configurations.</p>
+ * <p>This class serves an analogous purpose as the
+ * {@link AbstractFileConfiguration} class for non hierarchical
+ * configurations. It behaves in exactly the same way, so please refer to the
+ * documentation of {@code AbstractFileConfiguration} for further details.</p>
+ *
+ * @since 1.2
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: AbstractHierarchicalFileConfiguration.java 1206575 2011-11-26 20:07:52Z oheger $
+ */
+public abstract class AbstractHierarchicalFileConfiguration
+extends HierarchicalConfiguration
+implements FileConfiguration, ConfigurationListener, ConfigurationErrorListener, FileSystemBased,
+        Reloadable
+{
+    /** Stores the delegate used for implementing functionality related to the
+     * {@code FileConfiguration} interface.
+     */
+    private FileConfigurationDelegate delegate;
+
+    /**
+     * Creates a new instance of {@code AbstractHierarchicalFileConfiguration}.
+     */
+    protected AbstractHierarchicalFileConfiguration()
+    {
+        initialize();
+    }
+
+    /**
+     * Creates a new instance of
+     * {@code AbstractHierarchicalFileConfiguration} and copies the
+     * content of the specified configuration into this object.
+     *
+     * @param c the configuration to copy
+     * @since 1.4
+     */
+    protected AbstractHierarchicalFileConfiguration(HierarchicalConfiguration c)
+    {
+        super(c);
+        initialize();
+    }
+
+    /**
+     * Creates and loads the configuration from the specified file.
+     *
+     * @param fileName The name of the plist file to load.
+     * @throws ConfigurationException Error while loading the file
+     */
+    public AbstractHierarchicalFileConfiguration(String fileName) throws ConfigurationException
+    {
+        this();
+        // store the file name
+        delegate.setFileName(fileName);
+
+        // load the file
+        load();
+    }
+
+    /**
+     * Creates and loads the configuration from the specified file.
+     *
+     * @param file The configuration file to load.
+     * @throws ConfigurationException Error while loading the file
+     */
+    public AbstractHierarchicalFileConfiguration(File file) throws ConfigurationException
+    {
+        this();
+        // set the file and update the url, the base path and the file name
+        setFile(file);
+
+        // load the file
+        if (file.exists())
+        {
+            load();
+        }
+    }
+
+    /**
+     * Creates and loads the configuration from the specified URL.
+     *
+     * @param url The location of the configuration file to load.
+     * @throws ConfigurationException Error while loading the file
+     */
+    public AbstractHierarchicalFileConfiguration(URL url) throws ConfigurationException
+    {
+        this();
+        // set the URL and update the base path and the file name
+        setURL(url);
+
+        // load the file
+        load();
+    }
+
+    /**
+     * Initializes this instance, mainly the internally used delegate object.
+     */
+    private void initialize()
+    {
+        delegate = createDelegate();
+        initDelegate(delegate);
+    }
+
+    @Override
+    protected void addPropertyDirect(String key, Object obj)
+    {
+        synchronized (delegate.getReloadLock())
+        {
+            super.addPropertyDirect(key, obj);
+            delegate.possiblySave();
+        }
+    }
+
+    @Override
+    public void clearProperty(String key)
+    {
+        synchronized (delegate.getReloadLock())
+        {
+            super.clearProperty(key);
+            delegate.possiblySave();
+        }
+    }
+
+    @Override
+    public void clearTree(String key)
+    {
+        synchronized (delegate.getReloadLock())
+        {
+            super.clearTree(key);
+            delegate.possiblySave();
+        }
+    }
+
+    @Override
+    public void setProperty(String key, Object value)
+    {
+        synchronized (delegate.getReloadLock())
+        {
+            super.setProperty(key, value);
+            delegate.possiblySave();
+        }
+    }
+
+    public void load() throws ConfigurationException
+    {
+        delegate.load();
+    }
+
+    public void load(String fileName) throws ConfigurationException
+    {
+        delegate.load(fileName);
+    }
+
+    public void load(File file) throws ConfigurationException
+    {
+        delegate.load(file);
+    }
+
+    public void load(URL url) throws ConfigurationException
+    {
+        delegate.load(url);
+    }
+
+    public void load(InputStream in) throws ConfigurationException
+    {
+        delegate.load(in);
+    }
+
+    public void load(InputStream in, String encoding) throws ConfigurationException
+    {
+        delegate.load(in, encoding);
+    }
+
+    public void save() throws ConfigurationException
+    {
+        delegate.save();
+    }
+
+    public void save(String fileName) throws ConfigurationException
+    {
+        delegate.save(fileName);
+    }
+
+    public void save(File file) throws ConfigurationException
+    {
+        delegate.save(file);
+    }
+
+    public void save(URL url) throws ConfigurationException
+    {
+        delegate.save(url);
+    }
+
+    public void save(OutputStream out) throws ConfigurationException
+    {
+        delegate.save(out);
+    }
+
+    public void save(OutputStream out, String encoding) throws ConfigurationException
+    {
+        delegate.save(out, encoding);
+    }
+
+    public String getFileName()
+    {
+        return delegate.getFileName();
+    }
+
+    public void setFileName(String fileName)
+    {
+        delegate.setFileName(fileName);
+    }
+
+    public String getBasePath()
+    {
+        return delegate.getBasePath();
+    }
+
+    public void setBasePath(String basePath)
+    {
+        delegate.setBasePath(basePath);
+    }
+
+    public File getFile()
+    {
+        return delegate.getFile();
+    }
+
+    public void setFile(File file)
+    {
+        delegate.setFile(file);
+    }
+
+    public URL getURL()
+    {
+        return delegate.getURL();
+    }
+
+    public void setURL(URL url)
+    {
+        delegate.setURL(url);
+    }
+
+    public void setAutoSave(boolean autoSave)
+    {
+        delegate.setAutoSave(autoSave);
+    }
+
+    public boolean isAutoSave()
+    {
+        return delegate.isAutoSave();
+    }
+
+    public ReloadingStrategy getReloadingStrategy()
+    {
+        return delegate.getReloadingStrategy();
+    }
+
+    public void setReloadingStrategy(ReloadingStrategy strategy)
+    {
+        delegate.setReloadingStrategy(strategy);
+    }
+
+    public void reload()
+    {
+        reload(false);
+    }
+
+    private boolean reload(boolean checkReload)
+    {
+        synchronized (delegate.getReloadLock())
+        {
+            setDetailEvents(false);
+            try
+            {
+                return delegate.reload(checkReload);
+            }
+            finally
+            {
+                setDetailEvents(true);
+            }
+        }
+    }
+
+    /**
+     * Reloads the associated configuration file. This method first clears the
+     * content of this configuration, then the associated configuration file is
+     * loaded again. Updates on this configuration which have not yet been saved
+     * are lost. Calling this method is like invoking {@code reload()}
+     * without checking the reloading strategy.
+     *
+     * @throws ConfigurationException if an error occurs
+     * @since 1.7
+     */
+    public void refresh() throws ConfigurationException
+    {
+        delegate.refresh();
+    }
+
+    public String getEncoding()
+    {
+        return delegate.getEncoding();
+    }
+
+    public void setEncoding(String encoding)
+    {
+        delegate.setEncoding(encoding);
+    }
+
+    @Override
+    public Object getReloadLock()
+    {
+        return delegate.getReloadLock();
+    }
+
+    @Override
+    public boolean containsKey(String key)
+    {
+        reload();
+        synchronized (delegate.getReloadLock())
+        {
+            return super.containsKey(key);
+        }
+    }
+
+    @Override
+    public Iterator<String> getKeys()
+    {
+        reload();
+        synchronized (delegate.getReloadLock())
+        {
+            return super.getKeys();
+        }
+    }
+
+    @Override
+    public Iterator<String> getKeys(String prefix)
+    {
+        reload();
+        synchronized (delegate.getReloadLock())
+        {
+            return super.getKeys(prefix);
+        }
+    }
+
+    @Override
+    public Object getProperty(String key)
+    {
+        if (reload(true))
+        {
+            // Avoid reloading again and getting the same error.
+            synchronized (delegate.getReloadLock())
+            {
+                return super.getProperty(key);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public boolean isEmpty()
+    {
+        reload();
+        synchronized (delegate.getReloadLock())
+        {
+            return super.isEmpty();
+        }
+    }
+
+    /**
+     * Directly adds sub nodes to this configuration. This implementation checks
+     * whether auto save is necessary after executing the operation.
+     *
+     * @param key the key where the nodes are to be added
+     * @param nodes a collection with the nodes to be added
+     * @since 1.5
+     */
+    @Override
+    public void addNodes(String key, Collection<? extends ConfigurationNode> nodes)
+    {
+        synchronized (delegate.getReloadLock())
+        {
+            super.addNodes(key, nodes);
+            delegate.possiblySave();
+        }
+    }
+
+    /**
+     * Fetches a list of nodes, which are selected by the specified key. This
+     * implementation will perform a reload if necessary.
+     *
+     * @param key the key
+     * @return a list with the selected nodes
+     */
+    @Override
+    protected List<ConfigurationNode> fetchNodeList(String key)
+    {
+        reload();
+        synchronized (delegate.getReloadLock())
+        {
+            return super.fetchNodeList(key);
+        }
+    }
+
+    /**
+     * Reacts on changes of an associated subnode configuration. If the auto
+     * save mechanism is active, the configuration must be saved.
+     *
+     * @param event the event describing the change
+     * @since 1.5
+     */
+    @Override
+    protected void subnodeConfigurationChanged(ConfigurationEvent event)
+    {
+        delegate.possiblySave();
+        super.subnodeConfigurationChanged(event);
+    }
+
+    /**
+     * Creates the file configuration delegate, i.e. the object that implements
+     * functionality required by the {@code FileConfiguration} interface.
+     * This base implementation will return an instance of the
+     * {@code FileConfigurationDelegate} class. Derived classes may
+     * override it to create a different delegate object.
+     *
+     * @return the file configuration delegate
+     */
+    protected FileConfigurationDelegate createDelegate()
+    {
+        return new FileConfigurationDelegate();
+    }
+
+    /**
+     * Helper method for initializing the file configuration delegate.
+     *
+     * @param del the delegate
+     */
+    private void initDelegate(FileConfigurationDelegate del)
+    {
+        del.addConfigurationListener(this);
+        del.addErrorListener(this);
+        del.setLogger(getLogger());
+    }
+
+    /**
+     * Reacts on configuration change events triggered by the delegate. These
+     * events are passed to the registered configuration listeners.
+     *
+     * @param event the triggered event
+     * @since 1.3
+     */
+    public void configurationChanged(ConfigurationEvent event)
+    {
+        // deliver reload events to registered listeners
+        setDetailEvents(true);
+        try
+        {
+            fireEvent(event.getType(), event.getPropertyName(), event
+                    .getPropertyValue(), event.isBeforeUpdate());
+        }
+        finally
+        {
+            setDetailEvents(false);
+        }
+    }
+
+    public void configurationError(ConfigurationErrorEvent event)
+    {
+        fireError(event.getType(), event.getPropertyName(), event.getPropertyValue(),
+                event.getCause());
+    }
+
+    /**
+     * Returns the file configuration delegate.
+     *
+     * @return the delegate
+     */
+    protected FileConfigurationDelegate getDelegate()
+    {
+        return delegate;
+    }
+
+    /**
+     * Allows to set the file configuration delegate.
+     * @param delegate the new delegate
+     */
+    protected void setDelegate(FileConfigurationDelegate delegate)
+    {
+        this.delegate = delegate;
+    }
+
+    /**
+     * Set the FileSystem to be used for this Configuration.
+     * @param fileSystem The FileSystem to use.
+     */
+    public void setFileSystem(FileSystem fileSystem)
+    {
+        delegate.setFileSystem(fileSystem);
+    }
+
+    /**
+     * Reset the FileSystem to the default;
+     */
+    public void resetFileSystem()
+    {
+        delegate.resetFileSystem();
+    }
+
+    /**
+     * Retrieve the FileSystem being used.
+     * @return The FileSystem.
+     */
+    public FileSystem getFileSystem()
+    {
+        return delegate.getFileSystem();
+    }
+
+    /**
+     * A special implementation of the {@code FileConfiguration} interface that is
+     * used internally to implement the {@code FileConfiguration} methods
+     * for hierarchical configurations.
+     */
+    protected class FileConfigurationDelegate extends AbstractFileConfiguration
+    {
+        public void load(Reader in) throws ConfigurationException
+        {
+            AbstractHierarchicalFileConfiguration.this.load(in);
+        }
+
+        public void save(Writer out) throws ConfigurationException
+        {
+            AbstractHierarchicalFileConfiguration.this.save(out);
+        }
+
+        @Override
+        public void clear()
+        {
+            AbstractHierarchicalFileConfiguration.this.clear();
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/BaseConfiguration.java b/src/main/java/org/apache/commons/configuration/BaseConfiguration.java
new file mode 100644
index 0000000..1e73d1c
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/BaseConfiguration.java
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Basic configuration class. Stores the configuration data but does not
+ * provide any load or save functions. If you want to load your Configuration
+ * from a file use PropertiesConfiguration or XmlConfiguration.
+ *
+ * This class extends normal Java properties by adding the possibility
+ * to use the same key many times concatenating the value strings
+ * instead of overwriting them.
+ *
+ * @author <a href="mailto:stefano at apache.org">Stefano Mazzocchi</a>
+ * @author <a href="mailto:jon at latchkey.com">Jon S. Stevens</a>
+ * @author <a href="mailto:daveb at miceda-data">Dave Bryson</a>
+ * @author <a href="mailto:geirm at optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:leon at opticode.co.za">Leon Messerschmidt</a>
+ * @author <a href="mailto:kjohnson at transparent.com">Kent Johnson</a>
+ * @author <a href="mailto:dlr at finemaltcoding.com">Daniel Rall</a>
+ * @author <a href="mailto:ipriha at surfeu.fi">Ilkka Priha</a>
+ * @author <a href="mailto:jvanzyl at apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:mpoeschl at marmot.at">Martin Poeschl</a>
+ * @author <a href="mailto:hps at intermeta.de">Henning P. Schmiedehausen</a>
+ * @author <a href="mailto:ksh at scand.com">Konstantin Shaposhnikov</a>
+ * @version $Id: BaseConfiguration.java 1231721 2012-01-15 18:32:07Z oheger $
+ */
+public class BaseConfiguration extends AbstractConfiguration implements Cloneable
+{
+    /** stores the configuration key-value pairs */
+    private Map<String, Object> store = new LinkedHashMap<String, Object>();
+
+    /**
+     * Adds a key/value pair to the map.  This routine does no magic morphing.
+     * It ensures the keylist is maintained
+     *
+     * @param key key to use for mapping
+     * @param value object to store
+     */
+    @Override
+    protected void addPropertyDirect(String key, Object value)
+    {
+        Object previousValue = getProperty(key);
+
+        if (previousValue == null)
+        {
+            store.put(key, value);
+        }
+        else if (previousValue instanceof List)
+        {
+            // safe to case because we have created the lists ourselves
+            @SuppressWarnings("unchecked")
+            List<Object> valueList = (List<Object>) previousValue;
+            // the value is added to the existing list
+            valueList.add(value);
+        }
+        else
+        {
+            // the previous value is replaced by a list containing the previous value and the new value
+            List<Object> list = new ArrayList<Object>();
+            list.add(previousValue);
+            list.add(value);
+
+            store.put(key, list);
+        }
+    }
+
+    /**
+     * Read property from underlying map.
+     *
+     * @param key key to use for mapping
+     *
+     * @return object associated with the given configuration key.
+     */
+    public Object getProperty(String key)
+    {
+        return store.get(key);
+    }
+
+    /**
+     * Check if the configuration is empty
+     *
+     * @return {@code true} if Configuration is empty,
+     * {@code false} otherwise.
+     */
+    public boolean isEmpty()
+    {
+        return store.isEmpty();
+    }
+
+    /**
+     * check if the configuration contains the key
+     *
+     * @param key the configuration key
+     *
+     * @return {@code true} if Configuration contain given key,
+     * {@code false} otherwise.
+     */
+    public boolean containsKey(String key)
+    {
+        return store.containsKey(key);
+    }
+
+    /**
+     * Clear a property in the configuration.
+     *
+     * @param key the key to remove along with corresponding value.
+     */
+    @Override
+    protected void clearPropertyDirect(String key)
+    {
+        if (containsKey(key))
+        {
+            store.remove(key);
+        }
+    }
+
+    @Override
+    public void clear()
+    {
+        fireEvent(EVENT_CLEAR, null, null, true);
+        store.clear();
+        fireEvent(EVENT_CLEAR, null, null, false);
+    }
+
+    /**
+     * Get the list of the keys contained in the configuration
+     * repository.
+     *
+     * @return An Iterator.
+     */
+    public Iterator<String> getKeys()
+    {
+        return store.keySet().iterator();
+    }
+
+    /**
+     * Creates a copy of this object. This implementation will create a deep
+     * clone, i.e. the map that stores the properties is cloned, too. So changes
+     * performed at the copy won't affect the original and vice versa.
+     *
+     * @return the copy
+     * @since 1.3
+     */
+    @Override
+    public Object clone()
+    {
+        try
+        {
+            BaseConfiguration copy = (BaseConfiguration) super.clone();
+            // This is safe because the type of the map is known
+            @SuppressWarnings("unchecked")
+            Map<String, Object> clonedStore = (Map<String, Object>) ConfigurationUtils.clone(store);
+            copy.store = clonedStore;
+
+            // Handle collections in the map; they have to be cloned, too
+            for (Map.Entry<String, Object> e : store.entrySet())
+            {
+                if (e.getValue() instanceof Collection)
+                {
+                    // This is safe because the collections were created by ourselves
+                    @SuppressWarnings("unchecked")
+                    Collection<String> strList = (Collection<String>) e.getValue();
+                    copy.store.put(e.getKey(), new ArrayList<String>(strList));
+                }
+            }
+
+            return copy;
+        }
+        catch (CloneNotSupportedException cex)
+        {
+            // should not happen
+            throw new ConfigurationRuntimeException(cex);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/BaseConfigurationXMLReader.java b/src/main/java/org/apache/commons/configuration/BaseConfigurationXMLReader.java
new file mode 100644
index 0000000..20a3e7b
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/BaseConfigurationXMLReader.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+/**
+ * <p>A specialized SAX2 XML parser that processes configuration objects.</p>
+ *
+ * <p>This class mimics to be a SAX compliant XML parser. It is able to iterate
+ * over the keys in a configuration object and to generate corresponding SAX
+ * events. By registering a {@code ContentHandler} at an instance
+ * it is possible to perform XML processing on a configuration object.</p>
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: BaseConfigurationXMLReader.java 1206765 2011-11-27 16:46:11Z oheger $
+ */
+public class BaseConfigurationXMLReader extends ConfigurationXMLReader
+{
+    /** Stores the actual configuration.*/
+    private Configuration config;
+
+    /**
+     * Creates a new instance of {@code BaseConfigurationXMLReader}.
+     */
+    public BaseConfigurationXMLReader()
+    {
+        super();
+    }
+
+    /**
+     * Creates a new instance of {@code BaseConfigurationXMLReader} and
+     * sets the configuration object to be parsed.
+     *
+     * @param conf the configuration to be parsed
+     */
+    public BaseConfigurationXMLReader(Configuration conf)
+    {
+        this();
+        setConfiguration(conf);
+    }
+
+    /**
+     * Returns the actual configuration to be processed.
+     *
+     * @return the actual configuration
+     */
+    public Configuration getConfiguration()
+    {
+        return config;
+    }
+
+    /**
+     * Sets the configuration to be processed.
+     *
+     * @param conf the configuration
+     */
+    public void setConfiguration(Configuration conf)
+    {
+        config = conf;
+    }
+
+    /**
+     * Returns the configuration to be processed.
+     *
+     * @return the actual configuration
+     */
+    @Override
+    public Configuration getParsedConfiguration()
+    {
+        return getConfiguration();
+    }
+
+    /**
+     * The main SAX event generation method. This element uses an internal
+     * {@code HierarchicalConfigurationConverter} object to iterate over
+     * all keys in the actual configuration and to generate corresponding SAX
+     * events.
+     */
+    @Override
+    protected void processKeys()
+    {
+        fireElementStart(getRootName(), null);
+        new SAXConverter().process(getConfiguration());
+        fireElementEnd(getRootName());
+    }
+
+    /**
+     * An internally used helper class to iterate over all configuration keys
+     * ant to generate corresponding SAX events.
+     *
+     */
+    class SAXConverter extends HierarchicalConfigurationConverter
+    {
+        /**
+         * Callback for the start of an element.
+         *
+         * @param name the element name
+         * @param value the element value
+         */
+        @Override
+        protected void elementStart(String name, Object value)
+        {
+            fireElementStart(name, null);
+            if (value != null)
+            {
+                fireCharacters(value.toString());
+            }
+        }
+
+        /**
+         * Callback for the end of an element.
+         *
+         * @param name the element name
+         */
+        @Override
+        protected void elementEnd(String name)
+        {
+            fireElementEnd(name);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/CombinedConfiguration.java b/src/main/java/org/apache/commons/configuration/CombinedConfiguration.java
new file mode 100644
index 0000000..6a96e32
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/CombinedConfiguration.java
@@ -0,0 +1,976 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.configuration.event.ConfigurationEvent;
+import org.apache.commons.configuration.event.ConfigurationListener;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.DefaultConfigurationKey;
+import org.apache.commons.configuration.tree.DefaultConfigurationNode;
+import org.apache.commons.configuration.tree.DefaultExpressionEngine;
+import org.apache.commons.configuration.tree.ExpressionEngine;
+import org.apache.commons.configuration.tree.NodeCombiner;
+import org.apache.commons.configuration.tree.TreeUtils;
+import org.apache.commons.configuration.tree.UnionCombiner;
+import org.apache.commons.configuration.tree.ViewNode;
+
+/**
+ * <p>
+ * A hierarchical composite configuration class.
+ * </p>
+ * <p>
+ * This class maintains a list of configuration objects, which can be added
+ * using the divers {@code addConfiguration()} methods. After that the
+ * configurations can be accessed either by name (if one was provided when the
+ * configuration was added) or by index. For the whole set of managed
+ * configurations a logical node structure is constructed. For this purpose a
+ * {@link org.apache.commons.configuration.tree.NodeCombiner NodeCombiner}
+ * object can be set. This makes it possible to specify different algorithms for
+ * the combination process.
+ * </p>
+ * <p>
+ * The big advantage of this class is that it creates a truly hierarchical
+ * structure of all the properties stored in the contained configurations - even
+ * if some of them are no hierarchical configurations per se. So all enhanced
+ * features provided by a hierarchical configuration (e.g. choosing an
+ * expression engine) are applicable.
+ * </p>
+ * <p>
+ * The class works by registering itself as an event listener at all added
+ * configurations. So it gets notified whenever one of these configurations is
+ * changed and can invalidate its internal node structure. The next time a
+ * property is accessed the node structure will be re-constructed using the
+ * current state of the managed configurations. Note that, depending on the used
+ * {@code NodeCombiner}, this may be a complex operation.
+ * </p>
+ * <p>
+ * Because of the way a {@code CombinedConfiguration} is working it has
+ * more or less view character: it provides a logic view on the configurations
+ * it contains. In this constellation not all methods defined for hierarchical
+ * configurations - especially methods that update the stored properties - can
+ * be implemented in a consistent manner. Using such methods (like
+ * {@code addProperty()}, or {@code clearProperty()} on a
+ * {@code CombinedConfiguration} is not strictly forbidden, however,
+ * depending on the current {@link NodeCombiner} and the involved
+ * properties, the results may be different than expected. Some examples may
+ * illustrate this:
+ * </p>
+ * <p>
+ * <ul>
+ * <li>Imagine a {@code CombinedConfiguration} <em>cc</em> containing
+ * two child configurations with the following content:
+ * <dl>
+ * <dt>user.properties</dt>
+ * <dd>
+ *
+ * <pre>
+ * gui.background = blue
+ * gui.position = (10, 10, 400, 200)
+ * </pre>
+ *
+ * </dd>
+ * <dt>default.properties</dt>
+ * <dd>
+ *
+ * <pre>
+ * gui.background = black
+ * gui.foreground = white
+ * home.dir = /data
+ * </pre>
+ *
+ * </dd>
+ * </dl>
+ * As a {@code NodeCombiner} a
+ * {@link org.apache.commons.configuration.tree.OverrideCombiner OverrideCombiner}
+ * is used. This combiner will ensure that defined user settings take precedence
+ * over the default values. If the resulting {@code CombinedConfiguration}
+ * is queried for the background color, {@code blue} will be returned
+ * because this value is defined in {@code user.properties}. Now
+ * consider what happens if the key {@code gui.background} is removed
+ * from the {@code CombinedConfiguration}:
+ *
+ * <pre>cc.clearProperty("gui.background");</pre>
+ *
+ * Will a {@code cc.containsKey("gui.background")} now return <b>false</b>?
+ * No, it won't! The {@code clearProperty()} operation is executed on the
+ * node set of the combined configuration, which was constructed from the nodes
+ * of the two child configurations. It causes the value of the
+ * <em>background</em> node to be cleared, which is also part of the first
+ * child configuration. This modification of one of its child configurations
+ * causes the {@code CombinedConfiguration} to be re-constructed. This
+ * time the {@code OverrideCombiner} cannot find a
+ * {@code gui.background} property in the first child configuration, but
+ * it finds one in the second, and adds it to the resulting combined
+ * configuration. So the property is still present (with a different value now).</li>
+ * <li>{@code addProperty()} can also be problematic: Most node
+ * combiners use special view nodes for linking parts of the original
+ * configurations' data together. If new properties are added to such a special
+ * node, they do not belong to any of the managed configurations and thus hang
+ * in the air. Using the same configurations as in the last example, the
+ * statement
+ *
+ * <pre>
+ * addProperty("database.user", "scott");
+ * </pre>
+ *
+ * would cause such a hanging property. If now one of the child configurations
+ * is changed and the {@code CombinedConfiguration} is re-constructed,
+ * this property will disappear! (Add operations are not problematic if they
+ * result in a child configuration being updated. For instance an
+ * {@code addProperty("home.url", "localhost");} will alter the second
+ * child configuration - because the prefix <em>home</em> is here already
+ * present; when the {@code CombinedConfiguration} is re-constructed,
+ * this change is taken into account.)</li>
+ * </ul>
+ * Because of such problems it is recommended to perform updates only on the
+ * managed child configurations.
+ * </p>
+ * <p>
+ * Whenever the node structure of a {@code CombinedConfiguration} becomes
+ * invalid (either because one of the contained configurations was modified or
+ * because the {@code invalidate()} method was directly called) an event
+ * is generated. So this can be detected by interested event listeners. This
+ * also makes it possible to add a combined configuration into another one.
+ * </p>
+ * <p>
+ * Implementation note: Adding and removing configurations to and from a
+ * combined configuration is not thread-safe. If a combined configuration is
+ * manipulated by multiple threads, the developer has to take care about
+ * properly synchronization.
+ * </p>
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @since 1.3
+ * @version $Id: CombinedConfiguration.java 1234985 2012-01-23 21:09:09Z oheger $
+ */
+public class CombinedConfiguration extends HierarchicalReloadableConfiguration implements
+        ConfigurationListener, Cloneable
+{
+    /**
+     * Constant for the invalidate event that is fired when the internal node
+     * structure becomes invalid.
+     */
+    public static final int EVENT_COMBINED_INVALIDATE = 40;
+
+    /**
+     * The serial version ID.
+     */
+    private static final long serialVersionUID = 8338574525528692307L;
+
+    /** Constant for the expression engine for parsing the at path. */
+    private static final DefaultExpressionEngine AT_ENGINE = new DefaultExpressionEngine();
+
+    /** Constant for the default node combiner. */
+    private static final NodeCombiner DEFAULT_COMBINER = new UnionCombiner();
+
+    /** Constant for the name of the property used for the reload check.*/
+    private static final String PROP_RELOAD_CHECK = "CombinedConfigurationReloadCheck";
+
+    /** Stores the combiner. */
+    private NodeCombiner nodeCombiner;
+
+    /** Stores the combined root node. */
+    private volatile ConfigurationNode combinedRoot;
+
+    /** Stores a list with the contained configurations. */
+    private List<ConfigData> configurations;
+
+    /** Stores a map with the named configurations. */
+    private Map<String, AbstractConfiguration> namedConfigurations;
+
+    /** The default behavior is to ignore exceptions that occur during reload */
+    private boolean ignoreReloadExceptions = true;
+
+    /** Set to true when the backing file has changed */
+    private boolean reloadRequired;
+
+    /**
+     * An expression engine used for converting child configurations to
+     * hierarchical ones.
+     */
+    private ExpressionEngine conversionExpressionEngine;
+
+    /** A flag whether an enhanced reload check is to be performed.*/
+    private boolean forceReloadCheck;
+
+    /**
+     * Creates a new instance of {@code CombinedConfiguration} and
+     * initializes the combiner to be used.
+     *
+     * @param comb the node combiner (can be <b>null</b>, then a union combiner
+     * is used as default)
+     */
+    public CombinedConfiguration(NodeCombiner comb)
+    {
+        setNodeCombiner((comb != null) ? comb : DEFAULT_COMBINER);
+        clear();
+    }
+
+    public CombinedConfiguration(NodeCombiner comb, Lock lock)
+    {
+        super(lock);
+        setNodeCombiner((comb != null) ? comb : DEFAULT_COMBINER);
+        clear();
+    }
+
+    public CombinedConfiguration(Lock lock)
+    {
+        this(null, lock);
+    }
+
+    /**
+     * Creates a new instance of {@code CombinedConfiguration} that uses
+     * a union combiner.
+     *
+     * @see org.apache.commons.configuration.tree.UnionCombiner
+     */
+    public CombinedConfiguration()
+    {
+        this(null, null);
+    }
+
+    /**
+     * Returns the node combiner that is used for creating the combined node
+     * structure.
+     *
+     * @return the node combiner
+     */
+    public NodeCombiner getNodeCombiner()
+    {
+        return nodeCombiner;
+    }
+
+    /**
+     * Sets the node combiner. This object will be used when the combined node
+     * structure is to be constructed. It must not be <b>null</b>, otherwise an
+     * {@code IllegalArgumentException} exception is thrown. Changing the
+     * node combiner causes an invalidation of this combined configuration, so
+     * that the new combiner immediately takes effect.
+     *
+     * @param nodeCombiner the node combiner
+     */
+    public void setNodeCombiner(NodeCombiner nodeCombiner)
+    {
+        if (nodeCombiner == null)
+        {
+            throw new IllegalArgumentException(
+                    "Node combiner must not be null!");
+        }
+        this.nodeCombiner = nodeCombiner;
+        invalidate();
+    }
+
+    /**
+     * Returns a flag whether an enhanced reload check must be performed.
+     *
+     * @return the force reload check flag
+     * @since 1.4
+     */
+    public boolean isForceReloadCheck()
+    {
+        return forceReloadCheck;
+    }
+
+    /**
+     * Sets the force reload check flag. If this flag is set, each property
+     * access on this configuration will cause a reload check on the contained
+     * configurations. This is a workaround for a problem with some reload
+     * implementations that only check if a reload is required when they are
+     * triggered. Per default this mode is disabled. If the force reload check
+     * flag is set to <b>true</b>, accessing properties will be less
+     * efficient, but reloads on contained configurations will be detected.
+     *
+     * @param forceReloadCheck the value of the flag
+     * @since 1.4
+     */
+    public void setForceReloadCheck(boolean forceReloadCheck)
+    {
+        this.forceReloadCheck = forceReloadCheck;
+    }
+
+    /**
+     * Returns the {@code ExpressionEngine} for converting flat child
+     * configurations to hierarchical ones.
+     *
+     * @return the conversion expression engine
+     * @since 1.6
+     */
+    public ExpressionEngine getConversionExpressionEngine()
+    {
+        return conversionExpressionEngine;
+    }
+
+    /**
+     * Sets the {@code ExpressionEngine} for converting flat child
+     * configurations to hierarchical ones. When constructing the root node for
+     * this combined configuration the properties of all child configurations
+     * must be combined to a single hierarchical node structure. In this
+     * process, non hierarchical configurations are converted to hierarchical
+     * ones first. This can be problematic if a child configuration contains
+     * keys that are no compatible with the default expression engine used by
+     * hierarchical configurations. Therefore it is possible to specify a
+     * specific expression engine to be used for this purpose.
+     *
+     * @param conversionExpressionEngine the conversion expression engine
+     * @see ConfigurationUtils#convertToHierarchical(Configuration, ExpressionEngine)
+     * @since 1.6
+     */
+    public void setConversionExpressionEngine(
+            ExpressionEngine conversionExpressionEngine)
+    {
+        this.conversionExpressionEngine = conversionExpressionEngine;
+    }
+
+    /**
+     * Retrieves the value of the ignoreReloadExceptions flag.
+     * @return true if exceptions are ignored, false otherwise.
+     */
+    public boolean isIgnoreReloadExceptions()
+    {
+        return ignoreReloadExceptions;
+    }
+
+    /**
+     * If set to true then exceptions that occur during reloading will be
+     * ignored. If false then the exceptions will be allowed to be thrown
+     * back to the caller.
+     * @param ignoreReloadExceptions true if exceptions should be ignored.
+     */
+    public void setIgnoreReloadExceptions(boolean ignoreReloadExceptions)
+    {
+        this.ignoreReloadExceptions = ignoreReloadExceptions;
+    }
+
+    /**
+     * Adds a new configuration to this combined configuration. It is possible
+     * (but not mandatory) to give the new configuration a name. This name must
+     * be unique, otherwise a {@code ConfigurationRuntimeException} will
+     * be thrown. With the optional {@code at} argument you can specify
+     * where in the resulting node structure the content of the added
+     * configuration should appear. This is a string that uses dots as property
+     * delimiters (independent on the current expression engine). For instance
+     * if you pass in the string {@code "database.tables"},
+     * all properties of the added configuration will occur in this branch.
+     *
+     * @param config the configuration to add (must not be <b>null</b>)
+     * @param name the name of this configuration (can be <b>null</b>)
+     * @param at the position of this configuration in the combined tree (can be
+     * <b>null</b>)
+     */
+    public void addConfiguration(AbstractConfiguration config, String name,
+            String at)
+    {
+        if (config == null)
+        {
+            throw new IllegalArgumentException(
+                    "Added configuration must not be null!");
+        }
+        if (name != null && namedConfigurations.containsKey(name))
+        {
+            throw new ConfigurationRuntimeException(
+                    "A configuration with the name '"
+                            + name
+                            + "' already exists in this combined configuration!");
+        }
+
+        ConfigData cd = new ConfigData(config, name, at);
+        if (getLogger().isDebugEnabled())
+        {
+            getLogger().debug("Adding configuration " + config + " with name " + name);
+        }
+        configurations.add(cd);
+        if (name != null)
+        {
+            namedConfigurations.put(name, config);
+        }
+
+        config.addConfigurationListener(this);
+        invalidate();
+    }
+
+    /**
+     * Adds a new configuration to this combined configuration with an optional
+     * name. The new configuration's properties will be added under the root of
+     * the combined node structure.
+     *
+     * @param config the configuration to add (must not be <b>null</b>)
+     * @param name the name of this configuration (can be <b>null</b>)
+     */
+    public void addConfiguration(AbstractConfiguration config, String name)
+    {
+        addConfiguration(config, name, null);
+    }
+
+    /**
+     * Adds a new configuration to this combined configuration. The new
+     * configuration is not given a name. Its properties will be added under the
+     * root of the combined node structure.
+     *
+     * @param config the configuration to add (must not be <b>null</b>)
+     */
+    public void addConfiguration(AbstractConfiguration config)
+    {
+        addConfiguration(config, null, null);
+    }
+
+    /**
+     * Returns the number of configurations that are contained in this combined
+     * configuration.
+     *
+     * @return the number of contained configurations
+     */
+    public int getNumberOfConfigurations()
+    {
+        return configurations.size();
+    }
+
+    /**
+     * Returns the configuration at the specified index. The contained
+     * configurations are numbered in the order they were added to this combined
+     * configuration. The index of the first configuration is 0.
+     *
+     * @param index the index
+     * @return the configuration at this index
+     */
+    public Configuration getConfiguration(int index)
+    {
+        ConfigData cd = configurations.get(index);
+        return cd.getConfiguration();
+    }
+
+    /**
+     * Returns the configuration with the given name. This can be <b>null</b>
+     * if no such configuration exists.
+     *
+     * @param name the name of the configuration
+     * @return the configuration with this name
+     */
+    public Configuration getConfiguration(String name)
+    {
+        return namedConfigurations.get(name);
+    }
+
+    /**
+     * Returns a List of all the configurations that have been added.
+     * @return A List of all the configurations.
+     * @since 1.7
+     */
+    public List<AbstractConfiguration> getConfigurations()
+    {
+        List<AbstractConfiguration> list = new ArrayList<AbstractConfiguration>(configurations.size());
+        for (ConfigData cd : configurations)
+        {
+            list.add(cd.getConfiguration());
+        }
+        return list;
+    }
+
+    /**
+     * Returns a List of the names of all the configurations that have been
+     * added in the order they were added. A NULL value will be present in
+     * the list for each configuration that was added without a name.
+     * @return A List of all the configuration names.
+     * @since 1.7
+     */
+    public List<String> getConfigurationNameList()
+    {
+        List<String> list = new ArrayList<String>(configurations.size());
+        for (ConfigData cd : configurations)
+        {
+            list.add(cd.getName());
+        }
+        return list;
+    }
+
+    /**
+     * Removes the specified configuration from this combined configuration.
+     *
+     * @param config the configuration to be removed
+     * @return a flag whether this configuration was found and could be removed
+     */
+    public boolean removeConfiguration(Configuration config)
+    {
+        for (int index = 0; index < getNumberOfConfigurations(); index++)
+        {
+            if (configurations.get(index).getConfiguration() == config)
+            {
+                removeConfigurationAt(index);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Removes the configuration at the specified index.
+     *
+     * @param index the index
+     * @return the removed configuration
+     */
+    public Configuration removeConfigurationAt(int index)
+    {
+        ConfigData cd = configurations.remove(index);
+        if (cd.getName() != null)
+        {
+            namedConfigurations.remove(cd.getName());
+        }
+        cd.getConfiguration().removeConfigurationListener(this);
+        invalidate();
+        return cd.getConfiguration();
+    }
+
+    /**
+     * Removes the configuration with the specified name.
+     *
+     * @param name the name of the configuration to be removed
+     * @return the removed configuration (<b>null</b> if this configuration
+     * was not found)
+     */
+    public Configuration removeConfiguration(String name)
+    {
+        Configuration conf = getConfiguration(name);
+        if (conf != null)
+        {
+            removeConfiguration(conf);
+        }
+        return conf;
+    }
+
+    /**
+     * Returns a set with the names of all configurations contained in this
+     * combined configuration. Of course here are only these configurations
+     * listed, for which a name was specified when they were added.
+     *
+     * @return a set with the names of the contained configurations (never
+     * <b>null</b>)
+     */
+    public Set<String> getConfigurationNames()
+    {
+        return namedConfigurations.keySet();
+    }
+
+    /**
+     * Invalidates this combined configuration. This means that the next time a
+     * property is accessed the combined node structure must be re-constructed.
+     * Invalidation of a combined configuration also means that an event of type
+     * {@code EVENT_COMBINED_INVALIDATE} is fired. Note that while other
+     * events most times appear twice (once before and once after an update),
+     * this event is only fired once (after update).
+     */
+    public void invalidate()
+    {
+        reloadRequired = true;
+        fireEvent(EVENT_COMBINED_INVALIDATE, null, null, false);
+    }
+
+    /**
+     * Event listener call back for configuration update events. This method is
+     * called whenever one of the contained configurations was modified. It
+     * invalidates this combined configuration.
+     *
+     * @param event the update event
+     */
+    public void configurationChanged(ConfigurationEvent event)
+    {
+        if (event.getType() == AbstractFileConfiguration.EVENT_CONFIG_CHANGED)
+        {
+            fireEvent(event.getType(), event.getPropertyName(), event.getPropertyValue(), event.isBeforeUpdate());
+        }
+        else if (!event.isBeforeUpdate())
+        {
+            invalidate();
+        }
+    }
+
+    /**
+     * Returns the configuration root node of this combined configuration. This
+     * method will construct a combined node structure using the current node
+     * combiner if necessary.
+     *
+     * @return the combined root node
+     */
+    @Override
+    public ConfigurationNode getRootNode()
+    {
+        synchronized (getReloadLock())
+        {
+            if (reloadRequired || combinedRoot == null)
+            {
+                combinedRoot = constructCombinedNode();
+                reloadRequired = false;
+            }
+            return combinedRoot;
+        }
+    }
+
+    /**
+     * Clears this configuration. All contained configurations will be removed.
+     */
+    @Override
+    public void clear()
+    {
+        fireEvent(EVENT_CLEAR, null, null, true);
+        configurations = new ArrayList<ConfigData>();
+        namedConfigurations = new HashMap<String, AbstractConfiguration>();
+        fireEvent(EVENT_CLEAR, null, null, false);
+        invalidate();
+    }
+
+    /**
+     * Returns a copy of this object. This implementation performs a deep clone,
+     * i.e. all contained configurations will be cloned, too. For this to work,
+     * all contained configurations must be cloneable. Registered event
+     * listeners won't be cloned. The clone will use the same node combiner than
+     * the original.
+     *
+     * @return the copied object
+     */
+    @Override
+    public Object clone()
+    {
+        CombinedConfiguration copy = (CombinedConfiguration) super.clone();
+        copy.clear();
+        for (ConfigData cd : configurations)
+        {
+            copy.addConfiguration((AbstractConfiguration) ConfigurationUtils
+                    .cloneConfiguration(cd.getConfiguration()), cd.getName(),
+                    cd.getAt());
+        }
+
+        copy.setRootNode(new DefaultConfigurationNode());
+        return copy;
+    }
+
+    /**
+     * Returns the configuration source, in which the specified key is defined.
+     * This method will determine the configuration node that is identified by
+     * the given key. The following constellations are possible:
+     * <ul>
+     * <li>If no node object is found for this key, <b>null</b> is returned.</li>
+     * <li>If the key maps to multiple nodes belonging to different
+     * configuration sources, a {@code IllegalArgumentException} is
+     * thrown (in this case no unique source can be determined).</li>
+     * <li>If exactly one node is found for the key, the (child) configuration
+     * object, to which the node belongs is determined and returned.</li>
+     * <li>For keys that have been added directly to this combined
+     * configuration and that do not belong to the namespaces defined by
+     * existing child configurations this configuration will be returned.</li>
+     * </ul>
+     *
+     * @param key the key of a configuration property
+     * @return the configuration, to which this property belongs or <b>null</b>
+     * if the key cannot be resolved
+     * @throws IllegalArgumentException if the key maps to multiple properties
+     * and the source cannot be determined, or if the key is <b>null</b>
+     * @since 1.5
+     */
+    public Configuration getSource(String key)
+    {
+        if (key == null)
+        {
+            throw new IllegalArgumentException("Key must not be null!");
+        }
+
+        List<ConfigurationNode> nodes = fetchNodeList(key);
+        if (nodes.isEmpty())
+        {
+            return null;
+        }
+
+        Iterator<ConfigurationNode> it = nodes.iterator();
+        Configuration source = findSourceConfiguration(it.next());
+        while (it.hasNext())
+        {
+            Configuration src = findSourceConfiguration(it.next());
+            if (src != source)
+            {
+                throw new IllegalArgumentException("The key " + key
+                        + " is defined by multiple sources!");
+            }
+        }
+
+        return source;
+    }
+
+    /**
+     * Evaluates the passed in property key and returns a list with the matching
+     * configuration nodes. This implementation also evaluates the
+     * <em>force reload check</em> flag. If it is set,
+     * {@code performReloadCheck()} is invoked.
+     *
+     * @param key the property key
+     * @return a list with the matching configuration nodes
+     */
+    @Override
+    protected List<ConfigurationNode> fetchNodeList(String key)
+    {
+        if (isForceReloadCheck())
+        {
+            performReloadCheck();
+        }
+
+        return super.fetchNodeList(key);
+    }
+
+    /**
+     * Triggers the contained configurations to perform a reload check if
+     * necessary. This method is called when a property of this combined
+     * configuration is accessed and the {@code forceReloadCheck} property
+     * is set to <b>true</b>.
+     *
+     * @see #setForceReloadCheck(boolean)
+     * @since 1.6
+     */
+    protected void performReloadCheck()
+    {
+        for (ConfigData cd : configurations)
+        {
+            try
+            {
+                // simply retrieve a property; this is enough for
+                // triggering a reload
+                cd.getConfiguration().getProperty(PROP_RELOAD_CHECK);
+            }
+            catch (Exception ex)
+            {
+                if (!ignoreReloadExceptions)
+                {
+                    throw new ConfigurationRuntimeException(ex);
+                }
+            }
+        }
+    }
+
+    /**
+     * Creates the root node of this combined configuration.
+     *
+     * @return the combined root node
+     */
+    private ConfigurationNode constructCombinedNode()
+    {
+        if (getNumberOfConfigurations() < 1)
+        {
+            if (getLogger().isDebugEnabled())
+            {
+                getLogger().debug("No configurations defined for " + this);
+            }
+            return new ViewNode();
+        }
+
+        else
+        {
+            Iterator<ConfigData> it = configurations.iterator();
+            ConfigurationNode node = it.next().getTransformedRoot();
+            while (it.hasNext())
+            {
+                node = getNodeCombiner().combine(node,
+                        it.next().getTransformedRoot());
+            }
+            if (getLogger().isDebugEnabled())
+            {
+                ByteArrayOutputStream os = new ByteArrayOutputStream();
+                PrintStream stream = new PrintStream(os);
+                TreeUtils.printTree(stream, node);
+                getLogger().debug(os.toString());
+            }
+            return node;
+        }
+    }
+
+    /**
+     * Determines the configuration that owns the specified node.
+     *
+     * @param node the node
+     * @return the owning configuration
+     */
+    private Configuration findSourceConfiguration(ConfigurationNode node)
+    {
+        synchronized (getReloadLock())
+        {
+            ConfigurationNode root = null;
+            ConfigurationNode current = node;
+
+            // find the root node in this hierarchy
+            while (current != null)
+            {
+                root = current;
+                current = current.getParentNode();
+            }
+
+            // Check with the root nodes of the child configurations
+            for (ConfigData cd : configurations)
+            {
+                if (root == cd.getRootNode())
+                {
+                    return cd.getConfiguration();
+                }
+            }
+        }
+
+        return this;
+    }
+
+    /**
+     * An internal helper class for storing information about contained
+     * configurations.
+     */
+    class ConfigData
+    {
+        /** Stores a reference to the configuration. */
+        private AbstractConfiguration configuration;
+
+        /** Stores the name under which the configuration is stored. */
+        private String name;
+
+        /** Stores the at information as path of nodes. */
+        private Collection<String> atPath;
+
+        /** Stores the at string.*/
+        private String at;
+
+        /** Stores the root node for this child configuration.*/
+        private ConfigurationNode rootNode;
+
+        /**
+         * Creates a new instance of {@code ConfigData} and initializes
+         * it.
+         *
+         * @param config the configuration
+         * @param n the name
+         * @param at the at position
+         */
+        public ConfigData(AbstractConfiguration config, String n, String at)
+        {
+            configuration = config;
+            name = n;
+            atPath = parseAt(at);
+            this.at = at;
+        }
+
+        /**
+         * Returns the stored configuration.
+         *
+         * @return the configuration
+         */
+        public AbstractConfiguration getConfiguration()
+        {
+            return configuration;
+        }
+
+        /**
+         * Returns the configuration's name.
+         *
+         * @return the name
+         */
+        public String getName()
+        {
+            return name;
+        }
+
+        /**
+         * Returns the at position of this configuration.
+         *
+         * @return the at position
+         */
+        public String getAt()
+        {
+            return at;
+        }
+
+        /**
+         * Returns the root node for this child configuration.
+         *
+         * @return the root node of this child configuration
+         * @since 1.5
+         */
+        public ConfigurationNode getRootNode()
+        {
+            return rootNode;
+        }
+
+        /**
+         * Returns the transformed root node of the stored configuration. The
+         * term "transformed" means that an eventually defined at path
+         * has been applied.
+         *
+         * @return the transformed root node
+         */
+        public ConfigurationNode getTransformedRoot()
+        {
+            ViewNode result = new ViewNode();
+            ViewNode atParent = result;
+
+            if (atPath != null)
+            {
+                // Build the complete path
+                for (String p : atPath)
+                {
+                    ViewNode node = new ViewNode();
+                    node.setName(p);
+                    atParent.addChild(node);
+                    atParent = node;
+                }
+            }
+
+            // Copy data of the root node to the new path
+            ConfigurationNode root = ConfigurationUtils
+                    .convertToHierarchical(getConfiguration(),
+                            getConversionExpressionEngine()).getRootNode();
+            atParent.appendChildren(root);
+            atParent.appendAttributes(root);
+            rootNode = root;
+
+            return result;
+        }
+
+        /**
+         * Splits the at path into its components.
+         *
+         * @param at the at string
+         * @return a collection with the names of the single components
+         */
+        private Collection<String> parseAt(String at)
+        {
+            if (at == null)
+            {
+                return null;
+            }
+
+            Collection<String> result = new ArrayList<String>();
+            DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
+                    AT_ENGINE, at).iterator();
+            while (it.hasNext())
+            {
+                result.add(it.nextKey());
+            }
+            return result;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/CompositeConfiguration.java b/src/main/java/org/apache/commons/configuration/CompositeConfiguration.java
new file mode 100644
index 0000000..80aa304
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/CompositeConfiguration.java
@@ -0,0 +1,578 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
+
+/**
+ * <p>{@code CompositeConfiguration} allows you to add multiple {@code Configuration}
+ * objects to an aggregated configuration. If you add Configuration1, and then Configuration2,
+ * any properties shared will mean that the value defined by Configuration1
+ * will be returned. If Configuration1 doesn't have the property, then
+ * Configuration2 will be checked. You can add multiple different types or the
+ * same type of properties file.</p>
+ * <p>When querying properties the order in which child configurations have been
+ * added is relevant. To deal with property updates, a so-called <em>in-memory
+ * configuration</em> is used. Per default, such a configuration is created
+ * automatically. All property writes target this special configuration. There
+ * are constructors which allow you to provide a specific in-memory configuration.
+ * If used that way, the in-memory configuration is always the last one in the
+ * list of child configurations. This means that for query operations all other
+ * configurations take precedence.</p>
+ * <p>Alternatively it is possible to mark a child configuration as in-memory
+ * configuration when it is added. In this case the treatment of the in-memory
+ * configuration is slightly different: it remains in the list of child
+ * configurations at the position it was added, i.e. its priority for property
+ * queries can be defined by adding the child configurations in the correct
+ * order.</p>
+ *
+ * @author <a href="mailto:epugh at upstate.com">Eric Pugh</a>
+ * @author <a href="mailto:hps at intermeta.de">Henning P. Schmiedehausen</a>
+ * @version $Id: CompositeConfiguration.java 1534064 2013-10-21 08:44:33Z henning $
+ */
+public class CompositeConfiguration extends AbstractConfiguration
+implements Cloneable
+{
+    /** List holding all the configuration */
+    private List<Configuration> configList = new LinkedList<Configuration>();
+
+    /**
+     * Configuration that holds in memory stuff.  Inserted as first so any
+     * setProperty() override anything else added.
+     */
+    private Configuration inMemoryConfiguration;
+
+    /**
+     * Stores a flag whether the current in-memory configuration is also a
+     * child configuration.
+     */
+    private boolean inMemoryConfigIsChild;
+
+    /**
+     * Creates an empty CompositeConfiguration object which can then
+     * be added some other Configuration files
+     */
+    public CompositeConfiguration()
+    {
+        clear();
+    }
+
+    /**
+     * Creates a CompositeConfiguration object with a specified <em>in-memory
+     * configuration</em>. This configuration will store any changes made to the
+     * {@code CompositeConfiguration}. Note: Use this constructor if you want to
+     * set a special type of in-memory configuration. If you have a
+     * configuration which should act as both a child configuration and as
+     * in-memory configuration, use
+     * {@link #addConfiguration(Configuration, boolean)} with a value of
+     * <b>true</b> instead.
+     *
+     * @param inMemoryConfiguration the in memory configuration to use
+     */
+    public CompositeConfiguration(Configuration inMemoryConfiguration)
+    {
+        configList.clear();
+        this.inMemoryConfiguration = inMemoryConfiguration;
+        configList.add(inMemoryConfiguration);
+    }
+
+    /**
+     * Create a CompositeConfiguration with an empty in memory configuration
+     * and adds the collection of configurations specified.
+     *
+     * @param configurations the collection of configurations to add
+     */
+    public CompositeConfiguration(Collection<? extends Configuration> configurations)
+    {
+        this(new BaseConfiguration(), configurations);
+    }
+
+    /**
+     * Creates a CompositeConfiguration with a specified <em>in-memory
+     * configuration</em>, and then adds the given collection of configurations.
+     *
+     * @param inMemoryConfiguration the in memory configuration to use
+     * @param configurations        the collection of configurations to add
+     * @see #CompositeConfiguration(Configuration)
+     */
+    public CompositeConfiguration(Configuration inMemoryConfiguration,
+            Collection<? extends Configuration> configurations)
+    {
+        this(inMemoryConfiguration);
+
+        if (configurations != null)
+        {
+            for (Configuration c : configurations)
+            {
+                addConfiguration(c);
+            }
+        }
+    }
+
+    /**
+     * Add a configuration.
+     *
+     * @param config the configuration to add
+     */
+    public void addConfiguration(Configuration config)
+    {
+        addConfiguration(config, false);
+    }
+
+    /**
+     * Adds a child configuration and optionally makes it the <em>in-memory
+     * configuration</em>. This means that all future property write operations
+     * are executed on this configuration. Note that the current in-memory
+     * configuration is replaced by the new one. If it was created automatically
+     * or passed to the constructor, it is removed from the list of child
+     * configurations! Otherwise, it stays in the list of child configurations
+     * at its current position, but it passes its role as in-memory
+     * configuration to the new one.
+     *
+     * @param config the configuration to be added
+     * @param asInMemory <b>true</b> if this configuration becomes the new
+     *        <em>in-memory</em> configuration, <b>false</b> otherwise
+     * @since 1.8
+     */
+    public void addConfiguration(Configuration config, boolean asInMemory)
+    {
+        if (!configList.contains(config))
+        {
+            if (asInMemory)
+            {
+                replaceInMemoryConfiguration(config);
+                inMemoryConfigIsChild = true;
+            }
+
+            if (!inMemoryConfigIsChild)
+            {
+                // As the inMemoryConfiguration contains all manually added
+                // keys, we must make sure that it is always last. "Normal", non
+                // composed configurations add their keys at the end of the
+                // configuration and we want to mimic this behavior.
+                configList.add(configList.indexOf(inMemoryConfiguration),
+                        config);
+            }
+            else
+            {
+                // However, if the in-memory configuration is a regular child,
+                // only the order in which child configurations are added is
+                // relevant
+                configList.add(config);
+            }
+
+            if (config instanceof AbstractConfiguration)
+            {
+                ((AbstractConfiguration) config)
+                        .setThrowExceptionOnMissing(isThrowExceptionOnMissing());
+            }
+        }
+    }
+
+    /**
+     * Remove a configuration. The in memory configuration cannot be removed.
+     *
+     * @param config The configuration to remove
+     */
+    public void removeConfiguration(Configuration config)
+    {
+        // Make sure that you can't remove the inMemoryConfiguration from
+        // the CompositeConfiguration object
+        if (!config.equals(inMemoryConfiguration))
+        {
+            configList.remove(config);
+        }
+    }
+
+    /**
+     * Return the number of configurations.
+     *
+     * @return the number of configuration
+     */
+    public int getNumberOfConfigurations()
+    {
+        return configList.size();
+    }
+
+    /**
+     * Removes all child configurations and reinitializes the <em>in-memory
+     * configuration</em>. <strong>Attention:</strong> A new in-memory
+     * configuration is created; the old one is lost.
+     */
+    @Override
+    public void clear()
+    {
+        configList.clear();
+        // recreate the in memory configuration
+        inMemoryConfiguration = new BaseConfiguration();
+        ((BaseConfiguration) inMemoryConfiguration).setThrowExceptionOnMissing(isThrowExceptionOnMissing());
+        ((BaseConfiguration) inMemoryConfiguration).setListDelimiter(getListDelimiter());
+        ((BaseConfiguration) inMemoryConfiguration).setDelimiterParsingDisabled(isDelimiterParsingDisabled());
+        configList.add(inMemoryConfiguration);
+        inMemoryConfigIsChild = false;
+    }
+
+    /**
+     * Add this property to the inmemory Configuration.
+     *
+     * @param key The Key to add the property to.
+     * @param token The Value to add.
+     */
+    @Override
+    protected void addPropertyDirect(String key, Object token)
+    {
+        inMemoryConfiguration.addProperty(key, token);
+    }
+
+    /**
+     * Read property from underlying composite
+     *
+     * @param key key to use for mapping
+     *
+     * @return object associated with the given configuration key.
+     */
+    public Object getProperty(String key)
+    {
+        Configuration firstMatchingConfiguration = null;
+        for (Configuration config : configList)
+        {
+            if (config.containsKey(key))
+            {
+                firstMatchingConfiguration = config;
+                break;
+            }
+        }
+
+        if (firstMatchingConfiguration != null)
+        {
+            return firstMatchingConfiguration.getProperty(key);
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    public Iterator<String> getKeys()
+    {
+        Set<String> keys = new LinkedHashSet<String>();
+        for (Configuration config : configList)
+        {
+            for (Iterator<String> it = config.getKeys(); it.hasNext();)
+            {
+                keys.add(it.next());
+            }
+        }
+
+        return keys.iterator();
+    }
+
+    @Override
+    public Iterator<String> getKeys(String key)
+    {
+        Set<String> keys = new LinkedHashSet<String>();
+        for (Configuration config : configList)
+        {
+            for (Iterator<String> it = config.getKeys(key); it.hasNext();)
+            {
+                keys.add(it.next());
+            }
+        }
+
+        return keys.iterator();
+    }
+
+    public boolean isEmpty()
+    {
+        for (Configuration config : configList)
+        {
+            if (!config.isEmpty())
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    protected void clearPropertyDirect(String key)
+    {
+        for (Configuration config : configList)
+        {
+            config.clearProperty(key);
+        }
+    }
+
+    public boolean containsKey(String key)
+    {
+        for (Configuration config : configList)
+        {
+            if (config.containsKey(key))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public List<Object> getList(String key, List<?> defaultValue)
+    {
+        List<Object> list = new ArrayList<Object>();
+
+        // add all elements from the first configuration containing the requested key
+        Iterator<Configuration> it = configList.iterator();
+        while (it.hasNext() && list.isEmpty())
+        {
+            Configuration config = it.next();
+            if (config != inMemoryConfiguration && config.containsKey(key))
+            {
+                appendListProperty(list, config, key);
+            }
+        }
+
+        // add all elements from the in memory configuration
+        appendListProperty(list, inMemoryConfiguration, key);
+
+        if (list.isEmpty())
+        {
+            return (List<Object>) defaultValue;
+        }
+
+        ListIterator<Object> lit = list.listIterator();
+        while (lit.hasNext())
+        {
+            lit.set(interpolate(lit.next()));
+        }
+
+        return list;
+    }
+
+    @Override
+    public String[] getStringArray(String key)
+    {
+        List<Object> list = getList(key);
+
+        // transform property values into strings
+        String[] tokens = new String[list.size()];
+
+        for (int i = 0; i < tokens.length; i++)
+        {
+            tokens[i] = String.valueOf(list.get(i));
+        }
+
+        return tokens;
+    }
+
+    /**
+     * Return the configuration at the specified index.
+     *
+     * @param index The index of the configuration to retrieve
+     * @return the configuration at this index
+     */
+    public Configuration getConfiguration(int index)
+    {
+        return configList.get(index);
+    }
+
+    /**
+     * Returns the "in memory configuration". In this configuration
+     * changes are stored.
+     *
+     * @return the in memory configuration
+     */
+    public Configuration getInMemoryConfiguration()
+    {
+        return inMemoryConfiguration;
+    }
+
+    /**
+     * Returns a copy of this object. This implementation will create a deep
+     * clone, i.e. all configurations contained in this composite will also be
+     * cloned. This only works if all contained configurations support cloning;
+     * otherwise a runtime exception will be thrown. Registered event handlers
+     * won't get cloned.
+     *
+     * @return the copy
+     * @since 1.3
+     */
+    @Override
+    public Object clone()
+    {
+        try
+        {
+            CompositeConfiguration copy = (CompositeConfiguration) super
+                    .clone();
+            copy.clearConfigurationListeners();
+            copy.configList = new LinkedList<Configuration>();
+            copy.inMemoryConfiguration = ConfigurationUtils
+                    .cloneConfiguration(getInMemoryConfiguration());
+            copy.configList.add(copy.inMemoryConfiguration);
+
+            for (Configuration config : configList)
+            {
+                if (config != getInMemoryConfiguration())
+                {
+                    copy.addConfiguration(ConfigurationUtils
+                            .cloneConfiguration(config));
+                }
+            }
+
+            return copy;
+        }
+        catch (CloneNotSupportedException cnex)
+        {
+            // cannot happen
+            throw new ConfigurationRuntimeException(cnex);
+        }
+    }
+
+    /**
+     * Sets a flag whether added values for string properties should be checked
+     * for the list delimiter. This implementation ensures that the in memory
+     * configuration is correctly initialized.
+     *
+     * @param delimiterParsingDisabled the new value of the flag
+     * @since 1.4
+     */
+    @Override
+    public void setDelimiterParsingDisabled(boolean delimiterParsingDisabled)
+    {
+        if (inMemoryConfiguration instanceof AbstractConfiguration)
+        {
+            ((AbstractConfiguration) inMemoryConfiguration)
+                    .setDelimiterParsingDisabled(delimiterParsingDisabled);
+        }
+        super.setDelimiterParsingDisabled(delimiterParsingDisabled);
+    }
+
+    /**
+     * Sets the character that is used as list delimiter. This implementation
+     * ensures that the in memory configuration is correctly initialized.
+     *
+     * @param listDelimiter the new list delimiter character
+     * @since 1.4
+     */
+    @Override
+    public void setListDelimiter(char listDelimiter)
+    {
+        if (inMemoryConfiguration instanceof AbstractConfiguration)
+        {
+            ((AbstractConfiguration) inMemoryConfiguration)
+                    .setListDelimiter(listDelimiter);
+        }
+        super.setListDelimiter(listDelimiter);
+    }
+
+    /**
+     * Returns the configuration source, in which the specified key is defined.
+     * This method will iterate over all existing child configurations and check
+     * whether they contain the specified key. The following constellations are
+     * possible:
+     * <ul>
+     * <li>If exactly one child configuration contains the key, this
+     * configuration is returned as the source configuration. This may be the
+     * <em>in memory configuration</em> (this has to be explicitly checked by
+     * the calling application).</li>
+     * <li>If none of the child configurations contain the key, <b>null</b> is
+     * returned.</li>
+     * <li>If the key is contained in multiple child configurations or if the
+     * key is <b>null</b>, a {@code IllegalArgumentException} is thrown.
+     * In this case the source configuration cannot be determined.</li>
+     * </ul>
+     *
+     * @param key the key to be checked
+     * @return the source configuration of this key
+     * @throws IllegalArgumentException if the source configuration cannot be
+     * determined
+     * @since 1.5
+     */
+    public Configuration getSource(String key)
+    {
+        if (key == null)
+        {
+            throw new IllegalArgumentException("Key must not be null!");
+        }
+
+        Configuration source = null;
+        for (Configuration conf : configList)
+        {
+            if (conf.containsKey(key))
+            {
+                if (source != null)
+                {
+                    throw new IllegalArgumentException("The key " + key
+                            + " is defined by multiple sources!");
+                }
+                source = conf;
+            }
+        }
+
+        return source;
+    }
+
+    /**
+     * Replaces the current in-memory configuration by the given one.
+     *
+     * @param config the new in-memory configuration
+     */
+    private void replaceInMemoryConfiguration(Configuration config)
+    {
+        if (!inMemoryConfigIsChild)
+        {
+            // remove current in-memory configuration
+            configList.remove(inMemoryConfiguration);
+        }
+        inMemoryConfiguration = config;
+    }
+
+    /**
+     * Adds the value of a property to the given list. This method is used by
+     * {@code getList()} for gathering property values from the child
+     * configurations.
+     *
+     * @param dest the list for collecting the data
+     * @param config the configuration to query
+     * @param key the key of the property
+     */
+    private static void appendListProperty(List<Object> dest, Configuration config,
+            String key)
+    {
+        Object value = config.getProperty(key);
+        if (value != null)
+        {
+            if (value instanceof Collection)
+            {
+                Collection<?> col = (Collection<?>) value;
+                dest.addAll(col);
+            }
+            else
+            {
+                dest.add(value);
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/Configuration.java b/src/main/java/org/apache/commons/configuration/Configuration.java
new file mode 100644
index 0000000..c7d8de0
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/Configuration.java
@@ -0,0 +1,598 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * <p>The main Configuration interface.</p>
+ * <p>This interface allows accessing and manipulating a configuration object.
+ * The major part of the methods defined in this interface deals with accessing
+ * properties of various data types. There is a generic {@code getProperty()}
+ * method, which returns the value of the queried property in its raw data
+ * type. Other getter methods try to convert this raw data type into a specific
+ * data type. If this fails, a {@code ConversionException} will be thrown.</p>
+ * <p>For most of the property getter methods an overloaded version exists that
+ * allows to specify a default value, which will be returned if the queried
+ * property cannot be found in the configuration. The behavior of the methods
+ * that do not take a default value in case of a missing property is not defined
+ * by this interface and depends on a concrete implementation. E.g. the
+ * {@link AbstractConfiguration} class, which is the base class
+ * of most configuration implementations provided by this package, per default
+ * returns <b>null</b> if a property is not found, but provides the
+ * {@link AbstractConfiguration#setThrowExceptionOnMissing(boolean)
+ * setThrowExceptionOnMissing()}
+ * method, with which it can be configured to throw a {@code NoSuchElementException}
+ * exception in that case. (Note that getter methods for primitive types in
+ * {@code AbstractConfiguration} always throw an exception for missing
+ * properties because there is no way of overloading the return value.)</p>
+ * <p>With the {@code addProperty()} and {@code setProperty()} methods
+ * new properties can be added to a configuration or the values of properties
+ * can be changed. With {@code clearProperty()} a property can be removed.
+ * Other methods allow to iterate over the contained properties or to create
+ * a subset configuration.</p>
+ *
+ * @author Commons Configuration team
+ * @version $Id: Configuration.java 1534064 2013-10-21 08:44:33Z henning $
+ */
+public interface Configuration
+{
+    /**
+     * Return a decorator Configuration containing every key from the current
+     * Configuration that starts with the specified prefix. The prefix is
+     * removed from the keys in the subset. For example, if the configuration
+     * contains the following properties:
+     *
+     * <pre>
+     *    prefix.number = 1
+     *    prefix.string = Apache
+     *    prefixed.foo = bar
+     *    prefix = Jakarta</pre>
+     *
+     * the Configuration returned by {@code subset("prefix")} will contain
+     * the properties:
+     *
+     * <pre>
+     *    number = 1
+     *    string = Apache
+     *    = Jakarta</pre>
+     *
+     * (The key for the value "Jakarta" is an empty string)
+     * <p>
+     * Since the subset is a decorator and not a modified copy of the initial
+     * Configuration, any change made to the subset is available to the
+     * Configuration, and reciprocally.
+     *
+     * @param prefix The prefix used to select the properties.
+     * @return a subset configuration
+     *
+     * @see SubsetConfiguration
+     */
+    Configuration subset(String prefix);
+
+    /**
+     * Check if the configuration is empty.
+     *
+     * @return {@code true} if the configuration contains no property,
+     *         {@code false} otherwise.
+     */
+    boolean isEmpty();
+
+    /**
+     * Check if the configuration contains the specified key.
+     *
+     * @param key the key whose presence in this configuration is to be tested
+     *
+     * @return {@code true} if the configuration contains a value for this
+     *         key, {@code false} otherwise
+     */
+    boolean containsKey(String key);
+
+    /**
+     * Add a property to the configuration. If it already exists then the value
+     * stated here will be added to the configuration entry. For example, if
+     * the property:
+     *
+     * <pre>resource.loader = file</pre>
+     *
+     * is already present in the configuration and you call
+     *
+     * <pre>addProperty("resource.loader", "classpath")</pre>
+     *
+     * Then you will end up with a List like the following:
+     *
+     * <pre>["file", "classpath"]</pre>
+     *
+     * @param key The key to add the property to.
+     * @param value The value to add.
+     */
+    void addProperty(String key, Object value);
+
+    /**
+     * Set a property, this will replace any previously set values. Set values
+     * is implicitly a call to clearProperty(key), addProperty(key, value).
+     *
+     * @param key The key of the property to change
+     * @param value The new value
+     */
+    void setProperty(String key, Object value);
+
+    /**
+     * Remove a property from the configuration.
+     *
+     * @param key the key to remove along with corresponding value.
+     */
+    void clearProperty(String key);
+
+    /**
+     * Remove all properties from the configuration.
+     */
+    void clear();
+
+    /**
+     * Gets a property from the configuration. This is the most basic get
+     * method for retrieving values of properties. In a typical implementation
+     * of the {@code Configuration} interface the other get methods (that
+     * return specific data types) will internally make use of this method. On
+     * this level variable substitution is not yet performed. The returned
+     * object is an internal representation of the property value for the passed
+     * in key. It is owned by the {@code Configuration} object. So a caller
+     * should not modify this object. It cannot be guaranteed that this object
+     * will stay constant over time (i.e. further update operations on the
+     * configuration may change its internal state).
+     *
+     * @param key property to retrieve
+     * @return the value to which this configuration maps the specified key, or
+     *         null if the configuration contains no mapping for this key.
+     */
+    Object getProperty(String key);
+
+    /**
+     * Get the list of the keys contained in the configuration that match the
+     * specified prefix. For instance, if the configuration contains the
+     * following keys:<br>
+     * {@code db.user, db.pwd, db.url, window.xpos, window.ypos},<br>
+     * an invocation of {@code getKeys("db");}<br>
+     * will return the keys below:<br>
+     * {@code db.user, db.pwd, db.url}.<br>
+     * Note that the prefix itself is included in the result set if there is a
+     * matching key. The exact behavior - how the prefix is actually
+     * interpreted - depends on a concrete implementation.
+     *
+     * @param prefix The prefix to test against.
+     * @return An Iterator of keys that match the prefix.
+     * @see #getKeys()
+     */
+    Iterator<String> getKeys(String prefix);
+
+    /**
+     * Get the list of the keys contained in the configuration. The returned
+     * iterator can be used to obtain all defined keys. Note that the exact
+     * behavior of the iterator's {@code remove()} method is specific to
+     * a concrete implementation. It <em>may</em> remove the corresponding
+     * property from the configuration, but this is not guaranteed. In any case
+     * it is no replacement for calling
+     * {@link #clearProperty(String)} for this property. So it is
+     * highly recommended to avoid using the iterator's {@code remove()}
+     * method.
+     *
+     * @return An Iterator.
+     */
+    Iterator<String> getKeys();
+
+    /**
+     * Get a list of properties associated with the given configuration key.
+     * This method expects the given key to have an arbitrary number of String
+     * values, each of which is of the form {code key=value}. These
+     * strings are split at the equals sign, and the key parts will become
+     * keys of the returned {@code Properties} object, the value parts
+     * become values.
+     *
+     * @param key The configuration key.
+     * @return The associated properties if key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a String/List.
+     *
+     * @throws IllegalArgumentException if one of the tokens is
+     *         malformed (does not contain an equals sign).
+     */
+    Properties getProperties(String key);
+
+    /**
+     * Get a boolean associated with the given configuration key.
+     *
+     * @param key The configuration key.
+     * @return The associated boolean.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Boolean.
+     */
+    boolean getBoolean(String key);
+
+    /**
+     * Get a boolean associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value
+     * is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated boolean.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Boolean.
+     */
+    boolean getBoolean(String key, boolean defaultValue);
+
+    /**
+     * Get a {@link Boolean} associated with the given configuration key.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated boolean if key is found and has valid
+     *         format, default value otherwise.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Boolean.
+     */
+    Boolean getBoolean(String key, Boolean defaultValue);
+
+    /**
+     * Get a byte associated with the given configuration key.
+     *
+     * @param key The configuration key.
+     * @return The associated byte.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Byte.
+     */
+    byte getByte(String key);
+
+    /**
+     * Get a byte associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value
+     * is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated byte.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Byte.
+     */
+    byte getByte(String key, byte defaultValue);
+
+    /**
+     * Get a {@link Byte} associated with the given configuration key.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated byte if key is found and has valid format, default
+     *         value otherwise.
+     *
+     * @throws ConversionException is thrown if the key maps to an object that
+     *         is not a Byte.
+     */
+    Byte getByte(String key, Byte defaultValue);
+
+    /**
+     * Get a double associated with the given configuration key.
+     *
+     * @param key The configuration key.
+     * @return The associated double.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Double.
+     */
+    double getDouble(String key);
+
+    /**
+     * Get a double associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value
+     * is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated double.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Double.
+     */
+    double getDouble(String key, double defaultValue);
+
+    /**
+     * Get a {@link Double} associated with the given configuration key.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated double if key is found and has valid
+     *         format, default value otherwise.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Double.
+     */
+    Double getDouble(String key, Double defaultValue);
+
+    /**
+     * Get a float associated with the given configuration key.
+     *
+     * @param key The configuration key.
+     * @return The associated float.
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Float.
+     */
+    float getFloat(String key);
+
+    /**
+     * Get a float associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value
+     * is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated float.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Float.
+     */
+    float getFloat(String key, float defaultValue);
+
+    /**
+     * Get a {@link Float} associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value
+     * is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated float if key is found and has valid
+     *         format, default value otherwise.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Float.
+     */
+    Float getFloat(String key, Float defaultValue);
+
+    /**
+     * Get a int associated with the given configuration key.
+     *
+     * @param key The configuration key.
+     * @return The associated int.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Integer.
+     */
+    int getInt(String key);
+
+    /**
+     * Get a int associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value
+     * is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated int.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Integer.
+     */
+    int getInt(String key, int defaultValue);
+
+    /**
+     * Get an {@link Integer} associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value
+     * is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated int if key is found and has valid format, default
+     *         value otherwise.
+     *
+     * @throws ConversionException is thrown if the key maps to an object that
+     *         is not a Integer.
+     */
+    Integer getInteger(String key, Integer defaultValue);
+
+    /**
+     * Get a long associated with the given configuration key.
+     *
+     * @param key The configuration key.
+     * @return The associated long.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Long.
+     */
+    long getLong(String key);
+
+    /**
+     * Get a long associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value
+     * is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated long.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Long.
+     */
+    long getLong(String key, long defaultValue);
+
+    /**
+     * Get a {@link Long} associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value
+     * is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated long if key is found and has valid
+     * format, default value otherwise.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Long.
+     */
+    Long getLong(String key, Long defaultValue);
+
+    /**
+     * Get a short associated with the given configuration key.
+     *
+     * @param key The configuration key.
+     * @return The associated short.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Short.
+     */
+    short getShort(String key);
+
+    /**
+     * Get a short associated with the given configuration key.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated short.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Short.
+     */
+    short getShort(String key, short defaultValue);
+
+    /**
+     * Get a {@link Short} associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value
+     * is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated short if key is found and has valid
+     *         format, default value otherwise.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Short.
+     */
+    Short getShort(String key, Short defaultValue);
+
+    /**
+     * Get a {@link BigDecimal} associated with the given configuration key.
+     *
+     * @param key The configuration key.
+     * @return The associated BigDecimal if key is found and has valid format
+     */
+    BigDecimal getBigDecimal(String key);
+
+    /**
+     * Get a {@link BigDecimal} associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value
+     * is returned.
+     *
+     * @param key          The configuration key.
+     * @param defaultValue The default value.
+     *
+     * @return The associated BigDecimal if key is found and has valid
+     *         format, default value otherwise.
+     */
+    BigDecimal getBigDecimal(String key, BigDecimal defaultValue);
+
+    /**
+     * Get a {@link BigInteger} associated with the given configuration key.
+     *
+     * @param key The configuration key.
+     *
+     * @return The associated BigInteger if key is found and has valid format
+     */
+    BigInteger getBigInteger(String key);
+
+    /**
+     * Get a {@link BigInteger} associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value
+     * is returned.
+     *
+     * @param key          The configuration key.
+     * @param defaultValue The default value.
+     *
+     * @return The associated BigInteger if key is found and has valid
+     *         format, default value otherwise.
+     */
+    BigInteger getBigInteger(String key, BigInteger defaultValue);
+
+    /**
+     * Get a string associated with the given configuration key.
+     *
+     * @param key The configuration key.
+     * @return The associated string.
+     *
+     * @throws ConversionException is thrown if the key maps to an object that
+     *         is not a String.
+     */
+    String getString(String key);
+
+    /**
+     * Get a string associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value
+     * is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated string if key is found and has valid
+     *         format, default value otherwise.
+     *
+     * @throws ConversionException is thrown if the key maps to an object that
+     *         is not a String.
+     */
+    String getString(String key, String defaultValue);
+
+    /**
+     * Get an array of strings associated with the given configuration key.
+     * If the key doesn't map to an existing object an empty array is returned
+     *
+     * @param key The configuration key.
+     * @return The associated string array if key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a String/List of Strings.
+     */
+    String[] getStringArray(String key);
+
+    /**
+     * Get a List of strings associated with the given configuration key.
+     * If the key doesn't map to an existing object an empty List is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated List.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a List.
+     */
+    List<Object> getList(String key);
+
+    /**
+     * Get a List of strings associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value
+     * is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated List of strings.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a List.
+     */
+    List<Object> getList(String key, List<?> defaultValue);
+}
diff --git a/src/main/java/org/apache/commons/configuration/ConfigurationBuilder.java b/src/main/java/org/apache/commons/configuration/ConfigurationBuilder.java
new file mode 100644
index 0000000..1a83b43
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/ConfigurationBuilder.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+/**
+ * <p>
+ * Definition of an interface for objects that can create a configuration.
+ * </p>
+ * <p>
+ * This interface defines an abstract way of creating a
+ * {@code Configuration} object. It does not assume any specific way of
+ * how this is done; this is completely in the responsibility of an
+ * implementation class. There is just a single method that returns the
+ * configuration constructed by this builder.
+ * </p>
+ *
+ * @author <a href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a>
+ * @version $Id: ConfigurationBuilder.java 1208781 2011-11-30 21:11:02Z oheger $
+ */
+public interface ConfigurationBuilder
+{
+    /**
+     * Returns the configuration provided by this builder. An implementation has
+     * to perform all necessary steps for creating and initializing a
+     * {@code Configuration} object.
+     *
+     * @return the configuration
+     * @throws ConfigurationException if an error occurs
+     */
+    Configuration getConfiguration() throws ConfigurationException;
+}
diff --git a/src/main/java/org/apache/commons/configuration/ConfigurationComparator.java b/src/main/java/org/apache/commons/configuration/ConfigurationComparator.java
new file mode 100644
index 0000000..299b8e8
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/ConfigurationComparator.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+/**
+ * Comparator for configurations interface.
+ *
+ * @since 1.0
+ *
+ * @author <a href="mailto:herve.quiroz at esil.univ-mrs.fr">Herve Quiroz</a>
+ * @version $Id: ConfigurationComparator.java 1208783 2011-11-30 21:12:45Z oheger $
+ */
+public interface ConfigurationComparator
+{
+    /**
+     * Compare two configuration objects.
+     *
+     * @param a the first configuration
+     * @param b the second configuration
+     * @return true if the two configurations are identical according to
+     *         the implemented rules
+     */
+    boolean compare(Configuration a, Configuration b);
+}
+
diff --git a/src/main/java/org/apache/commons/configuration/ConfigurationConverter.java b/src/main/java/org/apache/commons/configuration/ConfigurationConverter.java
new file mode 100644
index 0000000..8db7b23
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/ConfigurationConverter.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.commons.collections.ExtendedProperties;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * Configuration converter. Helper class to convert between Configuration,
+ * ExtendedProperties and standard Properties.
+ *
+ * @author <a href="mailto:mpoeschl at marmot.at">Martin Poeschl</a>
+ * @version $Id: ConfigurationConverter.java 1208788 2011-11-30 21:15:37Z oheger $
+ */
+public final class ConfigurationConverter
+{
+    /**
+     * Private constructor prevents instances from being created.
+     */
+    private ConfigurationConverter()
+    {
+        // to prevent instanciation...
+    }
+
+    /**
+     * Convert a ExtendedProperties class into a Configuration class.
+     *
+     * @param eprops ExtendedProperties object to convert
+     * @return Configuration created from the ExtendedProperties
+     */
+    public static Configuration getConfiguration(ExtendedProperties eprops)
+    {
+        return new MapConfiguration(eprops);
+    }
+
+    /**
+     * Convert a standard Properties class into a configuration class.
+     *
+     * @param props properties object to convert
+     * @return Configuration configuration created from the Properties
+     */
+    public static Configuration getConfiguration(Properties props)
+    {
+        return new MapConfiguration(props);
+    }
+
+    /**
+     * Convert a Configuration class into a ExtendedProperties class.
+     *
+     * @param config Configuration object to convert
+     * @return ExtendedProperties created from the Configuration
+     */
+    public static ExtendedProperties getExtendedProperties(Configuration config)
+    {
+        ExtendedProperties props = new ExtendedProperties();
+
+        for (Iterator<String> keys = config.getKeys(); keys.hasNext();)
+        {
+            String key = keys.next();
+            Object property = config.getProperty(key);
+
+            // turn lists into vectors
+            if (property instanceof List)
+            {
+                property = new ArrayList<Object>((List<?>) property);
+            }
+
+            props.setProperty(key, property);
+        }
+
+        return props;
+    }
+
+    /**
+     * Convert a Configuration class into a Properties class. List properties
+     * are joined into a string using the delimiter of the configuration if it
+     * extends AbstractConfiguration, and a comma otherwise.
+     *
+     * @param config Configuration object to convert
+     * @return Properties created from the Configuration
+     */
+    public static Properties getProperties(Configuration config)
+    {
+        Properties props = new Properties();
+
+        char delimiter = (config instanceof AbstractConfiguration)
+            ? ((AbstractConfiguration) config).getListDelimiter() : ',';
+
+        for (Iterator<String> keys = config.getKeys(); keys.hasNext();)
+        {
+            String key = keys.next();
+            List<Object> list = config.getList(key);
+
+            // turn the list into a string
+            props.setProperty(key, StringUtils.join(list.iterator(), delimiter));
+        }
+
+        return props;
+    }
+
+    /**
+     * Convert a Configuration class into a Map class.
+     *
+     * @param config Configuration object to convert
+     * @return Map created from the Configuration
+     */
+    public static Map<Object, Object> getMap(Configuration config)
+    {
+        return new ConfigurationMap(config);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/configuration/ConfigurationException.java b/src/main/java/org/apache/commons/configuration/ConfigurationException.java
new file mode 100644
index 0000000..d852b02
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/ConfigurationException.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import org.apache.commons.lang.exception.NestableException;
+
+/**
+ * Any exception that occurs while initializing a Configuration
+ * object.
+ *
+ * @author Eric Pugh
+ * @version $Id: ConfigurationException.java 1208784 2011-11-30 21:13:18Z oheger $
+ */
+public class ConfigurationException extends NestableException
+{
+    /**
+     * The serial version ID.
+     */
+    private static final long serialVersionUID = -1316746661346991484L;
+
+    /**
+     * Constructs a new {@code ConfigurationException} without specified
+     * detail message.
+     */
+    public ConfigurationException()
+    {
+        super();
+    }
+
+    /**
+     * Constructs a new {@code ConfigurationException} with specified
+     * detail message.
+     *
+     * @param message  the error message
+     */
+    public ConfigurationException(String message)
+    {
+        super(message);
+    }
+
+    /**
+     * Constructs a new {@code ConfigurationException} with specified
+     * nested {@code Throwable}.
+     *
+     * @param cause  the exception or error that caused this exception to be thrown
+     */
+    public ConfigurationException(Throwable cause)
+    {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new {@code ConfigurationException} with specified
+     * detail message and nested {@code Throwable}.
+     *
+     * @param message  the error message
+     * @param cause    the exception or error that caused this exception to be thrown
+     */
+    public ConfigurationException(String message, Throwable cause)
+    {
+        super(message, cause);
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/ConfigurationFactory.java b/src/main/java/org/apache/commons/configuration/ConfigurationFactory.java
new file mode 100644
index 0000000..0016810
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/ConfigurationFactory.java
@@ -0,0 +1,903 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.Map;
+
+import org.apache.commons.configuration.plist.PropertyListConfiguration;
+import org.apache.commons.configuration.plist.XMLPropertyListConfiguration;
+import org.apache.commons.digester.AbstractObjectCreationFactory;
+import org.apache.commons.digester.CallMethodRule;
+import org.apache.commons.digester.Digester;
+import org.apache.commons.digester.ObjectCreationFactory;
+import org.apache.commons.digester.Substitutor;
+import org.apache.commons.digester.substitution.MultiVariableExpander;
+import org.apache.commons.digester.substitution.VariableSubstitutor;
+import org.apache.commons.digester.xmlrules.DigesterLoader;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+/**
+ * <p>
+ * Factory class to create a CompositeConfiguration from a .xml file using
+ * Digester.  By default it can handle the Configurations from commons-
+ * configuration.  If you need to add your own, then you can pass in your own
+ * digester rules to use.  It is also namespace aware, by providing a
+ * digesterRuleNamespaceURI.
+ * </p>
+ * <p>
+ * <em>Note:</em> Almost all of the features provided by this class and many
+ * more are also available for the {@link DefaultConfigurationBuilder}
+ * class. {@code DefaultConfigurationBuilder} also has a more robust
+ * merge algorithm for constructing combined configurations. So it is
+ * recommended to use this class instead of {@code ConfigurationFactory}.
+ * </p>
+ *
+ * @author <a href="mailto:epugh at upstate.com">Eric Pugh</a>
+ * @author <a href="mailto:hps at intermeta.de">Henning P. Schmiedehausen</a>
+ * @version $Id: ConfigurationFactory.java 1209685 2011-12-02 20:47:44Z oheger $
+ * @deprecated Use {@link DefaultConfigurationBuilder} instead; this class
+ * provides the same features as ConfigurationFactory plus some more; it can
+ * also process the same configuration definition files.
+ */
+ at Deprecated
+public class ConfigurationFactory
+{
+    /** Constant for the root element in the info file.*/
+    private static final String SEC_ROOT = "configuration/";
+
+    /** Constant for the override section.*/
+    private static final String SEC_OVERRIDE = SEC_ROOT + "override/";
+
+    /** Constant for the additional section.*/
+    private static final String SEC_ADDITIONAL = SEC_ROOT + "additional/";
+
+    /** Constant for the optional attribute.*/
+    private static final String ATTR_OPTIONAL = "optional";
+
+    /** Constant for the fileName attribute.*/
+    private static final String ATTR_FILENAME = "fileName";
+
+    /** Constant for the load method.*/
+    private static final String METH_LOAD = "load";
+
+    /** Constant for the default base path (points to actual directory).*/
+    private static final String DEF_BASE_PATH = ".";
+
+    /** static logger */
+    private static Log log = LogFactory.getLog(ConfigurationFactory.class);
+
+    /** The XML file with the details about the configuration to load */
+    private String configurationFileName;
+
+    /** The URL to the XML file with the details about the configuration to load. */
+    private URL configurationURL;
+
+    /**
+     * The implicit base path for included files. This path is determined by
+     * the configuration to load and used unless no other base path was
+     * explicitly specified.
+     */
+    private String implicitBasePath;
+
+    /** The basePath to prefix file paths for file based property files. */
+    private String basePath;
+
+    /** URL for xml digester rules file */
+    private URL digesterRules;
+
+    /** The digester namespace to parse */
+    private String digesterRuleNamespaceURI;
+
+    /**
+     * Constructor
+     */
+    public ConfigurationFactory()
+    {
+        setBasePath(DEF_BASE_PATH);
+    }
+    /**
+     * Constructor with ConfigurationFile Name passed
+     *
+     * @param configurationFileName The path to the configuration file
+     */
+    public ConfigurationFactory(String configurationFileName)
+    {
+        setConfigurationFileName(configurationFileName);
+    }
+
+    /**
+     * Return the configuration provided by this factory. It loads the
+     * configuration file which is a XML description of the actual
+     * configurations to load. It can contain various different types of
+     * configuration, e.g. Properties, XML and JNDI.
+     *
+     * @return A Configuration object
+     * @throws ConfigurationException A generic exception that we had trouble during the
+     * loading of the configuration data.
+     */
+    public Configuration getConfiguration() throws ConfigurationException
+    {
+        Digester digester;
+        InputStream input = null;
+        ConfigurationBuilder builder = new ConfigurationBuilder();
+        URL url = getConfigurationURL();
+        try
+        {
+            if (url == null)
+            {
+                url = ConfigurationUtils.locate(implicitBasePath, getConfigurationFileName());
+            }
+            input = url.openStream();
+        }
+        catch (Exception e)
+        {
+            log.error("Exception caught opening stream to URL", e);
+            throw new ConfigurationException("Exception caught opening stream to URL", e);
+        }
+
+        if (getDigesterRules() == null)
+        {
+            digester = new Digester();
+            configureNamespace(digester);
+            initDefaultDigesterRules(digester);
+        }
+        else
+        {
+            digester = DigesterLoader.createDigester(getDigesterRules());
+            // This might already be too late. As far as I can see, the namespace
+            // awareness must be configured before the digester rules are loaded.
+            configureNamespace(digester);
+        }
+
+        // Configure digester to always enable the context class loader
+        digester.setUseContextClassLoader(true);
+        // Add a substitutor to resolve system properties
+        enableDigesterSubstitutor(digester);
+        // Put the composite builder object below all of the other objects.
+        digester.push(builder);
+        // Parse the input stream to configure our mappings
+        try
+        {
+            digester.parse(input);
+            input.close();
+        }
+        catch (SAXException saxe)
+        {
+            log.error("SAX Exception caught", saxe);
+            throw new ConfigurationException("SAX Exception caught", saxe);
+        }
+        catch (IOException ioe)
+        {
+            log.error("IO Exception caught", ioe);
+            throw new ConfigurationException("IO Exception caught", ioe);
+        }
+        return builder.getConfiguration();
+    }
+
+    /**
+     * Returns the configurationFile.
+     *
+     * @return The name of the configuration file. Can be null.
+     */
+    public String getConfigurationFileName()
+    {
+        return configurationFileName;
+    }
+
+    /**
+     * Sets the configurationFile.
+     *
+     * @param configurationFileName  The name of the configurationFile to use.
+     */
+    public void setConfigurationFileName(String configurationFileName)
+    {
+        File file = new File(configurationFileName).getAbsoluteFile();
+        this.configurationFileName = file.getName();
+        implicitBasePath = file.getParent();
+    }
+
+    /**
+     * Returns the URL of the configuration file to be loaded.
+     *
+     * @return the URL of the configuration to load
+     */
+    public URL getConfigurationURL()
+    {
+        return configurationURL;
+    }
+
+    /**
+     * Sets the URL of the configuration to load. This configuration can be
+     * either specified by a file name or by a URL.
+     *
+     * @param url the URL of the configuration to load
+     */
+    public void setConfigurationURL(URL url)
+    {
+        configurationURL = url;
+        implicitBasePath = url.toString();
+    }
+
+    /**
+     * Returns the digesterRules.
+     *
+     * @return URL
+     */
+    public URL getDigesterRules()
+    {
+        return digesterRules;
+    }
+
+    /**
+     * Sets the digesterRules.
+     *
+     * @param digesterRules The digesterRules to set
+     */
+    public void setDigesterRules(URL digesterRules)
+    {
+        this.digesterRules = digesterRules;
+    }
+
+    /**
+     * Adds a substitutor to interpolate system properties
+     *
+     * @param digester The digester to which we add the substitutor
+     */
+    protected void enableDigesterSubstitutor(Digester digester)
+    {
+        // This is ugly, but it is safe because the Properties object returned
+        // by System.getProperties() (which is actually a Map<Object, Object>)
+        // contains only String keys.
+        @SuppressWarnings("unchecked")
+        Map<String, Object> systemProperties =
+                (Map<String, Object>) (Object) System.getProperties();
+        MultiVariableExpander expander = new MultiVariableExpander();
+        expander.addSource("$", systemProperties);
+
+        // allow expansion in both xml attributes and element text
+        Substitutor substitutor = new VariableSubstitutor(expander);
+        digester.setSubstitutor(substitutor);
+    }
+
+    /**
+     * Initializes the parsing rules for the default digester
+     *
+     * This allows the Configuration Factory to understand the default types:
+     * Properties, XML and JNDI. Two special sections are introduced:
+     * <code><override></code> and <code><additional></code>.
+     *
+     * @param digester The digester to configure
+     */
+    protected void initDefaultDigesterRules(Digester digester)
+    {
+        initDigesterSectionRules(digester, SEC_ROOT, false);
+        initDigesterSectionRules(digester, SEC_OVERRIDE, false);
+        initDigesterSectionRules(digester, SEC_ADDITIONAL, true);
+    }
+
+    /**
+     * Sets up digester rules for a specified section of the configuration
+     * info file.
+     *
+     * @param digester the current digester instance
+     * @param matchString specifies the section
+     * @param additional a flag if rules for the additional section are to be
+     * added
+     */
+    protected void initDigesterSectionRules(Digester digester, String matchString, boolean additional)
+    {
+        setupDigesterInstance(
+            digester,
+            matchString + "properties",
+            new PropertiesConfigurationFactory(),
+            METH_LOAD,
+            additional);
+
+        setupDigesterInstance(
+            digester,
+            matchString + "plist",
+            new PropertyListConfigurationFactory(),
+            METH_LOAD,
+            additional);
+
+        setupDigesterInstance(
+            digester,
+            matchString + "xml",
+            new FileConfigurationFactory(XMLConfiguration.class),
+            METH_LOAD,
+            additional);
+
+        setupDigesterInstance(
+            digester,
+            matchString + "hierarchicalXml",
+            new FileConfigurationFactory(XMLConfiguration.class),
+            METH_LOAD,
+            additional);
+
+        setupDigesterInstance(
+            digester,
+            matchString + "jndi",
+            new JNDIConfigurationFactory(),
+            null,
+            additional);
+
+        setupDigesterInstance(
+            digester,
+            matchString + "system",
+            new SystemConfigurationFactory(),
+            null,
+            additional);
+    }
+
+    /**
+     * Sets up digester rules for a configuration to be loaded.
+     *
+     * @param digester the current digester
+     * @param matchString the pattern to match with this rule
+     * @param factory an ObjectCreationFactory instance to use for creating new
+     * objects
+     * @param method the name of a method to be called or <b>null</b> for none
+     * @param additional a flag if rules for the additional section are to be
+     * added
+     */
+    protected void setupDigesterInstance(
+            Digester digester,
+            String matchString,
+            ObjectCreationFactory factory,
+            String method,
+            boolean additional)
+    {
+        if (additional)
+        {
+            setupUnionRules(digester, matchString);
+        }
+
+        digester.addFactoryCreate(matchString, factory);
+        digester.addSetProperties(matchString);
+
+        if (method != null)
+        {
+            digester.addRule(matchString, new CallOptionalMethodRule(method));
+        }
+
+        digester.addSetNext(matchString, "addConfiguration", Configuration.class.getName());
+    }
+
+    /**
+     * Sets up rules for configurations in the additional section.
+     *
+     * @param digester the current digester
+     * @param matchString the pattern to match with this rule
+     */
+    protected void setupUnionRules(Digester digester, String matchString)
+    {
+        digester.addObjectCreate(matchString,
+        AdditionalConfigurationData.class);
+        digester.addSetProperties(matchString);
+        digester.addSetNext(matchString, "addAdditionalConfig",
+        AdditionalConfigurationData.class.getName());
+    }
+
+    /**
+     * Returns the digesterRuleNamespaceURI.
+     *
+     * @return A String with the digesterRuleNamespaceURI.
+     */
+    public String getDigesterRuleNamespaceURI()
+    {
+        return digesterRuleNamespaceURI;
+    }
+
+    /**
+     * Sets the digesterRuleNamespaceURI.
+     *
+     * @param digesterRuleNamespaceURI The new digesterRuleNamespaceURI to use
+     */
+    public void setDigesterRuleNamespaceURI(String digesterRuleNamespaceURI)
+    {
+        this.digesterRuleNamespaceURI = digesterRuleNamespaceURI;
+    }
+
+    /**
+     * Configure the current digester to be namespace aware and to have
+     * a Configuration object to which all of the other configurations
+     * should be added
+     *
+     * @param digester The Digester to configure
+     */
+    private void configureNamespace(Digester digester)
+    {
+        if (getDigesterRuleNamespaceURI() != null)
+        {
+            digester.setNamespaceAware(true);
+            digester.setRuleNamespaceURI(getDigesterRuleNamespaceURI());
+        }
+        else
+        {
+            digester.setNamespaceAware(false);
+        }
+        digester.setValidating(false);
+    }
+
+    /**
+     * Returns the Base path from which this Configuration Factory operates.
+     * This is never null. If you set the BasePath to null, then a base path
+     * according to the configuration to load is returned.
+     *
+     * @return The base Path of this configuration factory.
+     */
+    public String getBasePath()
+    {
+        String path = StringUtils.isEmpty(basePath)
+                || DEF_BASE_PATH.equals(basePath) ? implicitBasePath : basePath;
+        return StringUtils.isEmpty(path) ? DEF_BASE_PATH : path;
+    }
+
+    /**
+     * Sets the basePath for all file references from this Configuration Factory.
+     * Normally a base path need not to be set because it is determined by
+     * the location of the configuration file to load. All relative pathes in
+     * this file are resolved relative to this file. Setting a base path makes
+     * sense if such relative pathes should be otherwise resolved, e.g. if
+     * the configuration file is loaded from the class path and all sub
+     * configurations it refers to are stored in a special config directory.
+     *
+     * @param basePath The new basePath to set.
+     */
+    public void setBasePath(String basePath)
+    {
+        this.basePath = basePath;
+    }
+
+    /**
+     * A base class for digester factory classes. This base class maintains
+     * a default class for the objects to be created.
+     * There will be sub classes for specific configuration implementations.
+     */
+    public class DigesterConfigurationFactory extends AbstractObjectCreationFactory
+    {
+        /** Actual class to use. */
+        private Class<?> clazz;
+
+        /**
+         * Creates a new instance of {@code DigesterConfigurationFactory}.
+         *
+         * @param clazz the class which we should instantiate
+         */
+        public DigesterConfigurationFactory(Class<?> clazz)
+        {
+            this.clazz = clazz;
+        }
+
+        /**
+         * Creates an instance of the specified class.
+         *
+         * @param attribs the attributes (ignored)
+         * @return the new object
+         * @throws Exception if object creation fails
+         */
+        @Override
+        public Object createObject(Attributes attribs) throws Exception
+        {
+            return clazz.newInstance();
+        }
+    }
+
+    /**
+     * A tiny inner class that allows the Configuration Factory to
+     * let the digester construct FileConfiguration objects
+     * that already have the correct base Path set.
+     *
+     */
+    public class FileConfigurationFactory extends DigesterConfigurationFactory
+    {
+        /**
+         * C'tor
+         *
+         * @param clazz The class which we should instantiate.
+         */
+        public FileConfigurationFactory(Class<?> clazz)
+        {
+            super(clazz);
+        }
+
+        /**
+         * Gets called by the digester.
+         *
+         * @param attributes the actual attributes
+         * @return the new object
+         * @throws Exception Couldn't instantiate the requested object.
+         */
+        @Override
+        public Object createObject(Attributes attributes) throws Exception
+        {
+            FileConfiguration conf = createConfiguration(attributes);
+            conf.setBasePath(getBasePath());
+            return conf;
+        }
+
+        /**
+         * Creates the object, a {@code FileConfiguration}.
+         *
+         * @param attributes the actual attributes
+         * @return the file configuration
+         * @throws Exception if the object could not be created
+         */
+        protected FileConfiguration createConfiguration(Attributes attributes) throws Exception
+        {
+            return (FileConfiguration) super.createObject(attributes);
+        }
+    }
+
+    /**
+     * A factory that returns an XMLPropertiesConfiguration for .xml files
+     * and a PropertiesConfiguration for the others.
+     *
+     * @since 1.2
+     */
+    public class PropertiesConfigurationFactory extends FileConfigurationFactory
+    {
+        /**
+         * Creates a new instance of {@code PropertiesConfigurationFactory}.
+         */
+        public PropertiesConfigurationFactory()
+        {
+            super(null);
+        }
+
+        /**
+         * Creates the new configuration object. Based on the file name
+         * provided in the attributes either a {@code PropertiesConfiguration}
+         * or a {@code XMLPropertiesConfiguration} object will be
+         * returned.
+         *
+         * @param attributes the attributes
+         * @return the new configuration object
+         * @throws Exception if an error occurs
+         */
+        @Override
+        protected FileConfiguration createConfiguration(Attributes attributes) throws Exception
+        {
+            String filename = attributes.getValue(ATTR_FILENAME);
+
+            if (filename != null && filename.toLowerCase().trim().endsWith(".xml"))
+            {
+                return new XMLPropertiesConfiguration();
+            }
+            else
+            {
+                return new PropertiesConfiguration();
+            }
+        }
+    }
+
+    /**
+     * A factory that returns an XMLPropertyListConfiguration for .xml files
+     * and a PropertyListConfiguration for the others.
+     *
+     * @since 1.2
+     */
+    public class PropertyListConfigurationFactory extends FileConfigurationFactory
+    {
+        /**
+         * Creates a new instance of PropertyListConfigurationFactory</code>.
+         */
+        public PropertyListConfigurationFactory()
+        {
+            super(null);
+        }
+
+        /**
+         * Creates the new configuration object. Based on the file name
+         * provided in the attributes either a {@code XMLPropertyListConfiguration}
+         * or a {@code PropertyListConfiguration} object will be
+         * returned.
+         *
+         * @param attributes the attributes
+         * @return the new configuration object
+         * @throws Exception if an error occurs
+         */
+        @Override
+        protected FileConfiguration createConfiguration(Attributes attributes) throws Exception
+        {
+            String filename = attributes.getValue(ATTR_FILENAME);
+
+            if (filename != null && filename.toLowerCase().trim().endsWith(".xml"))
+            {
+                return new XMLPropertyListConfiguration();
+            }
+            else
+            {
+                return new PropertyListConfiguration();
+            }
+        }
+    }
+
+    /**
+     * A tiny inner class that allows the Configuration Factory to
+     * let the digester construct JNDIConfiguration objects.
+     */
+    private class JNDIConfigurationFactory extends DigesterConfigurationFactory
+    {
+        /**
+         * Creates a new instance of {@code JNDIConfigurationFactory}.
+         */
+        public JNDIConfigurationFactory()
+        {
+            super(JNDIConfiguration.class);
+        }
+    }
+
+    /**
+     * A tiny inner class that allows the Configuration Factory to
+     * let the digester construct SystemConfiguration objects.
+     */
+    private class SystemConfigurationFactory extends DigesterConfigurationFactory
+    {
+        /**
+         * Creates a new instance of {@code SystemConfigurationFactory}.
+         */
+        public SystemConfigurationFactory()
+        {
+            super(SystemConfiguration.class);
+        }
+    }
+
+    /**
+     * A simple data class that holds all information about a configuration
+     * from the <code><additional></code> section.
+     */
+    public static class AdditionalConfigurationData
+    {
+        /** Stores the configuration object.*/
+        private Configuration configuration;
+
+        /** Stores the location of this configuration in the global tree.*/
+        private String at;
+
+        /**
+         * Returns the value of the {@code at} attribute.
+         *
+         * @return the at attribute
+         */
+        public String getAt()
+        {
+            return at;
+        }
+
+        /**
+         * Sets the value of the {@code at} attribute.
+         *
+         * @param string the attribute value
+         */
+        public void setAt(String string)
+        {
+            at = string;
+        }
+
+        /**
+         * Returns the configuration object.
+         *
+         * @return the configuration
+         */
+        public Configuration getConfiguration()
+        {
+            return configuration;
+        }
+
+        /**
+         * Sets the configuration object. Note: Normally this method should be
+         * named {@code setConfiguration()}, but the name
+         * {@code addConfiguration()} is required by some of the digester
+         * rules.
+         *
+         * @param config the configuration to set
+         */
+        public void addConfiguration(Configuration config)
+        {
+            configuration = config;
+        }
+    }
+
+    /**
+     * An internally used helper class for constructing the composite
+     * configuration object.
+     */
+    public static class ConfigurationBuilder
+    {
+        /** Stores the composite configuration.*/
+        private CompositeConfiguration config;
+
+        /** Stores a collection with the configs from the additional section.*/
+        private Collection<AdditionalConfigurationData> additionalConfigs;
+
+        /**
+         * Creates a new instance of {@code ConfigurationBuilder}.
+         */
+        public ConfigurationBuilder()
+        {
+            config = new CompositeConfiguration();
+            additionalConfigs = new LinkedList<AdditionalConfigurationData>();
+        }
+
+        /**
+         * Adds a new configuration to this object. This method is called by
+         * Digester.
+         *
+         * @param conf the configuration to be added
+         */
+        public void addConfiguration(Configuration conf)
+        {
+            config.addConfiguration(conf);
+        }
+
+        /**
+         * Adds information about an additional configuration. This method is
+         * called by Digester.
+         *
+         * @param data the data about the additional configuration
+         */
+        public void addAdditionalConfig(AdditionalConfigurationData data)
+        {
+            additionalConfigs.add(data);
+        }
+
+        /**
+         * Returns the final composite configuration.
+         *
+         * @return the final configuration object
+         */
+        public CompositeConfiguration getConfiguration()
+        {
+            if (!additionalConfigs.isEmpty())
+            {
+                Configuration unionConfig = createAdditionalConfiguration(additionalConfigs);
+                if (unionConfig != null)
+                {
+                    addConfiguration(unionConfig);
+                }
+                additionalConfigs.clear();
+            }
+
+            return config;
+        }
+
+        /**
+         * Creates a configuration object with the union of all properties
+         * defined in the <code><additional></code> section. This
+         * implementation returns a {@code HierarchicalConfiguration}
+         * object.
+         *
+         * @param configs a collection with
+         * {@code AdditionalConfigurationData} objects
+         * @return the union configuration (can be <b>null</b>)
+         */
+        protected Configuration createAdditionalConfiguration(Collection<AdditionalConfigurationData> configs)
+        {
+            HierarchicalConfiguration result = new HierarchicalConfiguration();
+
+            for (AdditionalConfigurationData cdata : configs)
+            {
+                result.addNodes(cdata.getAt(),
+                createRootNode(cdata).getChildren());
+            }
+
+            return result.isEmpty() ? null : result;
+        }
+
+        /**
+         * Creates a configuration root node for the specified configuration.
+         *
+         * @param cdata the configuration data object
+         * @return a root node for this configuration
+         */
+        private HierarchicalConfiguration.Node createRootNode(AdditionalConfigurationData cdata)
+        {
+            if (cdata.getConfiguration() instanceof HierarchicalConfiguration)
+            {
+                // we can directly use this configuration's root node
+                return ((HierarchicalConfiguration) cdata.getConfiguration()).getRoot();
+            }
+            else
+            {
+                // transform configuration to a hierarchical root node
+                HierarchicalConfiguration hc = new HierarchicalConfiguration();
+                ConfigurationUtils.copy(cdata.getConfiguration(), hc);
+                return hc.getRoot();
+            }
+        }
+    }
+
+    /**
+     * A special implementation of Digester's {@code CallMethodRule} that
+     * is internally used for calling a file configuration's {@code load()}
+     * method. This class differs from its ancestor that it catches all occurring
+     * exceptions when the specified method is called. It then checks whether
+     * for the corresponding configuration the optional attribute is set. If
+     * this is the case, the exception will simply be ignored.
+     *
+     * @since 1.4
+     */
+    private static class CallOptionalMethodRule extends CallMethodRule
+    {
+        /** A flag whether the optional attribute is set for this node. */
+        private boolean optional;
+
+        /**
+         * Creates a new instance of {@code CallOptionalMethodRule} and
+         * sets the name of the method to invoke.
+         *
+         * @param methodName the name of the method
+         */
+        public CallOptionalMethodRule(String methodName)
+        {
+            super(methodName);
+        }
+
+        /**
+         * Checks if the optional attribute is set.
+         *
+         * @param attrs the attributes
+         * @throws Exception if an error occurs
+         */
+        @Override
+        public void begin(Attributes attrs) throws Exception
+        {
+            optional = attrs.getValue(ATTR_OPTIONAL) != null
+                    && PropertyConverter.toBoolean(
+                            attrs.getValue(ATTR_OPTIONAL)).booleanValue();
+            super.begin(attrs);
+        }
+
+        /**
+         * Calls the method. If the optional attribute was set, occurring
+         * exceptions will be ignored.
+         *
+         * @throws Exception if an error occurs
+         */
+        @Override
+        public void end() throws Exception
+        {
+            try
+            {
+                super.end();
+            }
+            catch (Exception ex)
+            {
+                if (optional)
+                {
+                    log.warn("Could not create optional configuration!", ex);
+                }
+                else
+                {
+                    throw ex;
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/ConfigurationKey.java b/src/main/java/org/apache/commons/configuration/ConfigurationKey.java
new file mode 100644
index 0000000..76a55f4
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/ConfigurationKey.java
@@ -0,0 +1,687 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.io.Serializable;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * <p>A simple class that supports creation of and iteration on complex
+ * configuration keys.</p>
+ *
+ * <p>For key creation the class works similar to a StringBuilder: There are
+ * several {@code appendXXXX()} methods with which single parts
+ * of a key can be constructed. All these methods return a reference to the
+ * actual object so they can be written in a chain. When using this methods
+ * the exact syntax for keys need not be known.</p>
+ *
+ * <p>This class also defines a specialized iterator for configuration keys.
+ * With such an iterator a key can be tokenized into its single parts. For
+ * each part it can be checked whether it has an associated index.</p>
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: ConfigurationKey.java 1231749 2012-01-15 20:48:56Z oheger $
+ * @deprecated Use {@link org.apache.commons.configuration.tree.DefaultConfigurationKey}
+ * instead. It is associated with a {@code DefaultExpressionEngine} and thus
+ * can produce correct keys even if key separators have been changed.
+ */
+ at Deprecated
+public class ConfigurationKey implements Serializable
+{
+    /** Constant for a property delimiter.*/
+    public static final char PROPERTY_DELIMITER = '.';
+
+    /** Constant for an escaped delimiter. */
+    public static final String ESCAPED_DELIMITER =
+        String.valueOf(PROPERTY_DELIMITER) + String.valueOf(PROPERTY_DELIMITER);
+
+    /** Constant for an attribute start marker.*/
+    private static final String ATTRIBUTE_START = "[@";
+
+    /** Constant for an attribute end marker.*/
+    private static final String ATTRIBUTE_END = "]";
+
+    /** Constant for an index start marker.*/
+    private static final char INDEX_START = '(';
+
+    /** Constant for an index end marker.*/
+    private static final char INDEX_END = ')';
+
+    /** Constant for the initial StringBuilder size.*/
+    private static final int INITIAL_SIZE = 32;
+
+    /**
+     * The serial version ID.
+     */
+    private static final long serialVersionUID = -4299732083605277656L;
+
+    /** Holds a buffer with the so far created key.*/
+    private StringBuilder keyBuffer;
+
+    /**
+     * Creates a new, empty instance of {@code ConfigurationKey}.
+     */
+    public ConfigurationKey()
+    {
+        keyBuffer = new StringBuilder(INITIAL_SIZE);
+    }
+
+    /**
+     * Creates a new instance of {@code ConfigurationKey} and
+     * initializes it with the given key.
+     *
+     * @param key the key as a string
+     */
+    public ConfigurationKey(String key)
+    {
+        keyBuffer = new StringBuilder(key);
+        removeTrailingDelimiter();
+    }
+
+    /**
+     * Appends the name of a property to this key. If necessary, a
+     * property delimiter will be added.
+     *
+     * @param property the name of the property to be added
+     * @return a reference to this object
+     */
+    public ConfigurationKey append(String property)
+    {
+        if (keyBuffer.length() > 0 && !hasDelimiter() && !isAttributeKey(property))
+        {
+            keyBuffer.append(PROPERTY_DELIMITER);
+        }
+
+        keyBuffer.append(property);
+        removeTrailingDelimiter();
+        return this;
+    }
+
+    /**
+     * Appends an index to this configuration key.
+     *
+     * @param index the index to be appended
+     * @return a reference to this object
+     */
+    public ConfigurationKey appendIndex(int index)
+    {
+        keyBuffer.append(INDEX_START).append(index);
+        keyBuffer.append(INDEX_END);
+        return this;
+    }
+
+    /**
+     * Appends an attribute to this configuration key.
+     *
+     * @param attr the name of the attribute to be appended
+     * @return a reference to this object
+     */
+    public ConfigurationKey appendAttribute(String attr)
+    {
+        keyBuffer.append(constructAttributeKey(attr));
+        return this;
+    }
+
+    /**
+     * Checks if this key is an attribute key.
+     *
+     * @return a flag if this key is an attribute key
+     */
+    public boolean isAttributeKey()
+    {
+        return isAttributeKey(keyBuffer.toString());
+    }
+
+    /**
+     * Checks if the passed in key is an attribute key. Such attribute keys
+     * start and end with certain marker strings. In some cases they must be
+     * treated slightly different.
+     *
+     * @param key the key (part) to be checked
+     * @return a flag if this key is an attribute key
+     */
+    public static boolean isAttributeKey(String key)
+    {
+        return key != null
+        && key.startsWith(ATTRIBUTE_START)
+        && key.endsWith(ATTRIBUTE_END);
+    }
+
+    /**
+     * Decorates the given key so that it represents an attribute. Adds
+     * special start and end markers.
+     *
+     * @param key the key to be decorated
+     * @return the decorated attribute key
+     */
+    public static String constructAttributeKey(String key)
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append(ATTRIBUTE_START).append(key).append(ATTRIBUTE_END);
+        return buf.toString();
+    }
+
+    /**
+     * Extracts the name of the attribute from the given attribute key.
+     * This method removes the attribute markers - if any - from the
+     * specified key.
+     *
+     * @param key the attribute key
+     * @return the name of the corresponding attribute
+     */
+    public static String attributeName(String key)
+    {
+        return isAttributeKey(key) ? removeAttributeMarkers(key) : key;
+    }
+
+    /**
+     * Helper method for removing attribute markers from a key.
+     *
+     * @param key the key
+     * @return the key with removed attribute markers
+     */
+    static String removeAttributeMarkers(String key)
+    {
+        return key.substring(ATTRIBUTE_START.length(), key.length() - ATTRIBUTE_END.length());
+    }
+
+    /**
+     * Helper method that checks if the actual buffer ends with a property
+     * delimiter.
+     *
+     * @return a flag if there is a trailing delimiter
+     */
+    private boolean hasDelimiter()
+    {
+        int count = 0;
+        for (int idx = keyBuffer.length() - 1; idx >= 0
+                && keyBuffer.charAt(idx) == PROPERTY_DELIMITER; idx--)
+        {
+            count++;
+        }
+        return count % 2 != 0;
+    }
+
+    /**
+     * Removes a trailing delimiter if there is any.
+     */
+    private void removeTrailingDelimiter()
+    {
+        while (hasDelimiter())
+        {
+            keyBuffer.deleteCharAt(keyBuffer.length() - 1);
+        }
+    }
+
+    /**
+     * Returns a string representation of this object. This is the
+     * configuration key as a plain string.
+     *
+     * @return a string for this object
+     */
+    @Override
+    public String toString()
+    {
+        return keyBuffer.toString();
+    }
+
+    /**
+     * Returns an iterator for iterating over the single components of
+     * this configuration key.
+     *
+     * @return an iterator for this key
+     */
+    public KeyIterator iterator()
+    {
+        return new KeyIterator();
+    }
+
+    /**
+     * Returns the actual length of this configuration key.
+     *
+     * @return the length of this key
+     */
+    public int length()
+    {
+        return keyBuffer.length();
+    }
+
+    /**
+     * Sets the new length of this configuration key. With this method it is
+     * possible to truncate the key, e.g. to return to a state prior calling
+     * some {@code append()} methods. The semantic is the same as
+     * the {@code setLength()} method of {@code StringBuilder}.
+     *
+     * @param len the new length of the key
+     */
+    public void setLength(int len)
+    {
+        keyBuffer.setLength(len);
+    }
+
+    /**
+     * Checks if two {@code ConfigurationKey} objects are equal. The
+     * method can be called with strings or other objects, too.
+     *
+     * @param c the object to compare
+     * @return a flag if both objects are equal
+     */
+    @Override
+    public boolean equals(Object c)
+    {
+        if (c == null)
+        {
+            return false;
+        }
+
+        return keyBuffer.toString().equals(c.toString());
+    }
+
+    /**
+     * Returns the hash code for this object.
+     *
+     * @return the hash code
+     */
+    @Override
+    public int hashCode()
+    {
+        return String.valueOf(keyBuffer).hashCode();
+    }
+
+    /**
+     * Returns a configuration key object that is initialized with the part
+     * of the key that is common to this key and the passed in key.
+     *
+     * @param other the other key
+     * @return a key object with the common key part
+     */
+    public ConfigurationKey commonKey(ConfigurationKey other)
+    {
+        if (other == null)
+        {
+            throw new IllegalArgumentException("Other key must no be null!");
+        }
+
+        ConfigurationKey result = new ConfigurationKey();
+        KeyIterator it1 = iterator();
+        KeyIterator it2 = other.iterator();
+
+        while (it1.hasNext() && it2.hasNext() && partsEqual(it1, it2))
+        {
+            if (it1.isAttribute())
+            {
+                result.appendAttribute(it1.currentKey());
+            }
+            else
+            {
+                result.append(it1.currentKey());
+                if (it1.hasIndex)
+                {
+                    result.appendIndex(it1.getIndex());
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns the "difference key" to a given key. This value
+     * is the part of the passed in key that differs from this key. There is
+     * the following relation:
+     * {@code other = key.commonKey(other) + key.differenceKey(other)}
+     * for an arbitrary configuration key {@code key}.
+     *
+     * @param other the key for which the difference is to be calculated
+     * @return the difference key
+     */
+    public ConfigurationKey differenceKey(ConfigurationKey other)
+    {
+        ConfigurationKey common = commonKey(other);
+        ConfigurationKey result = new ConfigurationKey();
+
+        if (common.length() < other.length())
+        {
+            String k = other.toString().substring(common.length());
+            // skip trailing delimiters
+            int i = 0;
+            while (i < k.length() && k.charAt(i) == PROPERTY_DELIMITER)
+            {
+                i++;
+            }
+
+            if (i < k.length())
+            {
+                result.append(k.substring(i));
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Helper method for comparing two key parts.
+     *
+     * @param it1 the iterator with the first part
+     * @param it2 the iterator with the second part
+     * @return a flag if both parts are equal
+     */
+    private static boolean partsEqual(KeyIterator it1, KeyIterator it2)
+    {
+        return it1.nextKey().equals(it2.nextKey())
+        && it1.getIndex() == it2.getIndex()
+        && it1.isAttribute() == it2.isAttribute();
+    }
+
+    /**
+     * A specialized iterator class for tokenizing a configuration key.
+     * This class implements the normal iterator interface. In addition it
+     * provides some specific methods for configuration keys.
+     */
+    public class KeyIterator implements Iterator<Object>, Cloneable
+    {
+        /** Stores the current key name.*/
+        private String current;
+
+        /** Stores the start index of the actual token.*/
+        private int startIndex;
+
+        /** Stores the end index of the actual token.*/
+        private int endIndex;
+
+        /** Stores the index of the actual property if there is one.*/
+        private int indexValue;
+
+        /** Stores a flag if the actual property has an index.*/
+        private boolean hasIndex;
+
+        /** Stores a flag if the actual property is an attribute.*/
+        private boolean attribute;
+
+        /**
+         * Helper method for determining the next indices.
+         *
+         * @return the next key part
+         */
+        private String findNextIndices()
+        {
+            startIndex = endIndex;
+            // skip empty names
+            while (startIndex < keyBuffer.length()
+                    && keyBuffer.charAt(startIndex) == PROPERTY_DELIMITER)
+            {
+                startIndex++;
+            }
+
+            // Key ends with a delimiter?
+            if (startIndex >= keyBuffer.length())
+            {
+                endIndex = keyBuffer.length();
+                startIndex = endIndex - 1;
+                return keyBuffer.substring(startIndex, endIndex);
+            }
+            else
+            {
+                return nextKeyPart();
+            }
+        }
+
+        /**
+         * Helper method for extracting the next key part. Takes escaping of
+         * delimiter characters into account.
+         *
+         * @return the next key part
+         */
+        private String nextKeyPart()
+        {
+            StringBuilder key = new StringBuilder(INITIAL_SIZE);
+            int idx = startIndex;
+            int endIdx = keyBuffer.toString().indexOf(ATTRIBUTE_START,
+                    startIndex);
+            if (endIdx < 0 || endIdx == startIndex)
+            {
+                endIdx = keyBuffer.length();
+            }
+            boolean found = false;
+
+            while (!found && idx < endIdx)
+            {
+                char c = keyBuffer.charAt(idx);
+                if (c == PROPERTY_DELIMITER)
+                {
+                    // a duplicated delimiter means escaping
+                    if (idx == endIdx - 1
+                            || keyBuffer.charAt(idx + 1) != PROPERTY_DELIMITER)
+                    {
+                        found = true;
+                    }
+                    else
+                    {
+                        idx++;
+                    }
+                }
+                if (!found)
+                {
+                    key.append(c);
+                    idx++;
+                }
+            }
+
+            endIndex = idx;
+            return key.toString();
+        }
+
+        /**
+         * Returns the next key part of this configuration key. This is a short
+         * form of {@code nextKey(false)}.
+         *
+         * @return the next key part
+         */
+        public String nextKey()
+        {
+            return nextKey(false);
+        }
+
+        /**
+         * Returns the next key part of this configuration key. The boolean
+         * parameter indicates wheter a decorated key should be returned. This
+         * affects only attribute keys: if the parameter is <b>false</b>, the
+         * attribute markers are stripped from the key; if it is <b>true</b>,
+         * they remain.
+         *
+         * @param decorated a flag if the decorated key is to be returned
+         * @return the next key part
+         */
+        public String nextKey(boolean decorated)
+        {
+            if (!hasNext())
+            {
+                throw new NoSuchElementException("No more key parts!");
+            }
+
+            hasIndex = false;
+            indexValue = -1;
+            String key = findNextIndices();
+
+            current = key;
+            hasIndex = checkIndex(key);
+            attribute = checkAttribute(current);
+
+            return currentKey(decorated);
+        }
+
+        /**
+         * Helper method for checking if the passed key is an attribute.
+         * If this is the case, the internal fields will be set.
+         *
+         * @param key the key to be checked
+         * @return a flag if the key is an attribute
+         */
+        private boolean checkAttribute(String key)
+        {
+            if (isAttributeKey(key))
+            {
+                current = removeAttributeMarkers(key);
+                return true;
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        /**
+         * Helper method for checking if the passed key contains an index.
+         * If this is the case, internal fields will be set.
+         *
+         * @param key the key to be checked
+         * @return a flag if an index is defined
+         */
+        private boolean checkIndex(String key)
+        {
+            boolean result = false;
+
+            int idx = key.lastIndexOf(INDEX_START);
+            if (idx > 0)
+            {
+                int endidx = key.indexOf(INDEX_END, idx);
+
+                if (endidx > idx + 1)
+                {
+                    indexValue = Integer.parseInt(key.substring(idx + 1, endidx));
+                    current = key.substring(0, idx);
+                    result = true;
+                }
+            }
+
+            return result;
+        }
+
+        /**
+         * Checks if there is a next element.
+         *
+         * @return a flag if there is a next element
+         */
+        public boolean hasNext()
+        {
+            return endIndex < keyBuffer.length();
+        }
+
+        /**
+         * Returns the next object in the iteration.
+         *
+         * @return the next object
+         */
+        public Object next()
+        {
+            return nextKey();
+        }
+
+        /**
+         * Removes the current object in the iteration. This method is not
+         * supported by this iterator type, so an exception is thrown.
+         */
+        public void remove()
+        {
+            throw new UnsupportedOperationException("Remove not supported!");
+        }
+
+        /**
+         * Returns the current key of the iteration (without skipping to the
+         * next element). This is the same key the previous {@code next()}
+         * call had returned. (Short form of {@code currentKey(false)}.
+         *
+         * @return the current key
+         */
+        public String currentKey()
+        {
+            return currentKey(false);
+        }
+
+        /**
+         * Returns the current key of the iteration (without skipping to the
+         * next element). The boolean parameter indicates wheter a decorated
+         * key should be returned. This affects only attribute keys: if the
+         * parameter is <b>false</b>, the attribute markers are stripped from
+         * the key; if it is <b>true</b>, they remain.
+         *
+         * @param decorated a flag if the decorated key is to be returned
+         * @return the current key
+         */
+        public String currentKey(boolean decorated)
+        {
+            return (decorated && isAttribute()) ? constructAttributeKey(current) : current;
+        }
+
+        /**
+         * Returns a flag if the current key is an attribute. This method can
+         * be called after {@code next()}.
+         *
+         * @return a flag if the current key is an attribute
+         */
+        public boolean isAttribute()
+        {
+            return attribute;
+        }
+
+        /**
+         * Returns the index value of the current key. If the current key does
+         * not have an index, return value is -1. This method can be called
+         * after {@code next()}.
+         *
+         * @return the index value of the current key
+         */
+        public int getIndex()
+        {
+            return indexValue;
+        }
+
+        /**
+         * Returns a flag if the current key has an associated index.
+         * This method can be called after {@code next()}.
+         *
+         * @return a flag if the current key has an index
+         */
+        public boolean hasIndex()
+        {
+            return hasIndex;
+        }
+
+        /**
+         * Creates a clone of this object.
+         *
+         * @return a clone of this object
+         */
+        @Override
+        public Object clone()
+        {
+            try
+            {
+                return super.clone();
+            }
+            catch (CloneNotSupportedException cex)
+            {
+                // should not happen
+                return null;
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/ConfigurationMap.java b/src/main/java/org/apache/commons/configuration/ConfigurationMap.java
new file mode 100644
index 0000000..2f93f6c
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/ConfigurationMap.java
@@ -0,0 +1,210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * <p>The {@code ConfigurationMap} wraps a
+ * configuration-collection
+ * {@link org.apache.commons.configuration.Configuration}
+ * instance to provide a {@code Map} interface.</p>
+ *
+ * <p><em>Note:</em> This implementation is incomplete.</p>
+ *
+ * @author <a href="mailto:ricardo.gladwell at btinternet.com">Ricardo Gladwell</a>
+ * @version $Id: ConfigurationMap.java 1301959 2012-03-17 16:43:18Z oheger $
+ * @since 1.0
+ */
+public class ConfigurationMap extends AbstractMap<Object, Object>
+{
+    /**
+     * The {@code Configuration} wrapped by this class.
+     */
+    private final Configuration configuration;
+
+    /**
+     * Creates a new instance of a {@code ConfigurationMap}
+     * that wraps the specified {@code Configuration}
+     * instance.
+     * @param configuration {@code Configuration}
+     * instance.
+     */
+    public ConfigurationMap(Configuration configuration)
+    {
+        this.configuration = configuration;
+    }
+
+    /**
+     * Returns the wrapped {@code Configuration} object.
+     *
+     * @return the wrapped configuration
+     * @since 1.2
+     */
+    public Configuration getConfiguration()
+    {
+        return configuration;
+    }
+
+    /**
+     * Returns a set with the entries contained in this configuration-based map.
+     *
+     * @return a set with the contained entries
+     * @see java.util.Map#entrySet()
+     */
+    @Override
+    public Set<Map.Entry<Object, Object>> entrySet()
+    {
+        return new ConfigurationSet(configuration);
+    }
+
+    /**
+     * Stores the value for the specified key. The value is stored in the
+     * underlying configuration.
+     *
+     * @param key the key (will be converted to a string)
+     * @param value the value
+     * @return the old value of this key or <b>null</b> if it is new
+     * @see java.util.Map#put(java.lang.Object, java.lang.Object)
+     */
+    @Override
+    public Object put(Object key, Object value)
+    {
+        String strKey = String.valueOf(key);
+        Object old = configuration.getProperty(strKey);
+        configuration.setProperty(strKey, value);
+        return old;
+    }
+
+    /**
+     * Returns the value of the specified key. The key is converted to a string
+     * and then passed to the underlying configuration.
+     *
+     * @param key the key
+     * @return the value of this key
+     * @see java.util.Map#get(java.lang.Object)
+     */
+    @Override
+    public Object get(Object key)
+    {
+        return configuration.getProperty(String.valueOf(key));
+    }
+
+    /**
+     * Set of entries in the map.
+     */
+    static class ConfigurationSet extends AbstractSet<Map.Entry<Object, Object>>
+    {
+        /** The configuration mapped to this entry set. */
+        private final Configuration configuration;
+
+        /**
+         * A Map entry in the ConfigurationMap.
+         */
+        private final class Entry implements Map.Entry<Object, Object>
+        {
+            /** The key of the map entry. */
+            private Object key;
+
+            private Entry(Object key)
+            {
+                this.key = key;
+            }
+
+            public Object getKey()
+            {
+                return key;
+            }
+
+            public Object getValue()
+            {
+                return configuration.getProperty((String) key);
+            }
+
+            public Object setValue(Object value)
+            {
+                Object old = getValue();
+                configuration.setProperty((String) key, value);
+                return old;
+            }
+        }
+
+        /**
+         * Iterator over the entries in the ConfigurationMap.
+         */
+        private final class ConfigurationSetIterator implements Iterator<Map.Entry<Object, Object>>
+        {
+            /** An iterator over the keys in the configuration. */
+            private final Iterator<String> keys;
+
+            private ConfigurationSetIterator()
+            {
+                keys = configuration.getKeys();
+            }
+
+            public boolean hasNext()
+            {
+                return keys.hasNext();
+            }
+
+            public Map.Entry<Object, Object> next()
+            {
+                return new Entry(keys.next());
+            }
+
+            public void remove()
+            {
+                keys.remove();
+            }
+        }
+
+        ConfigurationSet(Configuration configuration)
+        {
+            this.configuration = configuration;
+        }
+
+        /**
+         * @see java.util.Collection#size()
+         */
+        @Override
+        public int size()
+        {
+            // Ouch. Now _that_ one is expensive...
+            int count = 0;
+            for (Iterator<String> iterator = configuration.getKeys(); iterator.hasNext();)
+            {
+                iterator.next();
+                count++;
+            }
+            return count;
+        }
+
+        /**
+         * @see java.util.Collection#iterator()
+         */
+        @Override
+        public Iterator<Map.Entry<Object, Object>> iterator()
+        {
+            return new ConfigurationSetIterator();
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/ConfigurationRuntimeException.java b/src/main/java/org/apache/commons/configuration/ConfigurationRuntimeException.java
new file mode 100644
index 0000000..44d9d43
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/ConfigurationRuntimeException.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import org.apache.commons.lang.exception.NestableRuntimeException;
+
+/**
+ * A configuration related runtime exception.
+ *
+ * @since 1.0
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: ConfigurationRuntimeException.java 1208785 2011-11-30 21:13:50Z oheger $
+ */
+public class ConfigurationRuntimeException extends NestableRuntimeException
+{
+    /**
+     * The serial version ID.
+     */
+    private static final long serialVersionUID = -7838702245512140996L;
+
+    /**
+     * Constructs a new {@code ConfigurationRuntimeException} without
+     * specified detail message.
+     */
+    public ConfigurationRuntimeException()
+    {
+        super();
+    }
+
+    /**
+     * Constructs a new {@code ConfigurationRuntimeException} with
+     * specified detail message.
+     *
+     * @param message  the error message
+     */
+    public ConfigurationRuntimeException(String message)
+    {
+        super(message);
+    }
+
+    /**
+     * Constructs a new {@code ConfigurationRuntimeException} with
+     * specified nested {@code Throwable}.
+     *
+     * @param cause  the exception or error that caused this exception to be thrown
+     */
+    public ConfigurationRuntimeException(Throwable cause)
+    {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new {@code ConfigurationRuntimeException} with
+     * specified detail message and nested {@code Throwable}.
+     *
+     * @param message  the error message
+     * @param cause    the exception or error that caused this exception to be thrown
+     */
+    public ConfigurationRuntimeException(String message, Throwable cause)
+    {
+        super(message, cause);
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/ConfigurationUtils.java b/src/main/java/org/apache/commons/configuration/ConfigurationUtils.java
new file mode 100644
index 0000000..2fd9b55
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/ConfigurationUtils.java
@@ -0,0 +1,759 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.io.File;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Iterator;
+
+import org.apache.commons.configuration.event.ConfigurationErrorEvent;
+import org.apache.commons.configuration.event.ConfigurationErrorListener;
+import org.apache.commons.configuration.event.EventSource;
+import org.apache.commons.configuration.reloading.Reloadable;
+import org.apache.commons.configuration.tree.ExpressionEngine;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Miscellaneous utility methods for configurations.
+ *
+ * @see ConfigurationConverter Utility methods to convert configurations.
+ *
+ * @author <a href="mailto:herve.quiroz at esil.univ-mrs.fr">Herve Quiroz</a>
+ * @author Emmanuel Bourg
+ * @version $Id: ConfigurationUtils.java 1208795 2011-11-30 21:18:17Z oheger $
+ */
+public final class ConfigurationUtils
+{
+    /** Constant for the file URL protocol.*/
+    static final String PROTOCOL_FILE = "file";
+
+    /** Constant for the resource path separator.*/
+    static final String RESOURCE_PATH_SEPARATOR = "/";
+
+    /** Constant for the file URL protocol */
+    private static final String FILE_SCHEME = "file:";
+
+    /** Constant for the name of the clone() method.*/
+    private static final String METHOD_CLONE = "clone";
+
+    /** Constant for parsing numbers in hex format. */
+    private static final int HEX = 16;
+
+    /** The logger.*/
+    private static final Log LOG = LogFactory.getLog(ConfigurationUtils.class);
+
+    /**
+     * Private constructor. Prevents instances from being created.
+     */
+    private ConfigurationUtils()
+    {
+        // to prevent instantiation...
+    }
+
+    /**
+     * Dump the configuration key/value mappings to some ouput stream.
+     *
+     * @param configuration the configuration
+     * @param out the output stream to dump the configuration to
+     */
+    public static void dump(Configuration configuration, PrintStream out)
+    {
+        dump(configuration, new PrintWriter(out));
+    }
+
+    /**
+     * Dump the configuration key/value mappings to some writer.
+     *
+     * @param configuration the configuration
+     * @param out the writer to dump the configuration to
+     */
+    public static void dump(Configuration configuration, PrintWriter out)
+    {
+        for (Iterator<String> keys = configuration.getKeys(); keys.hasNext();)
+        {
+            String key = keys.next();
+            Object value = configuration.getProperty(key);
+            out.print(key);
+            out.print("=");
+            out.print(value);
+
+            if (keys.hasNext())
+            {
+                out.println();
+            }
+        }
+
+        out.flush();
+    }
+
+    /**
+     * Get a string representation of the key/value mappings of a
+     * configuration.
+     *
+     * @param configuration the configuration
+     * @return a string representation of the configuration
+     */
+    public static String toString(Configuration configuration)
+    {
+        StringWriter writer = new StringWriter();
+        dump(configuration, new PrintWriter(writer));
+        return writer.toString();
+    }
+
+    /**
+     * <p>Copy all properties from the source configuration to the target
+     * configuration. Properties in the target configuration are replaced with
+     * the properties with the same key in the source configuration.</p>
+     * <p><em>Note:</em> This method is not able to handle some specifics of
+     * configurations derived from {@code AbstractConfiguration} (e.g.
+     * list delimiters). For a full support of all of these features the
+     * {@code copy()} method of {@code AbstractConfiguration} should
+     * be used. In a future release this method might become deprecated.</p>
+     *
+     * @param source the source configuration
+     * @param target the target configuration
+     * @since 1.1
+     */
+    public static void copy(Configuration source, Configuration target)
+    {
+        for (Iterator<String> keys = source.getKeys(); keys.hasNext();)
+        {
+            String key = keys.next();
+            target.setProperty(key, source.getProperty(key));
+        }
+    }
+
+    /**
+     * <p>Append all properties from the source configuration to the target
+     * configuration. Properties in the source configuration are appended to
+     * the properties with the same key in the target configuration.</p>
+     * <p><em>Note:</em> This method is not able to handle some specifics of
+     * configurations derived from {@code AbstractConfiguration} (e.g.
+     * list delimiters). For a full support of all of these features the
+     * {@code copy()} method of {@code AbstractConfiguration} should
+     * be used. In a future release this method might become deprecated.</p>
+     *
+     * @param source the source configuration
+     * @param target the target configuration
+     * @since 1.1
+     */
+    public static void append(Configuration source, Configuration target)
+    {
+        for (Iterator<String> keys = source.getKeys(); keys.hasNext();)
+        {
+            String key = keys.next();
+            target.addProperty(key, source.getProperty(key));
+        }
+    }
+
+    /**
+     * Converts the passed in configuration to a hierarchical one. If the
+     * configuration is already hierarchical, it is directly returned. Otherwise
+     * all properties are copied into a new hierarchical configuration.
+     *
+     * @param conf the configuration to convert
+     * @return the new hierarchical configuration (the result is <b>null</b> if
+     * and only if the passed in configuration is <b>null</b>)
+     * @since 1.3
+     */
+    public static HierarchicalConfiguration convertToHierarchical(
+            Configuration conf)
+    {
+        return convertToHierarchical(conf, null);
+    }
+
+    /**
+     * Converts the passed in {@code Configuration} object to a
+     * hierarchical one using the specified {@code ExpressionEngine}. This
+     * conversion works by adding the keys found in the configuration to a newly
+     * created hierarchical configuration. When adding new keys to a
+     * hierarchical configuration the keys are interpreted by its
+     * {@code ExpressionEngine}. If they contain special characters (e.g.
+     * brackets) that are treated in a special way by the default expression
+     * engine, it may be necessary using a specific engine that can deal with
+     * such characters. Otherwise <b>null</b> can be passed in for the
+     * {@code ExpressionEngine}; then the default expression engine is
+     * used. If the passed in configuration is already hierarchical, it is
+     * directly returned. (However, the {@code ExpressionEngine} is set if
+     * it is not <b>null</b>.) Otherwise all properties are copied into a new
+     * hierarchical configuration.
+     *
+     * @param conf the configuration to convert
+     * @param engine the {@code ExpressionEngine} for the hierarchical
+     *        configuration or <b>null</b> for the default
+     * @return the new hierarchical configuration (the result is <b>null</b> if
+     *         and only if the passed in configuration is <b>null</b>)
+     * @since 1.6
+     */
+    public static HierarchicalConfiguration convertToHierarchical(
+            Configuration conf, ExpressionEngine engine)
+    {
+        if (conf == null)
+        {
+            return null;
+        }
+
+        if (conf instanceof HierarchicalConfiguration)
+        {
+            HierarchicalConfiguration hc;
+            if (conf instanceof Reloadable)
+            {
+                Object lock = ((Reloadable) conf).getReloadLock();
+                synchronized (lock)
+                {
+                    hc = new HierarchicalConfiguration((HierarchicalConfiguration) conf);
+                }
+            }
+            else
+            {
+                hc = (HierarchicalConfiguration) conf;
+            }
+            if (engine != null)
+            {
+                hc.setExpressionEngine(engine);
+            }
+
+            return hc;
+        }
+        else
+        {
+            HierarchicalConfiguration hc = new HierarchicalConfiguration();
+            if (engine != null)
+            {
+                hc.setExpressionEngine(engine);
+            }
+
+            // Workaround for problem with copy()
+            boolean delimiterParsingStatus = hc.isDelimiterParsingDisabled();
+            hc.setDelimiterParsingDisabled(true);
+            hc.append(conf);
+            hc.setDelimiterParsingDisabled(delimiterParsingStatus);
+            return hc;
+        }
+    }
+
+    /**
+     * Clones the given configuration object if this is possible. If the passed
+     * in configuration object implements the {@code Cloneable}
+     * interface, its {@code clone()} method will be invoked. Otherwise
+     * an exception will be thrown.
+     *
+     * @param config the configuration object to be cloned (can be <b>null</b>)
+     * @return the cloned configuration (<b>null</b> if the argument was
+     * <b>null</b>, too)
+     * @throws ConfigurationRuntimeException if cloning is not supported for
+     * this object
+     * @since 1.3
+     */
+    public static Configuration cloneConfiguration(Configuration config)
+            throws ConfigurationRuntimeException
+    {
+        if (config == null)
+        {
+            return null;
+        }
+        else
+        {
+            try
+            {
+                return (Configuration) clone(config);
+            }
+            catch (CloneNotSupportedException cnex)
+            {
+                throw new ConfigurationRuntimeException(cnex);
+            }
+        }
+    }
+
+    /**
+     * An internally used helper method for cloning objects. This implementation
+     * is not very sophisticated nor efficient. Maybe it can be replaced by an
+     * implementation from Commons Lang later. The method checks whether the
+     * passed in object implements the {@code Cloneable} interface. If
+     * this is the case, the {@code clone()} method is invoked by
+     * reflection. Errors that occur during the cloning process are re-thrown as
+     * runtime exceptions.
+     *
+     * @param obj the object to be cloned
+     * @return the cloned object
+     * @throws CloneNotSupportedException if the object cannot be cloned
+     */
+    static Object clone(Object obj) throws CloneNotSupportedException
+    {
+        if (obj instanceof Cloneable)
+        {
+            try
+            {
+                Method m = obj.getClass().getMethod(METHOD_CLONE);
+                return m.invoke(obj);
+            }
+            catch (NoSuchMethodException nmex)
+            {
+                throw new CloneNotSupportedException(
+                        "No clone() method found for class"
+                                + obj.getClass().getName());
+            }
+            catch (IllegalAccessException iaex)
+            {
+                throw new ConfigurationRuntimeException(iaex);
+            }
+            catch (InvocationTargetException itex)
+            {
+                throw new ConfigurationRuntimeException(itex);
+            }
+        }
+        else
+        {
+            throw new CloneNotSupportedException(obj.getClass().getName()
+                    + " does not implement Cloneable");
+        }
+    }
+
+    /**
+     * Constructs a URL from a base path and a file name. The file name can
+     * be absolute, relative or a full URL. If necessary the base path URL is
+     * applied.
+     *
+     * @param basePath the base path URL (can be <b>null</b>)
+     * @param file the file name
+     * @return the resulting URL
+     * @throws MalformedURLException if URLs are invalid
+     */
+    public static URL getURL(String basePath, String file) throws MalformedURLException
+    {
+        return FileSystem.getDefaultFileSystem().getURL(basePath, file);
+    }
+
+    /**
+     * Helper method for constructing a file object from a base path and a
+     * file name. This method is called if the base path passed to
+     * {@code getURL()} does not seem to be a valid URL.
+     *
+     * @param basePath the base path
+     * @param fileName the file name
+     * @return the resulting file
+     */
+    static File constructFile(String basePath, String fileName)
+    {
+        File file;
+
+        File absolute = null;
+        if (fileName != null)
+        {
+            absolute = new File(fileName);
+        }
+
+        if (StringUtils.isEmpty(basePath) || (absolute != null && absolute.isAbsolute()))
+        {
+            file = new File(fileName);
+        }
+        else
+        {
+            StringBuilder fName = new StringBuilder();
+            fName.append(basePath);
+
+            // My best friend. Paranoia.
+            if (!basePath.endsWith(File.separator))
+            {
+                fName.append(File.separator);
+            }
+
+            //
+            // We have a relative path, and we have
+            // two possible forms here. If we have the
+            // "./" form then just strip that off first
+            // before continuing.
+            //
+            if (fileName.startsWith("." + File.separator))
+            {
+                fName.append(fileName.substring(2));
+            }
+            else
+            {
+                fName.append(fileName);
+            }
+
+            file = new File(fName.toString());
+        }
+
+        return file;
+    }
+
+    /**
+     * Return the location of the specified resource by searching the user home
+     * directory, the current classpath and the system classpath.
+     *
+     * @param name the name of the resource
+     *
+     * @return the location of the resource
+     */
+    public static URL locate(String name)
+    {
+        return locate(null, name);
+    }
+
+    /**
+     * Return the location of the specified resource by searching the user home
+     * directory, the current classpath and the system classpath.
+     *
+     * @param base the base path of the resource
+     * @param name the name of the resource
+     *
+     * @return the location of the resource
+     */
+    public static URL locate(String base, String name)
+    {
+        return locate(FileSystem.getDefaultFileSystem(), base, name);
+    }
+
+    /**
+     * Return the location of the specified resource by searching the user home
+     * directory, the current classpath and the system classpath.
+     *
+     * @param fileSystem the FileSystem to use.
+     * @param base the base path of the resource
+     * @param name the name of the resource
+     *
+     * @return the location of the resource
+     */
+    public static URL locate(FileSystem fileSystem, String base, String name)
+    {
+        if (LOG.isDebugEnabled())
+        {
+            StringBuilder buf = new StringBuilder();
+            buf.append("ConfigurationUtils.locate(): base is ").append(base);
+            buf.append(", name is ").append(name);
+            LOG.debug(buf.toString());
+        }
+
+        if (name == null)
+        {
+            // undefined, always return null
+            return null;
+        }
+
+        // attempt to create an URL directly
+
+        URL url = fileSystem.locateFromURL(base, name);
+
+        // attempt to load from an absolute path
+        if (url == null)
+        {
+            File file = new File(name);
+            if (file.isAbsolute() && file.exists()) // already absolute?
+            {
+                try
+                {
+                    url = toURL(file);
+                    LOG.debug("Loading configuration from the absolute path " + name);
+                }
+                catch (MalformedURLException e)
+                {
+                    LOG.warn("Could not obtain URL from file", e);
+                }
+            }
+        }
+
+        // attempt to load from the base directory
+        if (url == null)
+        {
+            try
+            {
+                File file = constructFile(base, name);
+                if (file != null && file.exists())
+                {
+                    url = toURL(file);
+                }
+
+                if (url != null)
+                {
+                    LOG.debug("Loading configuration from the path " + file);
+                }
+            }
+            catch (MalformedURLException e)
+            {
+                LOG.warn("Could not obtain URL from file", e);
+            }
+        }
+
+        // attempt to load from the user home directory
+        if (url == null)
+        {
+            try
+            {
+                File file = constructFile(System.getProperty("user.home"), name);
+                if (file != null && file.exists())
+                {
+                    url = toURL(file);
+                }
+
+                if (url != null)
+                {
+                    LOG.debug("Loading configuration from the home path " + file);
+                }
+
+            }
+            catch (MalformedURLException e)
+            {
+                LOG.warn("Could not obtain URL from file", e);
+            }
+        }
+
+        // attempt to load from classpath
+        if (url == null)
+        {
+            url = locateFromClasspath(name);
+        }
+        return url;
+    }
+
+    /**
+     * Tries to find a resource with the given name in the classpath.
+     * @param resourceName the name of the resource
+     * @return the URL to the found resource or <b>null</b> if the resource
+     * cannot be found
+     */
+    static URL locateFromClasspath(String resourceName)
+    {
+        URL url = null;
+        // attempt to load from the context classpath
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+        if (loader != null)
+        {
+            url = loader.getResource(resourceName);
+
+            if (url != null)
+            {
+                LOG.debug("Loading configuration from the context classpath (" + resourceName + ")");
+            }
+        }
+
+        // attempt to load from the system classpath
+        if (url == null)
+        {
+            url = ClassLoader.getSystemResource(resourceName);
+
+            if (url != null)
+            {
+                LOG.debug("Loading configuration from the system classpath (" + resourceName + ")");
+            }
+        }
+        return url;
+    }
+
+    /**
+     * Return the path without the file name, for example http://xyz.net/foo/bar.xml
+     * results in http://xyz.net/foo/
+     *
+     * @param url the URL from which to extract the path
+     * @return the path component of the passed in URL
+     */
+    static String getBasePath(URL url)
+    {
+        if (url == null)
+        {
+            return null;
+        }
+
+        String s = url.toString();
+        if (s.startsWith(FILE_SCHEME) && !s.startsWith("file://"))
+        {
+            s = "file://" + s.substring(FILE_SCHEME.length());
+        }
+
+        if (s.endsWith("/") || StringUtils.isEmpty(url.getPath()))
+        {
+            return s;
+        }
+        else
+        {
+            return s.substring(0, s.lastIndexOf("/") + 1);
+        }
+    }
+
+    /**
+     * Extract the file name from the specified URL.
+     *
+     * @param url the URL from which to extract the file name
+     * @return the extracted file name
+     */
+    static String getFileName(URL url)
+    {
+        if (url == null)
+        {
+            return null;
+        }
+
+        String path = url.getPath();
+
+        if (path.endsWith("/") || StringUtils.isEmpty(path))
+        {
+            return null;
+        }
+        else
+        {
+            return path.substring(path.lastIndexOf("/") + 1);
+        }
+    }
+
+    /**
+     * Tries to convert the specified base path and file name into a file object.
+     * This method is called e.g. by the save() methods of file based
+     * configurations. The parameter strings can be relative files, absolute
+     * files and URLs as well. This implementation checks first whether the passed in
+     * file name is absolute. If this is the case, it is returned. Otherwise
+     * further checks are performed whether the base path and file name can be
+     * combined to a valid URL or a valid file name. <em>Note:</em> The test
+     * if the passed in file name is absolute is performed using
+     * {@code java.io.File.isAbsolute()}. If the file name starts with a
+     * slash, this method will return <b>true</b> on Unix, but <b>false</b> on
+     * Windows. So to ensure correct behavior for relative file names on all
+     * platforms you should never let relative paths start with a slash. E.g.
+     * in a configuration definition file do not use something like that:
+     * <pre>
+     * <properties fileName="/subdir/my.properties"/>
+     * </pre>
+     * Under Windows this path would be resolved relative to the configuration
+     * definition file. Under Unix this would be treated as an absolute path
+     * name.
+     *
+     * @param basePath the base path
+     * @param fileName the file name
+     * @return the file object (<b>null</b> if no file can be obtained)
+     */
+    public static File getFile(String basePath, String fileName)
+    {
+        // Check if the file name is absolute
+        File f = new File(fileName);
+        if (f.isAbsolute())
+        {
+            return f;
+        }
+
+        // Check if URLs are involved
+        URL url;
+        try
+        {
+            url = new URL(new URL(basePath), fileName);
+        }
+        catch (MalformedURLException mex1)
+        {
+            try
+            {
+                url = new URL(fileName);
+            }
+            catch (MalformedURLException mex2)
+            {
+                url = null;
+            }
+        }
+
+        if (url != null)
+        {
+            return fileFromURL(url);
+        }
+
+        return constructFile(basePath, fileName);
+    }
+
+    /**
+     * Tries to convert the specified URL to a file object. If this fails,
+     * <b>null</b> is returned. Note: This code has been copied from the
+     * {@code FileUtils} class from <em>Commons IO</em>.
+     *
+     * @param url the URL
+     * @return the resulting file object
+     */
+    public static File fileFromURL(URL url)
+    {
+        if (url == null || !url.getProtocol().equals(PROTOCOL_FILE))
+        {
+            return null;
+        }
+        else
+        {
+            String filename = url.getFile().replace('/', File.separatorChar);
+            int pos = 0;
+            while ((pos = filename.indexOf('%', pos)) >= 0)
+            {
+                if (pos + 2 < filename.length())
+                {
+                    String hexStr = filename.substring(pos + 1, pos + 3);
+                    char ch = (char) Integer.parseInt(hexStr, HEX);
+                    filename = filename.substring(0, pos) + ch
+                            + filename.substring(pos + 3);
+                }
+            }
+            return new File(filename);
+        }
+    }
+
+    /**
+     * Convert the specified file into an URL. This method is equivalent
+     * to file.toURI().toURL(). It was used to work around a bug in the JDK
+     * preventing the transformation of a file into an URL if the file name
+     * contains a '#' character. See the issue CONFIGURATION-300 for
+     * more details. Now that we switched to JDK 1.4 we can directly use
+     * file.toURI().toURL().
+     *
+     * @param file the file to be converted into an URL
+     */
+    static URL toURL(File file) throws MalformedURLException
+    {
+        return file.toURI().toURL();
+    }
+
+    /**
+     * Enables runtime exceptions for the specified configuration object. This
+     * method can be used for configuration implementations that may face errors
+     * on normal property access, e.g. {@code DatabaseConfiguration} or
+     * {@code JNDIConfiguration}. Per default such errors are simply
+     * logged and then ignored. This implementation will register a special
+     * {@link ConfigurationErrorListener} that throws a runtime
+     * exception (namely a {@code ConfigurationRuntimeException}) on
+     * each received error event.
+     *
+     * @param src the configuration, for which runtime exceptions are to be
+     * enabled; this configuration must be derived from
+     * {@link EventSource}
+     */
+    public static void enableRuntimeExceptions(Configuration src)
+    {
+        if (!(src instanceof EventSource))
+        {
+            throw new IllegalArgumentException(
+                    "Configuration must be derived from EventSource!");
+        }
+        ((EventSource) src).addErrorListener(new ConfigurationErrorListener()
+        {
+            public void configurationError(ConfigurationErrorEvent event)
+            {
+                // Throw a runtime exception
+                throw new ConfigurationRuntimeException(event.getCause());
+            }
+        });
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/ConfigurationXMLReader.java b/src/main/java/org/apache/commons/configuration/ConfigurationXMLReader.java
new file mode 100644
index 0000000..58190f4
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/ConfigurationXMLReader.java
@@ -0,0 +1,364 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.io.IOException;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * <p>A base class for "faked" {@code XMLReader} classes
+ * that transform a configuration object in a set of SAX parsing events.</p>
+ * <p>This class provides dummy implementations for most of the methods
+ * defined in the {@code XMLReader} interface that are not used for this
+ * special purpose. There will be concrete sub classes that process specific
+ * configuration classes.</p>
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: ConfigurationXMLReader.java 1208805 2011-11-30 21:33:33Z oheger $
+ */
+public abstract class ConfigurationXMLReader implements XMLReader
+{
+    /** Constant for the namespace URI.*/
+    protected static final String NS_URI = "";
+
+    /** Constant for the default name of the root element.*/
+    private static final String DEFAULT_ROOT_NAME = "config";
+
+    /** An empty attributes object.*/
+    private static final Attributes EMPTY_ATTRS = new AttributesImpl();
+
+    /** Stores the content handler.*/
+    private ContentHandler contentHandler;
+
+    /** Stores an exception that occurred during parsing.*/
+    private SAXException exception;
+
+    /** Stores the name for the root element.*/
+    private String rootName;
+
+    /**
+     * Creates a new instance of {@code ConfigurationXMLReader}.
+     */
+    protected ConfigurationXMLReader()
+    {
+        super();
+        setRootName(DEFAULT_ROOT_NAME);
+    }
+
+    /**
+     * Parses the acutal configuration object. The passed system ID will be
+     * ignored.
+     *
+     * @param systemId the system ID (ignored)
+     * @throws IOException if no configuration was specified
+     * @throws SAXException if an error occurs during parsing
+     */
+    public void parse(String systemId) throws IOException, SAXException
+    {
+        parseConfiguration();
+    }
+
+    /**
+     * Parses the actual configuration object. The passed input source will be
+     * ignored.
+     *
+     * @param input the input source (ignored)
+     * @throws IOException if no configuration was specified
+     * @throws SAXException if an error occurs during parsing
+     */
+    public void parse(InputSource input) throws IOException, SAXException
+    {
+        parseConfiguration();
+    }
+
+    /**
+     * Dummy implementation of the interface method.
+     *
+     * @param name the name of the feature
+     * @return always <b>false</b> (no features are supported)
+     */
+    public boolean getFeature(String name)
+    {
+        return false;
+    }
+
+    /**
+     * Dummy implementation of the interface method.
+     *
+     * @param name the name of the feature to be set
+     * @param value the value of the feature
+     */
+    public void setFeature(String name, boolean value)
+    {
+    }
+
+    /**
+     * Returns the actually set content handler.
+     *
+     * @return the content handler
+     */
+    public ContentHandler getContentHandler()
+    {
+        return contentHandler;
+    }
+
+    /**
+     * Sets the content handler. The object specified here will receive SAX
+     * events during parsing.
+     *
+     * @param handler the content handler
+     */
+    public void setContentHandler(ContentHandler handler)
+    {
+        contentHandler = handler;
+    }
+
+    /**
+     * Returns the DTD handler. This class does not support DTD handlers,
+     * so this method always returns <b>null</b>.
+     *
+     * @return the DTD handler
+     */
+    public DTDHandler getDTDHandler()
+    {
+        return null;
+    }
+
+    /**
+     * Sets the DTD handler. The passed value is ignored.
+     *
+     * @param handler the handler to be set
+     */
+    public void setDTDHandler(DTDHandler handler)
+    {
+    }
+
+    /**
+     * Returns the entity resolver. This class does not support an entity
+     * resolver, so this method always returns <b>null</b>.
+     *
+     * @return the entity resolver
+     */
+    public EntityResolver getEntityResolver()
+    {
+        return null;
+    }
+
+    /**
+     * Sets the entity resolver. The passed value is ignored.
+     *
+     * @param resolver the entity resolver
+     */
+    public void setEntityResolver(EntityResolver resolver)
+    {
+    }
+
+    /**
+     * Returns the error handler. This class does not support an error handler,
+     * so this method always returns <b>null</b>.
+     *
+     * @return the error handler
+     */
+    public ErrorHandler getErrorHandler()
+    {
+        return null;
+    }
+
+    /**
+     * Sets the error handler. The passed value is ignored.
+     *
+     * @param handler the error handler
+     */
+    public void setErrorHandler(ErrorHandler handler)
+    {
+    }
+
+    /**
+     * Dummy implementation of the interface method. No properties are
+     * supported, so this method always returns <b>null</b>.
+     *
+     * @param name the name of the requested property
+     * @return the property value
+     */
+    public Object getProperty(String name)
+    {
+        return null;
+    }
+
+    /**
+     * Dummy implementation of the interface method. No properties are
+     * supported, so a call of this method just has no effect.
+     *
+     * @param name the property name
+     * @param value the property value
+     */
+    public void setProperty(String name, Object value)
+    {
+    }
+
+    /**
+     * Returns the name to be used for the root element.
+     *
+     * @return the name for the root element
+     */
+    public String getRootName()
+    {
+        return rootName;
+    }
+
+    /**
+     * Sets the name for the root element.
+     *
+     * @param string the name for the root element.
+     */
+    public void setRootName(String string)
+    {
+        rootName = string;
+    }
+
+    /**
+     * Fires a SAX element start event.
+     *
+     * @param name the name of the actual element
+     * @param attribs the attributes of this element (can be <b>null</b>)
+     */
+    protected void fireElementStart(String name, Attributes attribs)
+    {
+        if (getException() == null)
+        {
+            try
+            {
+                Attributes at = (attribs == null) ? EMPTY_ATTRS : attribs;
+                getContentHandler().startElement(NS_URI, name, name, at);
+            }
+            catch (SAXException ex)
+            {
+                exception = ex;
+            }
+        }
+    }
+
+    /**
+     * Fires a SAX element end event.
+     *
+     * @param name the name of the affected element
+     */
+    protected void fireElementEnd(String name)
+    {
+        if (getException() == null)
+        {
+            try
+            {
+                getContentHandler().endElement(NS_URI, name, name);
+            }
+            catch (SAXException ex)
+            {
+                exception = ex;
+            }
+        }
+    }
+
+    /**
+     * Fires a SAX characters event.
+     *
+     * @param text the text
+     */
+    protected void fireCharacters(String text)
+    {
+        if (getException() == null)
+        {
+            try
+            {
+                char[] ch = text.toCharArray();
+                getContentHandler().characters(ch, 0, ch.length);
+            }
+            catch (SAXException ex)
+            {
+                exception = ex;
+            }
+        }
+    }
+
+    /**
+     * Returns a reference to an exception that occurred during parsing.
+     *
+     * @return a SAXExcpetion or <b>null</b> if none occurred
+     */
+    public SAXException getException()
+    {
+        return exception;
+    }
+
+    /**
+     * Parses the configuration object and generates SAX events. This is the
+     * main processing method.
+     *
+     * @throws IOException if no configuration has been specified
+     * @throws SAXException if an error occurs during parsing
+     */
+    protected void parseConfiguration() throws IOException, SAXException
+    {
+        if (getParsedConfiguration() == null)
+        {
+            throw new IOException("No configuration specified!");
+        }
+
+        if (getContentHandler() != null)
+        {
+            exception = null;
+            getContentHandler().startDocument();
+            processKeys();
+            if (getException() != null)
+            {
+                throw getException();
+            }
+            getContentHandler().endDocument();
+        }
+    }
+
+    /**
+     * Returns a reference to the configuration that is parsed by this object.
+     *
+     * @return the parsed configuration
+     */
+    public abstract Configuration getParsedConfiguration();
+
+    /**
+     * Processes all keys stored in the actual configuration. This method is
+     * called by {@code parseConfiguration()} to start the main parsing
+     * process. {@code parseConfiguration()} calls the content handler's
+     * {@code startDocument()} and {@code endElement()} methods
+     * and cares for exception handling. The remaining actions are left to this
+     * method that must be implemented in a concrete sub class.
+     *
+     * @throws IOException if an IO error occurs
+     * @throws SAXException if a SAX error occurs
+     */
+    protected abstract void processKeys() throws IOException, SAXException;
+}
diff --git a/src/main/java/org/apache/commons/configuration/ConversionException.java b/src/main/java/org/apache/commons/configuration/ConversionException.java
new file mode 100644
index 0000000..179b2b9
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/ConversionException.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+/**
+ * Exception thrown when a property is incompatible with the type requested.
+ *
+ * @since 1.0
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: ConversionException.java 1208806 2011-11-30 21:34:11Z oheger $
+ */
+public class ConversionException extends ConfigurationRuntimeException
+{
+    /**
+     * The serial version UID.
+     */
+    private static final long serialVersionUID = -5167943099293540392L;
+
+    /**
+     * Constructs a new {@code ConversionException} without specified
+     * detail message.
+     */
+    public ConversionException()
+    {
+        super();
+    }
+
+    /**
+     * Constructs a new {@code ConversionException} with specified
+     * detail message.
+     *
+     * @param message  the error message
+     */
+    public ConversionException(String message)
+    {
+        super(message);
+    }
+
+    /**
+     * Constructs a new {@code ConversionException} with specified
+     * nested {@code Throwable}.
+     *
+     * @param cause  the exception or error that caused this exception to be thrown
+     */
+    public ConversionException(Throwable cause)
+    {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new {@code ConversionException} with specified
+     * detail message and nested {@code Throwable}.
+     *
+     * @param message  the error message
+     * @param cause    the exception or error that caused this exception to be thrown
+     */
+    public ConversionException(String message, Throwable cause)
+    {
+        super(message, cause);
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/DataConfiguration.java b/src/main/java/org/apache/commons/configuration/DataConfiguration.java
new file mode 100644
index 0000000..fef8ff2
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/DataConfiguration.java
@@ -0,0 +1,1979 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.awt.Color;
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.NoSuchElementException;
+
+import org.apache.commons.lang.ClassUtils;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * Decorator providing additional getters for any Configuration. This extended
+ * Configuration supports more types:
+ * <ul>
+ *   <li>{@link java.net.URL}</li>
+ *   <li>{@link java.util.Locale}</li>
+ *   <li>{@link java.util.Date}</li>
+ *   <li>{@link java.util.Calendar}</li>
+ *   <li>{@link java.awt.Color}</li>
+ *   <li>{@link java.net.InetAddress}</li>
+ *   <li>{@link javax.mail.internet.InternetAddress} (requires Javamail in the classpath)</li>
+ *   <li>{@link java.lang.Enum} (Java 5 enumeration types)</li>
+ * </ul>
+ *
+ * Lists and arrays are available for all types.
+ *
+ * <h4>Example</h4>
+ *
+ * Configuration file <tt>config.properties</tt>:
+ * <pre>
+ * title.color = #0000FF
+ * remote.host = 192.168.0.53
+ * default.locales = fr,en,de
+ * email.contact = ebourg at apache.org, oheger at apache.org
+ * </pre>
+ *
+ * Usage:
+ *
+ * <pre>
+ * DataConfiguration config = new DataConfiguration(new PropertiesConfiguration("config.properties"));
+ *
+ * // retrieve a property using a specialized getter
+ * Color color = config.getColor("title.color");
+ *
+ * // retrieve a property using a generic getter
+ * InetAddress host = (InetAddress) config.get(InetAddress.class, "remote.host");
+ * Locale[] locales = (Locale[]) config.getArray(Locale.class, "default.locales");
+ * List contacts = config.getList(InternetAddress.class, "email.contact");
+ * </pre>
+ *
+ * <h4>Dates</h4>
+ *
+ * Date objects are expected to be formatted with the pattern <tt>yyyy-MM-dd HH:mm:ss</tt>.
+ * This default format can be changed by specifying another format in the
+ * getters, or by putting a date format in the configuration under the key
+ * <tt>org.apache.commons.configuration.format.date</tt>.
+ *
+ * @author <a href="ebourg at apache.org">Emmanuel Bourg</a>
+ * @version $Id: DataConfiguration.java 1234985 2012-01-23 21:09:09Z oheger $
+ * @since 1.1
+ */
+public class DataConfiguration extends AbstractConfiguration implements Serializable
+{
+    /** The key of the property storing the user defined date format. */
+    public static final String DATE_FORMAT_KEY = "org.apache.commons.configuration.format.date";
+
+    /** The default format for dates. */
+    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+    /**
+     * The serial version UID.
+     */
+    private static final long serialVersionUID = -69011336405718640L;
+
+    /** Stores the wrapped configuration.*/
+    protected Configuration configuration;
+
+    /**
+     * Creates a new instance of {@code DataConfiguration} and sets the
+     * wrapped configuration.
+     *
+     * @param configuration the wrapped configuration
+     */
+    public DataConfiguration(Configuration configuration)
+    {
+        this.configuration = configuration;
+    }
+
+    /**
+     * Return the configuration decorated by this DataConfiguration.
+     *
+     * @return the wrapped configuration
+     */
+    public Configuration getConfiguration()
+    {
+        return configuration;
+    }
+
+    public Object getProperty(String key)
+    {
+        return configuration.getProperty(key);
+    }
+
+    @Override
+    protected void addPropertyDirect(String key, Object obj)
+    {
+        if (configuration instanceof AbstractConfiguration)
+        {
+            ((AbstractConfiguration) configuration).addPropertyDirect(key, obj);
+        }
+        else
+        {
+            configuration.addProperty(key, obj);
+        }
+    }
+
+    @Override
+    public void addProperty(String key, Object value)
+    {
+        getConfiguration().addProperty(key, value);
+    }
+
+    public boolean isEmpty()
+    {
+        return configuration.isEmpty();
+    }
+
+    public boolean containsKey(String key)
+    {
+        return configuration.containsKey(key);
+    }
+
+    @Override
+    public void clearProperty(String key)
+    {
+        configuration.clearProperty(key);
+    }
+
+    @Override
+    public void setProperty(String key, Object value)
+    {
+        configuration.setProperty(key, value);
+    }
+
+    public Iterator<String> getKeys()
+    {
+        return configuration.getKeys();
+    }
+
+    /**
+     * Get an object of the specified type associated with the given
+     * configuration key. If the key doesn't map to an existing object, the
+     * method returns null unless {@link #isThrowExceptionOnMissing()} is set
+     * to <tt>true</tt>.
+     *
+     * @param <T> the target type of the value
+     * @param cls the target class of the value
+     * @param key the key of the value
+     *
+     * @return the value of the requested type for the key
+     *
+     * @throws NoSuchElementException if the key doesn't map to an existing
+     *     object and <tt>throwExceptionOnMissing=true</tt>
+     * @throws ConversionException if the value is not compatible with the requested type
+     *
+     * @since 1.5
+     */
+    public <T> T get(Class<T> cls, String key)
+    {
+        T value = get(cls, key, null);
+        if (value != null)
+        {
+            return value;
+        }
+        else if (isThrowExceptionOnMissing())
+        {
+            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    /**
+     * Get an object of the specified type associated with the given
+     * configuration key. If the key doesn't map to an existing object, the
+     * default value is returned.
+     *
+     * @param <T>          the target type of the value
+     * @param cls          the target class of the value
+     * @param key          the key of the value
+     * @param defaultValue the default value
+     *
+     * @return the value of the requested type for the key
+     *
+     * @throws ConversionException if the value is not compatible with the requested type
+     *
+     * @since 1.5
+     */
+    public <T> T get(Class<T> cls, String key, T defaultValue)
+    {
+        Object value = resolveContainerStore(key);
+
+        if (value == null)
+        {
+            return defaultValue;
+        }
+
+        if (Date.class.equals(cls) || Calendar.class.equals(cls))
+        {
+            return convert(cls, key, interpolate(value), new String[] {getDefaultDateFormat()});
+        }
+        else
+        {
+            return convert(cls, key, interpolate(value), null);
+        }
+    }
+
+    /**
+     * Get a list of typed objects associated with the given configuration key.
+     * If the key doesn't map to an existing object, an empty list is returned.
+     *
+     * @param <T> the type expected for the elements of the list
+     * @param cls the class expected for the elements of the list
+     * @param key The configuration key.
+     * @return The associated list if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an object that
+     *     is not compatible with a list of the specified class.
+     *
+     * @since 1.5
+     */
+    public <T> List<T> getList(Class<T> cls, String key)
+    {
+        return getList(cls, key, new ArrayList<T>());
+    }
+
+    /**
+     * Get a list of typed objects associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value is
+     * returned.
+     *
+     * @param <T>          the type expected for the elements of the list
+     * @param cls          the class expected for the elements of the list
+     * @param key          the configuration key.
+     * @param defaultValue the default value.
+     * @return The associated List.
+     *
+     * @throws ConversionException is thrown if the key maps to an object that
+     *     is not compatible with a list of the specified class.
+     *
+     * @since 1.5
+     */
+    public <T> List<T> getList(Class<T> cls, String key, List<T> defaultValue)
+    {
+        Object value = getProperty(key);
+        Class<?> valueClass = value != null ? value.getClass() : null;
+
+        List<T> list;
+
+        if (value == null || (value instanceof String && StringUtils.isEmpty((String) value)))
+        {
+            // the value is null or is an empty string
+            list = defaultValue;
+        }
+        else
+        {
+            list = new ArrayList<T>();
+
+            Object[] params = null;
+            if (cls.equals(Date.class) || cls.equals(Calendar.class))
+            {
+                params = new Object[] {getDefaultDateFormat()};
+            }
+
+            if (valueClass.isArray())
+            {
+                // get the class of the objects contained in the array
+                Class<?> arrayType = valueClass.getComponentType();
+                int length = Array.getLength(value);
+
+                if (arrayType.equals(cls)
+                        || (arrayType.isPrimitive() && cls.equals(ClassUtils.primitiveToWrapper(arrayType))))
+                {
+                    // the value is an array of the specified type, or an array
+                    // of the primitive type derived from the specified type
+                    for (int i = 0; i < length; i++)
+                    {
+                        list.add(cls.cast(Array.get(value, i)));
+                    }
+                }
+                else
+                {
+                    // attempt to convert the elements of the array
+                    for (int i = 0; i < length; i++)
+                    {
+                        list.add(convert(cls, key, interpolate(Array.get(value, i)), params));
+                    }
+                }
+            }
+            else if (value instanceof Collection)
+            {
+                Collection<?> values = (Collection<?>) value;
+
+                for (Object o : values)
+                {
+                    list.add(convert(cls, key, interpolate(o), params));
+                }
+            }
+            else
+            {
+                // attempt to convert a single value
+                list.add(convert(cls, key, interpolate(value), params));
+            }
+        }
+
+        return list;
+    }
+
+    /**
+     * Get an array of typed objects associated with the given configuration key.
+     * If the key doesn't map to an existing object, an empty list is returned.
+     *
+     * @param cls the type expected for the elements of the array
+     * @param key The configuration key.
+     * @return The associated array if the key is found, and the value compatible with the type specified.
+     *
+     * @throws ConversionException is thrown if the key maps to an object that
+     *     is not compatible with a list of the specified class.
+     *
+     * @since 1.5
+     */
+    public Object getArray(Class<?> cls, String key)
+    {
+        return getArray(cls, key, Array.newInstance(cls, 0));
+    }
+
+    /**
+     * Get an array of typed objects associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value is returned.
+     *
+     * @param cls          the type expected for the elements of the array
+     * @param key          the configuration key.
+     * @param defaultValue the default value
+     * @return The associated array if the key is found, and the value compatible with the type specified.
+     *
+     * @throws ConversionException is thrown if the key maps to an object that
+     *     is not compatible with an array of the specified class.
+     * @throws IllegalArgumentException if the default value is not an array of the specified type
+     *
+     * @since 1.5
+     */
+    public Object getArray(Class<?> cls, String key, Object defaultValue)
+    {
+        // check the type of the default value
+        if (defaultValue != null
+                && (!defaultValue.getClass().isArray() || !cls
+                        .isAssignableFrom(defaultValue.getClass()
+                                .getComponentType())))
+        {
+            throw new IllegalArgumentException(
+                    "The type of the default value (" + defaultValue.getClass()
+                            + ")" + " is not an array of the specified class ("
+                            + cls + ")");
+        }
+
+        if (cls.isPrimitive())
+        {
+            return getPrimitiveArray(cls, key, defaultValue);
+        }
+
+        List<?> list = getList(cls, key);
+        if (list.isEmpty())
+        {
+            return defaultValue;
+        }
+        else
+        {
+            return list.toArray((Object[]) Array.newInstance(cls, list.size()));
+        }
+    }
+
+    /**
+     * Get an array of primitive values associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value is returned.
+     *
+     * @param cls          the primitive type expected for the elements of the array
+     * @param key          the configuration key.
+     * @param defaultValue the default value
+     * @return The associated array if the key is found, and the value compatible with the type specified.
+     *
+     * @throws ConversionException is thrown if the key maps to an object that
+     *     is not compatible with an array of the specified class.
+     *
+     * @since 1.5
+     */
+    private Object getPrimitiveArray(Class<?> cls, String key, Object defaultValue)
+    {
+        Object value = getProperty(key);
+        Class<?> valueClass = value != null ? value.getClass() : null;
+
+        Object array;
+
+        if (value == null || (value instanceof String && StringUtils.isEmpty((String) value)))
+        {
+            // the value is null or is an empty string
+            array = defaultValue;
+        }
+        else
+        {
+            if (valueClass.isArray())
+            {
+                // get the class of the objects contained in the array
+                Class<?> arrayType = valueClass.getComponentType();
+                int length = Array.getLength(value);
+
+                if (arrayType.equals(cls))
+                {
+                    // the value is an array of the same primitive type
+                    array = value;
+                }
+                else if (arrayType.equals(ClassUtils.primitiveToWrapper(cls)))
+                {
+                    // the value is an array of the wrapper type derived from the specified primitive type
+                    array = Array.newInstance(cls, length);
+
+                    for (int i = 0; i < length; i++)
+                    {
+                        Array.set(array, i, Array.get(value, i));
+                    }
+                }
+                else
+                {
+                    throw new ConversionException('\'' + key + "' (" + arrayType + ")"
+                            + " doesn't map to a compatible array of " + cls);
+                }
+            }
+            else if (value instanceof Collection)
+            {
+                Collection<?> values = (Collection<?>) value;
+
+                array = Array.newInstance(cls, values.size());
+
+                int i = 0;
+                for (Object o : values)
+                {
+                    // This is safe because PropertyConverter can handle
+                    // conversion to wrapper classes correctly.
+                    @SuppressWarnings("unchecked")
+                    Object convertedValue = convert(ClassUtils.primitiveToWrapper(cls), key, interpolate(o), null);
+                    Array.set(array, i++, convertedValue);
+                }
+            }
+            else
+            {
+                // attempt to convert a single value
+                // This is safe because PropertyConverter can handle
+                // conversion to wrapper classes correctly.
+                @SuppressWarnings("unchecked")
+                Object convertedValue = convert(ClassUtils.primitiveToWrapper(cls), key, interpolate(value), null);
+
+                // create an array of one element
+                array = Array.newInstance(cls, 1);
+                Array.set(array, 0, convertedValue);
+            }
+        }
+
+        return array;
+    }
+
+    /**
+     * Get a list of Boolean objects associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty list is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated Boolean list if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of booleans.
+     */
+    public List<Boolean> getBooleanList(String key)
+    {
+        return getBooleanList(key, new ArrayList<Boolean>());
+    }
+
+    /**
+     * Get a list of Boolean objects associated with the given
+     * configuration key. If the key doesn't map to an existing object,
+     * the default value is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated List of Booleans.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of booleans.
+     */
+    public List<Boolean> getBooleanList(String key, List<Boolean> defaultValue)
+    {
+         return getList(Boolean.class, key, defaultValue);
+    }
+
+    /**
+     * Get an array of boolean primitives associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated boolean array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of booleans.
+     */
+    public boolean[] getBooleanArray(String key)
+    {
+        return (boolean[]) getArray(Boolean.TYPE, key);
+    }
+
+    /**
+     * Get an array of boolean primitives associated with the given
+     * configuration key. If the key doesn't map to an existing object,
+     * the default value is returned.
+     *
+     * @param key          The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated boolean array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of booleans.
+     */
+    public boolean[] getBooleanArray(String key, boolean[] defaultValue)
+    {
+        return (boolean[]) getArray(Boolean.TYPE, key, defaultValue);
+    }
+
+    /**
+     * Get a list of Byte objects associated with the given configuration key.
+     * If the key doesn't map to an existing object an empty list is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated Byte list if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of bytes.
+     */
+    public List<Byte> getByteList(String key)
+    {
+        return getByteList(key, new ArrayList<Byte>());
+    }
+
+    /**
+     * Get a list of Byte objects associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value is
+     * returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated List of Bytes.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of bytes.
+     */
+    public List<Byte> getByteList(String key, List<Byte> defaultValue)
+    {
+        return getList(Byte.class, key, defaultValue);
+    }
+
+    /**
+     * Get an array of byte primitives associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated byte array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of bytes.
+     */
+    public byte[] getByteArray(String key)
+    {
+        return getByteArray(key, new byte[0]);
+    }
+
+    /**
+     * Get an array of byte primitives associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue the default value, which will be returned if the property is not found
+     * @return The associated byte array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of bytes.
+     */
+    public byte[] getByteArray(String key, byte[] defaultValue)
+    {
+        return (byte[]) getArray(Byte.TYPE, key, defaultValue);
+    }
+
+    /**
+     * Get a list of Short objects associated with the given configuration key.
+     * If the key doesn't map to an existing object an empty list is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated Short list if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of shorts.
+     */
+    public List<Short> getShortList(String key)
+    {
+        return getShortList(key, new ArrayList<Short>());
+    }
+
+    /**
+     * Get a list of Short objects associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value is
+     * returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated List of Shorts.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of shorts.
+     */
+    public List<Short> getShortList(String key, List<Short> defaultValue)
+    {
+        return getList(Short.class, key, defaultValue);
+    }
+
+    /**
+     * Get an array of short primitives associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated short array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of shorts.
+     */
+    public short[] getShortArray(String key)
+    {
+        return getShortArray(key, new short[0]);
+    }
+
+    /**
+     * Get an array of short primitives associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue the default value, which will be returned if the property is not found
+     * @return The associated short array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of shorts.
+     */
+    public short[] getShortArray(String key, short[] defaultValue)
+    {
+        return (short[]) getArray(Short.TYPE, key, defaultValue);
+    }
+
+    /**
+     * Get a list of Integer objects associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty list is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated Integer list if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of integers.
+     */
+    public List<Integer> getIntegerList(String key)
+    {
+        return getIntegerList(key, new ArrayList<Integer>());
+    }
+
+    /**
+     * Get a list of Integer objects associated with the given
+     * configuration key. If the key doesn't map to an existing object,
+     * the default value is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated List of Integers.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of integers.
+     */
+    public List<Integer> getIntegerList(String key, List<Integer> defaultValue)
+    {
+        return getList(Integer.class, key, defaultValue);
+    }
+
+    /**
+     * Get an array of int primitives associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated int array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of integers.
+     */
+    public int[] getIntArray(String key)
+    {
+        return getIntArray(key, new int[0]);
+    }
+
+    /**
+     * Get an array of int primitives associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue the default value, which will be returned if the property is not found
+     * @return The associated int array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of integers.
+     */
+    public int[] getIntArray(String key, int[] defaultValue)
+    {
+        return (int[]) getArray(Integer.TYPE, key, defaultValue);
+    }
+
+    /**
+     * Get a list of Long objects associated with the given configuration key.
+     * If the key doesn't map to an existing object an empty list is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated Long list if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of longs.
+     */
+    public List<Long> getLongList(String key)
+    {
+        return getLongList(key, new ArrayList<Long>());
+    }
+
+    /**
+     * Get a list of Long objects associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value is
+     * returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated List of Longs.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of longs.
+     */
+    public List<Long> getLongList(String key, List<Long> defaultValue)
+    {
+        return getList(Long.class, key, defaultValue);
+    }
+
+    /**
+     * Get an array of long primitives associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated long array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of longs.
+     */
+    public long[] getLongArray(String key)
+    {
+        return getLongArray(key, new long[0]);
+    }
+
+    /**
+     * Get an array of long primitives associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue the default value, which will be returned if the property is not found
+     * @return The associated long array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of longs.
+     */
+    public long[] getLongArray(String key, long[] defaultValue)
+    {
+        return (long[]) getArray(Long.TYPE, key, defaultValue);
+    }
+
+    /**
+     * Get a list of Float objects associated with the given configuration key.
+     * If the key doesn't map to an existing object an empty list is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated Float list if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of floats.
+     */
+    public List<Float> getFloatList(String key)
+    {
+        return getFloatList(key, new ArrayList<Float>());
+    }
+
+    /**
+     * Get a list of Float objects associated with the given
+     * configuration key. If the key doesn't map to an existing object,
+     * the default value is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated List of Floats.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of floats.
+     */
+    public List<Float> getFloatList(String key, List<Float> defaultValue)
+    {
+        return getList(Float.class, key, defaultValue);
+    }
+
+    /**
+     * Get an array of float primitives associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated float array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of floats.
+     */
+    public float[] getFloatArray(String key)
+    {
+        return getFloatArray(key, new float[0]);
+    }
+
+    /**
+     * Get an array of float primitives associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue the default value, which will be returned if the property is not found
+     * @return The associated float array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of floats.
+     */
+    public float[] getFloatArray(String key, float[] defaultValue)
+    {
+        return (float[]) getArray(Float.TYPE, key, defaultValue);
+    }
+
+    /**
+     * Get a list of Double objects associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty list is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated Double list if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of doubles.
+     */
+    public List<Double> getDoubleList(String key)
+    {
+        return getDoubleList(key, new ArrayList<Double>());
+    }
+
+    /**
+     * Get a list of Double objects associated with the given
+     * configuration key. If the key doesn't map to an existing object,
+     * the default value is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated List of Doubles.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of doubles.
+     */
+    public List<Double> getDoubleList(String key, List<Double> defaultValue)
+    {
+        return getList(Double.class, key, defaultValue);
+    }
+
+    /**
+     * Get an array of double primitives associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated double array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of doubles.
+     */
+    public double[] getDoubleArray(String key)
+    {
+        return getDoubleArray(key, new double[0]);
+    }
+
+    /**
+     * Get an array of double primitives associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue the default value, which will be returned if the property is not found
+     * @return The associated double array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of doubles.
+     */
+    public double[] getDoubleArray(String key, double[] defaultValue)
+    {
+        return (double[]) getArray(Double.TYPE, key, defaultValue);
+    }
+
+    /**
+     * Get a list of BigIntegers associated with the given configuration key.
+     * If the key doesn't map to an existing object an empty list is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated BigInteger list if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of BigIntegers.
+     */
+    public List<BigInteger> getBigIntegerList(String key)
+    {
+        return getBigIntegerList(key, new ArrayList<BigInteger>());
+    }
+
+    /**
+     * Get a list of BigIntegers associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value is
+     * returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated List of BigIntegers.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of BigIntegers.
+     */
+    public List<BigInteger> getBigIntegerList(String key, List<BigInteger> defaultValue)
+    {
+        return getList(BigInteger.class, key, defaultValue);
+    }
+
+    /**
+     * Get an array of BigIntegers associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated BigInteger array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of BigIntegers.
+     */
+    public BigInteger[] getBigIntegerArray(String key)
+    {
+        return getBigIntegerArray(key, new BigInteger[0]);
+    }
+
+    /**
+     * Get an array of BigIntegers associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue the default value, which will be returned if the property is not found
+     * @return The associated BigInteger array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of BigIntegers.
+     */
+    public BigInteger[] getBigIntegerArray(String key, BigInteger[] defaultValue)
+    {
+        return (BigInteger[]) getArray(BigInteger.class, key, defaultValue);
+    }
+
+    /**
+     * Get a list of BigDecimals associated with the given configuration key.
+     * If the key doesn't map to an existing object an empty list is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated BigDecimal list if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of BigDecimals.
+     */
+    public List<BigDecimal> getBigDecimalList(String key)
+    {
+        return getBigDecimalList(key, new ArrayList<BigDecimal>());
+    }
+
+    /**
+     * Get a list of BigDecimals associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value is
+     * returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated List of BigDecimals.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of BigDecimals.
+     */
+    public List<BigDecimal> getBigDecimalList(String key, List<BigDecimal> defaultValue)
+    {
+        return getList(BigDecimal.class, key, defaultValue);
+    }
+
+    /**
+     * Get an array of BigDecimals associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated BigDecimal array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of BigDecimals.
+     */
+    public BigDecimal[] getBigDecimalArray(String key)
+    {
+        return getBigDecimalArray(key, new BigDecimal[0]);
+    }
+
+    /**
+     * Get an array of BigDecimals associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue the default value, which will be returned if the property is not found
+     * @return The associated BigDecimal array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of BigDecimals.
+     */
+    public BigDecimal[] getBigDecimalArray(String key, BigDecimal[] defaultValue)
+    {
+        return (BigDecimal[]) getArray(BigDecimal.class, key, defaultValue);
+    }
+
+    /**
+     * Get an URL associated with the given configuration key.
+     *
+     * @param key The configuration key.
+     * @return The associated URL.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not an URL.
+     */
+    public URL getURL(String key)
+    {
+        return get(URL.class, key);
+    }
+
+    /**
+     * Get an URL associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value
+     * is returned.
+     *
+     * @param key          The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated URL.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not an URL.
+     */
+    public URL getURL(String key, URL defaultValue)
+    {
+        return get(URL.class, key, defaultValue);
+    }
+
+    /**
+     * Get a list of URLs associated with the given configuration key.
+     * If the key doesn't map to an existing object an empty list is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated URL list if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of URLs.
+     */
+    public List<URL> getURLList(String key)
+    {
+        return getURLList(key, new ArrayList<URL>());
+    }
+
+    /**
+     * Get a list of URLs associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value is
+     * returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated List of URLs.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of URLs.
+     */
+    public List<URL> getURLList(String key, List<URL> defaultValue)
+    {
+        return getList(URL.class, key, defaultValue);
+    }
+
+    /**
+     * Get an array of URLs associated with the given configuration key.
+     * If the key doesn't map to an existing object an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated URL array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of URLs.
+     */
+    public URL[] getURLArray(String key)
+    {
+        return getURLArray(key, new URL[0]);
+    }
+
+    /**
+     * Get an array of URLs associated with the given configuration key.
+     * If the key doesn't map to an existing object an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue the default value, which will be returned if the property is not found
+     * @return The associated URL array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of URLs.
+     */
+    public URL[] getURLArray(String key, URL[] defaultValue)
+    {
+        return (URL[]) getArray(URL.class, key, defaultValue);
+    }
+
+    /**
+     * Get a Date associated with the given configuration key. If the property
+     * is a String, it will be parsed with the format defined by the user in
+     * the {@link #DATE_FORMAT_KEY} property, or if it's not defined with the
+     * {@link #DEFAULT_DATE_FORMAT} pattern.
+     *
+     * @param key The configuration key.
+     * @return The associated Date.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Date.
+     */
+    public Date getDate(String key)
+    {
+        return get(Date.class, key);
+    }
+
+    /**
+     * Get a Date associated with the given configuration key. If the property
+     * is a String, it will be parsed with the specified format pattern.
+     *
+     * @param key    The configuration key.
+     * @param format The non-localized {@link java.text.DateFormat} pattern.
+     * @return The associated Date
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Date.
+     */
+    public Date getDate(String key, String format)
+    {
+        Date value = getDate(key, null, format);
+        if (value != null)
+        {
+            return value;
+        }
+        else if (isThrowExceptionOnMissing())
+        {
+            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    /**
+     * Get a Date associated with the given configuration key. If the property
+     * is a String, it will be parsed with the format defined by the user in
+     * the {@link #DATE_FORMAT_KEY} property, or if it's not defined with the
+     * {@link #DEFAULT_DATE_FORMAT} pattern. If the key doesn't map to an
+     * existing object, the default value is returned.
+     *
+     * @param key          The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated Date.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Date.
+     */
+    public Date getDate(String key, Date defaultValue)
+    {
+        return getDate(key, defaultValue, getDefaultDateFormat());
+    }
+
+    /**
+     * Get a Date associated with the given configuration key. If the property
+     * is a String, it will be parsed with the specified format pattern.
+     * If the key doesn't map to an existing object, the default value
+     * is returned.
+     *
+     * @param key          The configuration key.
+     * @param defaultValue The default value.
+     * @param format       The non-localized {@link java.text.DateFormat} pattern.
+     * @return The associated Date.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Date.
+     */
+    public Date getDate(String key, Date defaultValue, String format)
+    {
+        Object value = resolveContainerStore(key);
+
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        else
+        {
+            try
+            {
+                return PropertyConverter.toDate(interpolate(value), format);
+            }
+            catch (ConversionException e)
+            {
+                throw new ConversionException('\'' + key + "' doesn't map to a Date", e);
+            }
+        }
+    }
+    public List<Date> getDateList(String key)
+    {
+        return getDateList(key, new ArrayList<Date>());
+    }
+
+    /**
+     * Get a list of Dates associated with the given configuration key.
+     * If the property is a list of Strings, they will be parsed with the
+     * specified format pattern. If the key doesn't map to an existing object
+     * an empty list is returned.
+     *
+     * @param key    The configuration key.
+     * @param format The non-localized {@link java.text.DateFormat} pattern.
+     * @return The associated Date list if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Dates.
+     */
+    public List<Date> getDateList(String key, String format)
+    {
+        return getDateList(key, new ArrayList<Date>(), format);
+    }
+
+    /**
+     * Get a list of Dates associated with the given configuration key.
+     * If the property is a list of Strings, they will be parsed with the
+     * format defined by the user in the {@link #DATE_FORMAT_KEY} property,
+     * or if it's not defined with the {@link #DEFAULT_DATE_FORMAT} pattern.
+     * If the key doesn't map to an existing object, the default value is
+     * returned.
+     *
+     * @param key          The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated Date list if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Dates.
+     */
+    public List<Date> getDateList(String key, List<Date> defaultValue)
+    {
+        return getDateList(key, defaultValue, getDefaultDateFormat());
+    }
+
+    /**
+     * Get a list of Dates associated with the given configuration key.
+     * If the property is a list of Strings, they will be parsed with the
+     * specified format pattern. If the key doesn't map to an existing object,
+     * the default value is returned.
+     *
+     * @param key          The configuration key.
+     * @param defaultValue The default value.
+     * @param format       The non-localized {@link java.text.DateFormat} pattern.
+     * @return The associated Date list if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Dates.
+     */
+    public List<Date> getDateList(String key, List<Date> defaultValue, String format)
+    {
+        Object value = getProperty(key);
+
+        List<Date> list;
+
+        if (value == null || (value instanceof String && StringUtils.isEmpty((String) value)))
+        {
+            list = defaultValue;
+        }
+        else if (value.getClass().isArray())
+        {
+            list = new ArrayList<Date>();
+            int length = Array.getLength(value);
+            for (int i = 0; i < length; i++)
+            {
+                list.add(convert(Date.class, key, interpolate(Array.get(value, i)), new String[] {format}));
+            }
+        }
+        else if (value instanceof Collection)
+        {
+            Collection<?> values = (Collection<?>) value;
+            list = new ArrayList<Date>();
+
+            for (Object o : values)
+            {
+                list.add(convert(Date.class, key, interpolate(o), new String[] {format}));
+            }
+        }
+        else
+        {
+            // attempt to convert a single value
+            list = new ArrayList<Date>();
+            list.add(convert(Date.class, key, interpolate(value), new String[] {format}));
+        }
+
+        return list;
+    }
+
+    /**
+     * Get an array of Dates associated with the given configuration key.
+     * If the property is a list of Strings, they will be parsed with the
+     * format defined by the user in the {@link #DATE_FORMAT_KEY} property,
+     * or if it's not defined with the {@link #DEFAULT_DATE_FORMAT} pattern.
+     * If the key doesn't map to an existing object an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated Date array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Dates.
+     */
+    public Date[] getDateArray(String key)
+    {
+        return getDateArray(key, new Date[0]);
+    }
+
+    /**
+     * Get an array of Dates associated with the given configuration key.
+     * If the property is a list of Strings, they will be parsed with the
+     * specified format pattern. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key    The configuration key.
+     * @param format The non-localized {@link java.text.DateFormat} pattern.
+     * @return The associated Date array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Dates.
+     */
+    public Date[] getDateArray(String key, String format)
+    {
+        return getDateArray(key, new Date[0], format);
+    }
+
+    /**
+     * Get an array of Dates associated with the given configuration key.
+     * If the property is a list of Strings, they will be parsed with the
+     * format defined by the user in the {@link #DATE_FORMAT_KEY} property,
+     * or if it's not defined with the {@link #DEFAULT_DATE_FORMAT} pattern.
+     * If the key doesn't map to an existing object an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue the default value, which will be returned if the property is not found
+     * @return The associated Date array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Dates.
+     */
+    public Date[] getDateArray(String key, Date[] defaultValue)
+    {
+        return getDateArray(key, defaultValue, getDefaultDateFormat());
+    }
+
+    /**
+     * Get an array of Dates associated with the given configuration key.
+     * If the property is a list of Strings, they will be parsed with the
+     * specified format pattern. If the key doesn't map to an existing object,
+     * the default value is returned.
+     *
+     * @param key          The configuration key.
+     * @param defaultValue The default value.
+     * @param format       The non-localized {@link java.text.DateFormat} pattern.
+     * @return The associated Date array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Dates.
+     */
+    public Date[] getDateArray(String key, Date[] defaultValue, String format)
+    {
+        List<Date> list = getDateList(key, format);
+        if (list.isEmpty())
+        {
+            return defaultValue;
+        }
+        else
+        {
+            return list.toArray(new Date[list.size()]);
+        }
+    }
+
+    /**
+     * Get a Calendar associated with the given configuration key. If the
+     * property is a String, it will be parsed with the format defined by the
+     * user in the {@link #DATE_FORMAT_KEY} property, or if it's not defined
+     * with the {@link #DEFAULT_DATE_FORMAT} pattern.
+     *
+     * @param key The configuration key.
+     * @return The associated Calendar.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Calendar.
+     */
+    public Calendar getCalendar(String key)
+    {
+        return get(Calendar.class, key);
+    }
+
+    /**
+     * Get a Calendar associated with the given configuration key. If the
+     * property is a String, it will be parsed with the specified format
+     * pattern.
+     *
+     * @param key    The configuration key.
+     * @param format The non-localized {@link java.text.DateFormat} pattern.
+     * @return The associated Calendar
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Calendar.
+     */
+    public Calendar getCalendar(String key, String format)
+    {
+        Calendar value = getCalendar(key, null, format);
+        if (value != null)
+        {
+            return value;
+        }
+        else if (isThrowExceptionOnMissing())
+        {
+            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    /**
+     * Get a Calendar associated with the given configuration key. If the
+     * property is a String, it will be parsed with the format defined by the
+     * user in the {@link #DATE_FORMAT_KEY} property, or if it's not defined
+     * with the {@link #DEFAULT_DATE_FORMAT} pattern. If the key doesn't map
+     * to an existing object, the default value is returned.
+     *
+     * @param key          The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated Calendar.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Calendar.
+     */
+    public Calendar getCalendar(String key, Calendar defaultValue)
+    {
+        return getCalendar(key, defaultValue, getDefaultDateFormat());
+    }
+
+    /**
+     * Get a Calendar associated with the given configuration key. If the
+     * property is a String, it will be parsed with the specified format
+     * pattern. If the key doesn't map to an existing object, the default
+     * value is returned.
+     *
+     * @param key          The configuration key.
+     * @param defaultValue The default value.
+     * @param format       The non-localized {@link java.text.DateFormat} pattern.
+     * @return The associated Calendar.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Calendar.
+     */
+    public Calendar getCalendar(String key, Calendar defaultValue, String format)
+    {
+        Object value = resolveContainerStore(key);
+
+        if (value == null)
+        {
+            return defaultValue;
+        }
+        else
+        {
+            try
+            {
+                return PropertyConverter.toCalendar(interpolate(value), format);
+            }
+            catch (ConversionException e)
+            {
+                throw new ConversionException('\'' + key + "' doesn't map to a Calendar", e);
+            }
+        }
+    }
+
+    /**
+     * Get a list of Calendars associated with the given configuration key.
+     * If the property is a list of Strings, they will be parsed with the
+     * format defined by the user in the {@link #DATE_FORMAT_KEY} property,
+     * or if it's not defined with the {@link #DEFAULT_DATE_FORMAT} pattern.
+     * If the key doesn't map to an existing object an empty list is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated Calendar list if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Calendars.
+     */
+    public List<Calendar> getCalendarList(String key)
+    {
+        return getCalendarList(key, new ArrayList<Calendar>());
+    }
+
+    /**
+     * Get a list of Calendars associated with the given configuration key.
+     * If the property is a list of Strings, they will be parsed with the
+     * specified format pattern. If the key doesn't map to an existing object
+     * an empty list is returned.
+     *
+     * @param key    The configuration key.
+     * @param format The non-localized {@link java.text.DateFormat} pattern.
+     * @return The associated Calendar list if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Calendars.
+     */
+    public List<Calendar> getCalendarList(String key, String format)
+    {
+        return getCalendarList(key, new ArrayList<Calendar>(), format);
+    }
+
+    /**
+     * Get a list of Calendars associated with the given configuration key.
+     * If the property is a list of Strings, they will be parsed with the
+     * format defined by the user in the {@link #DATE_FORMAT_KEY} property,
+     * or if it's not defined with the {@link #DEFAULT_DATE_FORMAT} pattern.
+     * If the key doesn't map to an existing object, the default value is
+     * returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated Calendar list if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Calendars.
+     */
+    public List<Calendar> getCalendarList(String key, List<Calendar> defaultValue)
+    {
+        return getCalendarList(key, defaultValue, getDefaultDateFormat());
+    }
+
+    /**
+     * Get a list of Calendars associated with the given configuration key.
+     * If the property is a list of Strings, they will be parsed with the
+     * specified format pattern. If the key doesn't map to an existing object,
+     * the default value is returned.
+     *
+     * @param key          The configuration key.
+     * @param defaultValue The default value.
+     * @param format       The non-localized {@link java.text.DateFormat} pattern.
+     * @return The associated Calendar list if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Calendars.
+     */
+    public List<Calendar> getCalendarList(String key, List<Calendar> defaultValue, String format)
+    {
+        Object value = getProperty(key);
+
+        List<Calendar> list;
+
+        if (value == null || (value instanceof String && StringUtils.isEmpty((String) value)))
+        {
+            list = defaultValue;
+        }
+        else if (value.getClass().isArray())
+        {
+            list = new ArrayList<Calendar>();
+            int length = Array.getLength(value);
+            for (int i = 0; i < length; i++)
+            {
+                list.add(convert(Calendar.class, key, interpolate(Array.get(value, i)), new String[] {format}));
+            }
+        }
+        else if (value instanceof Collection)
+        {
+            Collection<?> values = (Collection<?>) value;
+            list = new ArrayList<Calendar>();
+
+            for (Object o : values)
+            {
+                list.add(convert(Calendar.class, key, interpolate(o), new String[] {format}));
+            }
+        }
+        else
+        {
+            // attempt to convert a single value
+            list = new ArrayList<Calendar>();
+            list.add(convert(Calendar.class, key, interpolate(value), new String[] {format}));
+        }
+
+        return list;
+    }
+
+    /**
+     * Get an array of Calendars associated with the given configuration key.
+     * If the property is a list of Strings, they will be parsed with the
+     * format defined by the user in the {@link #DATE_FORMAT_KEY} property,
+     * or if it's not defined with the {@link #DEFAULT_DATE_FORMAT} pattern.
+     * If the key doesn't map to an existing object an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated Calendar array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Calendars.
+     */
+    public Calendar[] getCalendarArray(String key)
+    {
+        return getCalendarArray(key, new Calendar[0]);
+    }
+
+    /**
+     * Get an array of Calendars associated with the given configuration key.
+     * If the property is a list of Strings, they will be parsed with the
+     * specified format pattern. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key    The configuration key.
+     * @param format The non-localized {@link java.text.DateFormat} pattern.
+     * @return The associated Calendar array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Calendars.
+     */
+    public Calendar[] getCalendarArray(String key, String format)
+    {
+        return getCalendarArray(key, new Calendar[0], format);
+    }
+
+    /**
+     * Get an array of Calendars associated with the given configuration key.
+     * If the property is a list of Strings, they will be parsed with the
+     * format defined by the user in the {@link #DATE_FORMAT_KEY} property,
+     * or if it's not defined with the {@link #DEFAULT_DATE_FORMAT} pattern.
+     * If the key doesn't map to an existing object an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue the default value, which will be returned if the property is not found
+     * @return The associated Calendar array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Calendars.
+     */
+    public Calendar[] getCalendarArray(String key, Calendar[] defaultValue)
+    {
+        return getCalendarArray(key, defaultValue, getDefaultDateFormat());
+    }
+
+    /**
+     * Get an array of Calendars associated with the given configuration key.
+     * If the property is a list of Strings, they will be parsed with the
+     * specified format pattern. If the key doesn't map to an existing object,
+     * the default value is returned.
+     *
+     * @param key          The configuration key.
+     * @param defaultValue The default value.
+     * @param format       The non-localized {@link java.text.DateFormat} pattern.
+     * @return The associated Calendar array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Calendars.
+     */
+    public Calendar[] getCalendarArray(String key, Calendar[] defaultValue, String format)
+    {
+        List<Calendar> list = getCalendarList(key, format);
+        if (list.isEmpty())
+        {
+            return defaultValue;
+        }
+        else
+        {
+            return list.toArray(new Calendar[list.size()]);
+        }
+    }
+
+    /**
+     * Returns the date format specified by the user in the DATE_FORMAT_KEY
+     * property, or the default format otherwise.
+     *
+     * @return the default date format
+     */
+    private String getDefaultDateFormat()
+    {
+        return getString(DATE_FORMAT_KEY, DEFAULT_DATE_FORMAT);
+    }
+
+    /**
+     * Get a Locale associated with the given configuration key.
+     *
+     * @param key The configuration key.
+     * @return The associated Locale.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Locale.
+     */
+    public Locale getLocale(String key)
+    {
+        return get(Locale.class, key);
+    }
+
+    /**
+     * Get a Locale associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value
+     * is returned.
+     *
+     * @param key          The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated Locale.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Locale.
+     */
+    public Locale getLocale(String key, Locale defaultValue)
+    {
+        return get(Locale.class, key, defaultValue);
+    }
+
+    /**
+     * Get a list of Locales associated with the given configuration key.
+     * If the key doesn't map to an existing object an empty list is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated Locale list if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Locales.
+     */
+    public List<Locale> getLocaleList(String key)
+    {
+        return getLocaleList(key, new ArrayList<Locale>());
+    }
+
+    /**
+     * Get a list of Locales associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value is
+     * returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated List of Locales.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Locales.
+     */
+    public List<Locale> getLocaleList(String key, List<Locale> defaultValue)
+    {
+        return getList(Locale.class, key, defaultValue);
+    }
+
+    /**
+     * Get an array of Locales associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated Locale array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Locales.
+     */
+    public Locale[] getLocaleArray(String key)
+    {
+        return getLocaleArray(key, new Locale[0]);
+    }
+
+    /**
+     * Get an array of Locales associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue the default value, which will be returned if the property is not found
+     * @return The associated Locale array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Locales.
+     */
+    public Locale[] getLocaleArray(String key, Locale[] defaultValue)
+    {
+        return (Locale[]) getArray(Locale.class, key, defaultValue);
+    }
+
+    /**
+     * Get a Color associated with the given configuration key.
+     *
+     * @param key The configuration key.
+     * @return The associated Color.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Color.
+     */
+    public Color getColor(String key)
+    {
+        return get(Color.class, key);
+    }
+
+    /**
+     * Get a Color associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value
+     * is returned.
+     *
+     * @param key          The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated Color.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a Color.
+     */
+    public Color getColor(String key, Color defaultValue)
+    {
+        return get(Color.class, key, defaultValue);
+    }
+
+    /**
+     * Get a list of Colors associated with the given configuration key.
+     * If the key doesn't map to an existing object an empty list is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated Color list if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Colors.
+     */
+    public List<Color> getColorList(String key)
+    {
+        return getColorList(key, new ArrayList<Color>());
+    }
+
+    /**
+     * Get a list of Colors associated with the given configuration key.
+     * If the key doesn't map to an existing object, the default value is
+     * returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue The default value.
+     * @return The associated List of Colors.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Colors.
+     */
+    public List<Color> getColorList(String key, List<Color> defaultValue)
+    {
+        return getList(Color.class, key, defaultValue);
+    }
+
+    /**
+     * Get an array of Colors associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @return The associated Color array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Colors.
+     */
+    public Color[] getColorArray(String key)
+    {
+        return getColorArray(key, new Color[0]);
+    }
+
+    /**
+     * Get an array of Colors associated with the given
+     * configuration key. If the key doesn't map to an existing object
+     * an empty array is returned.
+     *
+     * @param key The configuration key.
+     * @param defaultValue the default value, which will be returned if the property is not found
+     * @return The associated Color array if the key is found.
+     *
+     * @throws ConversionException is thrown if the key maps to an
+     *         object that is not a list of Colors.
+     */
+    public Color[] getColorArray(String key, Color[] defaultValue)
+    {
+        return (Color[]) getArray(Color.class, key, defaultValue);
+    }
+
+    /**
+     * Helper method for performing a type conversion using the
+     * {@code PropertyConverter} class.
+     *
+     * @param <T> the target type of the conversion
+     * @param cls the target class of the conversion
+     * @param key the configuration key
+     * @param value the value to be converted
+     * @param params additional parameters
+     * @throws ConversionException if the value is not compatible with the
+     *         requested type
+     */
+    private static <T> T convert(Class<T> cls, String key, Object value,
+            Object[] params)
+    {
+        try
+        {
+            Object result = PropertyConverter.to(cls, value, params);
+            // Will not throw a ClassCastException because PropertyConverter
+            // would have thrown a ConversionException if conversion had failed.
+            return cls.cast(result);
+        }
+        catch (ConversionException e)
+        {
+            throw new ConversionException('\'' + key + "' doesn't map to a "
+                    + cls, e);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/DatabaseConfiguration.java b/src/main/java/org/apache/commons/configuration/DatabaseConfiguration.java
new file mode 100644
index 0000000..28828ec
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/DatabaseConfiguration.java
@@ -0,0 +1,709 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Configuration stored in a database. The properties are retrieved from a
+ * table containing at least one column for the keys, and one column for the
+ * values. It's possible to store several configurations in the same table by
+ * adding a column containing the name of the configuration. The name of the
+ * table and the columns is specified in the constructor.
+ *
+ * <h4>Example 1 - One configuration per table</h4>
+ *
+ * <pre>
+ * CREATE TABLE myconfig (
+ *     `key`   VARCHAR NOT NULL PRIMARY KEY,
+ *     `value` VARCHAR
+ * );
+ *
+ * INSERT INTO myconfig (key, value) VALUES ('foo', 'bar');
+ *
+ *
+ * Configuration config = new DatabaseConfiguration(datasource, "myconfig", "key", "value");
+ * String value = config.getString("foo");
+ * </pre>
+ *
+ * <h4>Example 2 - Multiple configurations per table</h4>
+ *
+ * <pre>
+ * CREATE TABLE myconfigs (
+ *     `name`  VARCHAR NOT NULL,
+ *     `key`   VARCHAR NOT NULL,
+ *     `value` VARCHAR,
+ *     CONSTRAINT sys_pk_myconfigs PRIMARY KEY (`name`, `key`)
+ * );
+ *
+ * INSERT INTO myconfigs (name, key, value) VALUES ('config1', 'key1', 'value1');
+ * INSERT INTO myconfigs (name, key, value) VALUES ('config2', 'key2', 'value2');
+ *
+ *
+ * Configuration config1 = new DatabaseConfiguration(datasource, "myconfigs", "name", "key", "value", "config1");
+ * String value1 = conf.getString("key1");
+ *
+ * Configuration config2 = new DatabaseConfiguration(datasource, "myconfigs", "name", "key", "value", "config2");
+ * String value2 = conf.getString("key2");
+ * </pre>
+ * The configuration can be instructed to perform commits after database updates.
+ * This is achieved by setting the {@code commits} parameter of the
+ * constructors to <b>true</b>. If commits should not be performed (which is the
+ * default behavior), it should be ensured that the connections returned by the
+ * {@code DataSource} are in auto-commit mode.
+ *
+ * <h1>Note: Like JDBC itself, protection against SQL injection is left to the user.</h1>
+ * @since 1.0
+ *
+ * @author <a href="mailto:ebourg at apache.org">Emmanuel Bourg</a>
+ * @version $Id: DatabaseConfiguration.java 1344442 2012-05-30 20:17:35Z oheger $
+ */
+public class DatabaseConfiguration extends AbstractConfiguration
+{
+    /** The datasource to connect to the database. */
+    private final DataSource datasource;
+
+    /** The name of the table containing the configurations. */
+    private final String table;
+
+    /** The column containing the name of the configuration. */
+    private final String nameColumn;
+
+    /** The column containing the keys. */
+    private final String keyColumn;
+
+    /** The column containing the values. */
+    private final String valueColumn;
+
+    /** The name of the configuration. */
+    private final String name;
+
+    /** A flag whether commits should be performed by this configuration. */
+    private final boolean doCommits;
+
+    /**
+     * Build a configuration from a table containing multiple configurations.
+     * No commits are performed by the new configuration instance.
+     *
+     * @param datasource    the datasource to connect to the database
+     * @param table         the name of the table containing the configurations
+     * @param nameColumn    the column containing the name of the configuration
+     * @param keyColumn     the column containing the keys of the configuration
+     * @param valueColumn   the column containing the values of the configuration
+     * @param name          the name of the configuration
+     */
+    public DatabaseConfiguration(DataSource datasource, String table, String nameColumn,
+            String keyColumn, String valueColumn, String name)
+    {
+        this(datasource, table, nameColumn, keyColumn, valueColumn, name, false);
+    }
+
+    /**
+     * Creates a new instance of {@code DatabaseConfiguration} that operates on
+     * a database table containing multiple configurations.
+     *
+     * @param datasource the {@code DataSource} to connect to the database
+     * @param table the name of the table containing the configurations
+     * @param nameColumn the column containing the name of the configuration
+     * @param keyColumn the column containing the keys of the configuration
+     * @param valueColumn the column containing the values of the configuration
+     * @param name the name of the configuration
+     * @param commits a flag whether the configuration should perform a commit
+     *        after a database update
+     */
+    public DatabaseConfiguration(DataSource datasource, String table,
+            String nameColumn, String keyColumn, String valueColumn,
+            String name, boolean commits)
+    {
+        this.datasource = datasource;
+        this.table = table;
+        this.nameColumn = nameColumn;
+        this.keyColumn = keyColumn;
+        this.valueColumn = valueColumn;
+        this.name = name;
+        doCommits = commits;
+        setLogger(LogFactory.getLog(getClass()));
+        addErrorLogListener();  // log errors per default
+    }
+
+    /**
+     * Build a configuration from a table.
+     *
+     * @param datasource    the datasource to connect to the database
+     * @param table         the name of the table containing the configurations
+     * @param keyColumn     the column containing the keys of the configuration
+     * @param valueColumn   the column containing the values of the configuration
+     */
+    public DatabaseConfiguration(DataSource datasource, String table, String keyColumn, String valueColumn)
+    {
+        this(datasource, table, null, keyColumn, valueColumn, null);
+    }
+
+    /**
+     * Creates a new instance of {@code DatabaseConfiguration} that
+     * operates on a database table containing a single configuration only.
+     *
+     * @param datasource the {@code DataSource} to connect to the database
+     * @param table the name of the table containing the configurations
+     * @param keyColumn the column containing the keys of the configuration
+     * @param valueColumn the column containing the values of the configuration
+     * @param commits a flag whether the configuration should perform a commit
+     *        after a database update
+     */
+    public DatabaseConfiguration(DataSource datasource, String table,
+            String keyColumn, String valueColumn, boolean commits)
+    {
+        this(datasource, table, null, keyColumn, valueColumn, null, commits);
+    }
+
+    /**
+     * Returns a flag whether this configuration performs commits after database
+     * updates.
+     *
+     * @return a flag whether commits are performed
+     */
+    public boolean isDoCommits()
+    {
+        return doCommits;
+    }
+
+    /**
+     * Returns the value of the specified property. If this causes a database
+     * error, an error event will be generated of type
+     * {@code EVENT_READ_PROPERTY} with the causing exception. The
+     * event's {@code propertyName} is set to the passed in property key,
+     * the {@code propertyValue} is undefined.
+     *
+     * @param key the key of the desired property
+     * @return the value of this property
+     */
+    public Object getProperty(String key)
+    {
+        Object result = null;
+
+        // build the query
+        StringBuilder query = new StringBuilder("SELECT * FROM ");
+        query.append(table).append(" WHERE ");
+        query.append(keyColumn).append("=?");
+        if (nameColumn != null)
+        {
+            query.append(" AND " + nameColumn + "=?");
+        }
+
+        Connection conn = null;
+        PreparedStatement pstmt = null;
+        ResultSet rs = null;
+
+        try
+        {
+            conn = getConnection();
+
+            // bind the parameters
+            pstmt = conn.prepareStatement(query.toString());
+            pstmt.setString(1, key);
+            if (nameColumn != null)
+            {
+                pstmt.setString(2, name);
+            }
+
+            rs = pstmt.executeQuery();
+
+            List<Object> results = new ArrayList<Object>();
+            while (rs.next())
+            {
+                Object value = rs.getObject(valueColumn);
+                if (isDelimiterParsingDisabled())
+                {
+                    results.add(value);
+                }
+                else
+                {
+                    // Split value if it contains the list delimiter
+                    Iterator<?> it = PropertyConverter.toIterator(value, getListDelimiter());
+                    while (it.hasNext())
+                    {
+                        results.add(it.next());
+                    }
+                }
+            }
+
+            if (!results.isEmpty())
+            {
+                result = (results.size() > 1) ? results : results.get(0);
+            }
+        }
+        catch (SQLException e)
+        {
+            fireError(EVENT_READ_PROPERTY, key, null, e);
+        }
+        finally
+        {
+            close(conn, pstmt, rs);
+        }
+
+        return result;
+    }
+
+    /**
+     * Adds a property to this configuration. If this causes a database error,
+     * an error event will be generated of type {@code EVENT_ADD_PROPERTY}
+     * with the causing exception. The event's {@code propertyName} is
+     * set to the passed in property key, the {@code propertyValue}
+     * points to the passed in value.
+     *
+     * @param key the property key
+     * @param obj the value of the property to add
+     */
+    @Override
+    protected void addPropertyDirect(String key, Object obj)
+    {
+        // build the query
+        StringBuilder query = new StringBuilder("INSERT INTO " + table);
+        if (nameColumn != null)
+        {
+            query.append(" (" + nameColumn + ", " + keyColumn + ", " + valueColumn + ") VALUES (?, ?, ?)");
+        }
+        else
+        {
+            query.append(" (" + keyColumn + ", " + valueColumn + ") VALUES (?, ?)");
+        }
+
+        Connection conn = null;
+        PreparedStatement pstmt = null;
+
+        try
+        {
+            conn = getConnection();
+
+            // bind the parameters
+            pstmt = conn.prepareStatement(query.toString());
+            int index = 1;
+            if (nameColumn != null)
+            {
+                pstmt.setString(index++, name);
+            }
+            pstmt.setString(index++, key);
+            pstmt.setString(index++, String.valueOf(obj));
+
+            pstmt.executeUpdate();
+            commitIfRequired(conn);
+        }
+        catch (SQLException e)
+        {
+            fireError(EVENT_ADD_PROPERTY, key, obj, e);
+        }
+        finally
+        {
+            // clean up
+            close(conn, pstmt, null);
+        }
+    }
+
+    /**
+     * Adds a property to this configuration. This implementation will
+     * temporarily disable list delimiter parsing, so that even if the value
+     * contains the list delimiter, only a single record will be written into
+     * the managed table. The implementation of {@code getProperty()}
+     * will take care about delimiters. So list delimiters are fully supported
+     * by {@code DatabaseConfiguration}, but internally treated a bit
+     * differently.
+     *
+     * @param key the key of the new property
+     * @param value the value to be added
+     */
+    @Override
+    public void addProperty(String key, Object value)
+    {
+        boolean parsingFlag = isDelimiterParsingDisabled();
+        try
+        {
+            if (value instanceof String)
+            {
+                // temporarily disable delimiter parsing
+                setDelimiterParsingDisabled(true);
+            }
+            super.addProperty(key, value);
+        }
+        finally
+        {
+            setDelimiterParsingDisabled(parsingFlag);
+        }
+    }
+
+    /**
+     * Checks if this configuration is empty. If this causes a database error,
+     * an error event will be generated of type {@code EVENT_READ_PROPERTY}
+     * with the causing exception. Both the event's {@code propertyName}
+     * and {@code propertyValue} will be undefined.
+     *
+     * @return a flag whether this configuration is empty.
+     */
+    public boolean isEmpty()
+    {
+        boolean empty = true;
+
+        // build the query
+        StringBuilder query = new StringBuilder("SELECT count(*) FROM " + table);
+        if (nameColumn != null)
+        {
+            query.append(" WHERE " + nameColumn + "=?");
+        }
+
+        Connection conn = null;
+        PreparedStatement pstmt = null;
+        ResultSet rs = null;
+
+        try
+        {
+            conn = getConnection();
+
+            // bind the parameters
+            pstmt = conn.prepareStatement(query.toString());
+            if (nameColumn != null)
+            {
+                pstmt.setString(1, name);
+            }
+
+            rs = pstmt.executeQuery();
+
+            if (rs.next())
+            {
+                empty = rs.getInt(1) == 0;
+            }
+        }
+        catch (SQLException e)
+        {
+            fireError(EVENT_READ_PROPERTY, null, null, e);
+        }
+        finally
+        {
+            // clean up
+            close(conn, pstmt, rs);
+        }
+
+        return empty;
+    }
+
+    /**
+     * Checks whether this configuration contains the specified key. If this
+     * causes a database error, an error event will be generated of type
+     * {@code EVENT_READ_PROPERTY} with the causing exception. The
+     * event's {@code propertyName} will be set to the passed in key, the
+     * {@code propertyValue} will be undefined.
+     *
+     * @param key the key to be checked
+     * @return a flag whether this key is defined
+     */
+    public boolean containsKey(String key)
+    {
+        boolean found = false;
+
+        // build the query
+        StringBuilder query = new StringBuilder("SELECT * FROM " + table + " WHERE " + keyColumn + "=?");
+        if (nameColumn != null)
+        {
+            query.append(" AND " + nameColumn + "=?");
+        }
+
+        Connection conn = null;
+        PreparedStatement pstmt = null;
+        ResultSet rs = null;
+
+        try
+        {
+            conn = getConnection();
+
+            // bind the parameters
+            pstmt = conn.prepareStatement(query.toString());
+            pstmt.setString(1, key);
+            if (nameColumn != null)
+            {
+                pstmt.setString(2, name);
+            }
+
+            rs = pstmt.executeQuery();
+
+            found = rs.next();
+        }
+        catch (SQLException e)
+        {
+            fireError(EVENT_READ_PROPERTY, key, null, e);
+        }
+        finally
+        {
+            // clean up
+            close(conn, pstmt, rs);
+        }
+
+        return found;
+    }
+
+    /**
+     * Removes the specified value from this configuration. If this causes a
+     * database error, an error event will be generated of type
+     * {@code EVENT_CLEAR_PROPERTY} with the causing exception. The
+     * event's {@code propertyName} will be set to the passed in key, the
+     * {@code propertyValue} will be undefined.
+     *
+     * @param key the key of the property to be removed
+     */
+    @Override
+    protected void clearPropertyDirect(String key)
+    {
+        // build the query
+        StringBuilder query = new StringBuilder("DELETE FROM " + table + " WHERE " + keyColumn + "=?");
+        if (nameColumn != null)
+        {
+            query.append(" AND " + nameColumn + "=?");
+        }
+
+        Connection conn = null;
+        PreparedStatement pstmt = null;
+
+        try
+        {
+            conn = getConnection();
+
+            // bind the parameters
+            pstmt = conn.prepareStatement(query.toString());
+            pstmt.setString(1, key);
+            if (nameColumn != null)
+            {
+                pstmt.setString(2, name);
+            }
+
+            pstmt.executeUpdate();
+            commitIfRequired(conn);
+        }
+        catch (SQLException e)
+        {
+            fireError(EVENT_CLEAR_PROPERTY, key, null, e);
+        }
+        finally
+        {
+            // clean up
+            close(conn, pstmt, null);
+        }
+    }
+
+    /**
+     * Removes all entries from this configuration. If this causes a database
+     * error, an error event will be generated of type
+     * {@code EVENT_CLEAR} with the causing exception. Both the
+     * event's {@code propertyName} and the {@code propertyValue}
+     * will be undefined.
+     */
+    @Override
+    public void clear()
+    {
+        fireEvent(EVENT_CLEAR, null, null, true);
+        // build the query
+        StringBuilder query = new StringBuilder("DELETE FROM " + table);
+        if (nameColumn != null)
+        {
+            query.append(" WHERE " + nameColumn + "=?");
+        }
+
+        Connection conn = null;
+        PreparedStatement pstmt = null;
+
+        try
+        {
+            conn = getConnection();
+
+            // bind the parameters
+            pstmt = conn.prepareStatement(query.toString());
+            if (nameColumn != null)
+            {
+                pstmt.setString(1, name);
+            }
+
+            pstmt.executeUpdate();
+            commitIfRequired(conn);
+        }
+        catch (SQLException e)
+        {
+            fireError(EVENT_CLEAR, null, null, e);
+        }
+        finally
+        {
+            // clean up
+            close(conn, pstmt, null);
+        }
+        fireEvent(EVENT_CLEAR, null, null, false);
+    }
+
+    /**
+     * Returns an iterator with the names of all properties contained in this
+     * configuration. If this causes a database
+     * error, an error event will be generated of type
+     * {@code EVENT_READ_PROPERTY} with the causing exception. Both the
+     * event's {@code propertyName} and the {@code propertyValue}
+     * will be undefined.
+     * @return an iterator with the contained keys (an empty iterator in case
+     * of an error)
+     */
+    public Iterator<String> getKeys()
+    {
+        Collection<String> keys = new ArrayList<String>();
+
+        // build the query
+        StringBuilder query = new StringBuilder("SELECT DISTINCT " + keyColumn + " FROM " + table);
+        if (nameColumn != null)
+        {
+            query.append(" WHERE " + nameColumn + "=?");
+        }
+
+        Connection conn = null;
+        PreparedStatement pstmt = null;
+        ResultSet rs = null;
+
+        try
+        {
+            conn = getConnection();
+
+            // bind the parameters
+            pstmt = conn.prepareStatement(query.toString());
+            if (nameColumn != null)
+            {
+                pstmt.setString(1, name);
+            }
+
+            rs = pstmt.executeQuery();
+
+            while (rs.next())
+            {
+                keys.add(rs.getString(1));
+            }
+        }
+        catch (SQLException e)
+        {
+            fireError(EVENT_READ_PROPERTY, null, null, e);
+        }
+        finally
+        {
+            // clean up
+            close(conn, pstmt, rs);
+        }
+
+        return keys.iterator();
+    }
+
+    /**
+     * Returns the used {@code DataSource} object.
+     *
+     * @return the data source
+     * @since 1.4
+     */
+    public DataSource getDatasource()
+    {
+        return datasource;
+    }
+
+    /**
+     * Returns a {@code Connection} object. This method is called when
+     * ever the database is to be accessed. This implementation returns a
+     * connection from the current {@code DataSource}.
+     *
+     * @return the {@code Connection} object to be used
+     * @throws SQLException if an error occurs
+     * @since 1.4
+     * @deprecated Use a custom data source to change the connection used by the
+     * class. To be removed in Commons Configuration 2.0
+     */
+    @Deprecated
+    protected Connection getConnection() throws SQLException
+    {
+        return getDatasource().getConnection();
+    }
+
+    /**
+     * Close the specified database objects.
+     * Avoid closing if null and hide any SQLExceptions that occur.
+     *
+     * @param conn The database connection to close
+     * @param stmt The statement to close
+     * @param rs the result set to close
+     */
+    private void close(Connection conn, Statement stmt, ResultSet rs)
+    {
+        try
+        {
+            if (rs != null)
+            {
+                rs.close();
+            }
+        }
+        catch (SQLException e)
+        {
+            getLogger().error("An error occurred on closing the result set", e);
+        }
+
+        try
+        {
+            if (stmt != null)
+            {
+                stmt.close();
+            }
+        }
+        catch (SQLException e)
+        {
+            getLogger().error("An error occured on closing the statement", e);
+        }
+
+        try
+        {
+            if (conn != null)
+            {
+                conn.close();
+            }
+        }
+        catch (SQLException e)
+        {
+            getLogger().error("An error occured on closing the connection", e);
+        }
+    }
+
+    /**
+     * Performs a commit if needed. This method is called after updates of the
+     * managed database table. If the configuration should perform commits, it
+     * does so now.
+     *
+     * @param conn the active connection
+     * @throws SQLException if an error occurs
+     */
+    private void commitIfRequired(Connection conn) throws SQLException
+    {
+        if (isDoCommits())
+        {
+            conn.commit();
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/DefaultConfigurationBuilder.java b/src/main/java/org/apache/commons/configuration/DefaultConfigurationBuilder.java
new file mode 100644
index 0000000..77e51c3
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/DefaultConfigurationBuilder.java
@@ -0,0 +1,1800 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import java.io.File;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.configuration.beanutils.BeanDeclaration;
+import org.apache.commons.configuration.beanutils.BeanFactory;
+import org.apache.commons.configuration.beanutils.BeanHelper;
+import org.apache.commons.configuration.beanutils.DefaultBeanFactory;
+import org.apache.commons.configuration.beanutils.XMLBeanDeclaration;
+import org.apache.commons.configuration.event.ConfigurationErrorListener;
+import org.apache.commons.configuration.event.ConfigurationListener;
+import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
+import org.apache.commons.configuration.resolver.CatalogResolver;
+import org.apache.commons.configuration.resolver.EntityRegistry;
+import org.apache.commons.configuration.resolver.EntityResolverSupport;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.DefaultExpressionEngine;
+import org.apache.commons.configuration.tree.OverrideCombiner;
+import org.apache.commons.configuration.tree.UnionCombiner;
+import org.apache.commons.lang.text.StrLookup;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.xml.sax.EntityResolver;
+
+/**
+ * <p>
+ * A factory class that creates a composite configuration from an XML based
+ * <em>configuration definition file</em>.
+ * </p>
+ * <p>
+ * This class provides an easy and flexible means for loading multiple
+ * configuration sources and combining the results into a single configuration
+ * object. The sources to be loaded are defined in an XML document that can
+ * contain certain tags representing the different supported configuration
+ * classes. If such a tag is found, the corresponding {@code Configuration}
+ * class is instantiated and initialized using the classes of the
+ * {@code beanutils} package (namely
+ * {@link org.apache.commons.configuration.beanutils.XMLBeanDeclaration XMLBeanDeclaration}
+ * will be used to extract the configuration's initialization parameters, which
+ * allows for complex initialization scenarios).
+ * </p>
+ * <p>
+ * It is also possible to add custom tags to the configuration definition file.
+ * For this purpose register your own {@code ConfigurationProvider}
+ * implementation for your tag using the {@code addConfigurationProvider()}
+ * method. This provider will then be called when the corresponding custom tag
+ * is detected. For the default configuration classes providers are already
+ * registered.
+ * </p>
+ * <p>
+ * The configuration definition file has the following basic structure:
+ * </p>
+ * <p>
+ *
+ * <pre>
+ * <configuration systemProperties="properties file name">
+ *   <header>
+ *     <!-- Optional meta information about the composite configuration -->
+ *   </header>
+ *   <override>
+ *     <!-- Declarations for override configurations -->
+ *   </override>
+ *   <additional>
+ *     <!-- Declarations for union configurations -->
+ *   </additional>
+ * </configuration>
+ * </pre>
+ *
+ * </p>
+ * <p>
+ * The name of the root element (here {@code configuration}) is
+ * arbitrary. The optional systemProperties attribute identifies the path to
+ * a property file containing properties that should be added to the system
+ * properties. If specified on the root element, the system properties are
+ * set before the rest of the configuration is processed.
+ * </p>
+ * <p>
+ * There are two sections (both of them are optional) for declaring
+ * <em>override</em> and <em>additional</em> configurations. Configurations
+ * in the former section are evaluated in the order of their declaration, and
+ * properties of configurations declared earlier hide those of configurations
+ * declared later. Configurations in the latter section are combined to a union
+ * configuration, i.e. all of their properties are added to a large hierarchical
+ * configuration. Configuration declarations that occur as direct children of
+ * the root element are treated as override declarations.
+ * </p>
+ * <p>
+ * Each configuration declaration consists of a tag whose name is associated
+ * with a {@code ConfigurationProvider}. This can be one of the
+ * predefined tags like {@code properties}, or {@code xml}, or
+ * a custom tag, for which a configuration provider was registered. Attributes
+ * and sub elements with specific initialization parameters can be added. There
+ * are some reserved attributes with a special meaning that can be used in every
+ * configuration declaration:
+ * </p>
+ * <p>
+ * <table border="1">
+ * <tr>
+ * <th>Attribute</th>
+ * <th>Meaning</th>
+ * </tr>
+ * <tr>
+ * <td valign="top">{@code config-name}</td>
+ * <td>Allows to specify a name for this configuration. This name can be used
+ * to obtain a reference to the configuration from the resulting combined
+ * configuration (see below).</td>
+ * </tr>
+ * <tr>
+ * <td valign="top">{@code config-at}</td>
+ * <td>With this attribute an optional prefix can be specified for the
+ * properties of the corresponding configuration.</td>
+ * </tr>
+ * <tr>
+ * <td valign="top">{@code config-optional}</td>
+ * <td>Declares a configuration as optional. This means that errors that occur
+ * when creating the configuration are ignored. (However
+ * {@link org.apache.commons.configuration.event.ConfigurationErrorListener}s
+ * registered at the builder instance will get notified about this error: they
+ * receive an event of type {@code EVENT_ERR_LOAD_OPTIONAL}. The key
+ * property of this event contains the name of the optional configuration source
+ * that caused this problem.)</td>
+ * </tr>
+ * </table>
+ * </p>
+ * <p>
+ * The optional <em>header</em> section can contain some meta data about the
+ * created configuration itself. For instance, it is possible to set further
+ * properties of the {@code NodeCombiner} objects used for constructing
+ * the resulting configuration.
+ * </p>
+ * <p>
+ * The default configuration object returned by this builder is an instance of the
+ * {@link CombinedConfiguration} class. The return value of the
+ * {@code getConfiguration()} method can be casted to this type, and the
+ * {@code getConfiguration(boolean)} method directly declares
+ * {@code CombinedConfiguration} as return type. This allows for
+ * convenient access to the configuration objects maintained by the combined
+ * configuration (e.g. for updates of single configuration objects). It has also
+ * the advantage that the properties stored in all declared configuration
+ * objects are collected and transformed into a single hierarchical structure,
+ * which can be accessed using different expression engines. The actual CombinedConfiguration
+ * implementation can be overridden by specifying the class in the <em>config-class</em>
+ * attribute of the result element.
+ * </p>
+ * <p>
+ * A custom EntityResolver can be used for all XMLConfigurations by adding
+ * <pre>
+ * <entity-resolver config-class="EntityResolver fully qualified class name">
+ * </pre>
+ * The CatalogResolver can be used for all XMLConfiguration by adding
+ * <pre>
+ * <entity-resolver catalogFiles="comma separated list of catalog files">
+ * </pre>
+ * </p>
+ * <p>
+ * Additional ConfigurationProviders can be added by configuring them in the <em>header</em>
+ * section.
+ * <pre>
+ * <providers>
+ *   <provider config-tag="tag name" config-class="provider fully qualified class name"/>
+ * </providers>
+ * </pre>
+ * </p>
+ * <p>
+ * Additional variable resolvers can be added by configuring them in the <em>header</em>
+ * section.
+ * <pre>
+ * <lookups>
+ *   <lookup config-prefix="prefix" config-class="StrLookup fully qualified class name"/>
+ * </lookups>
+ * </pre>
+ * </p>
+ * <p>
+ * All declared override configurations are directly added to the resulting
+ * combined configuration. If they are given names (using the
+ * {@code config-name} attribute), they can directly be accessed using
+ * the {@code getConfiguration(String)} method of
+ * {@code CombinedConfiguration}. The additional configurations are
+ * altogether added to another combined configuration, which uses a union
+ * combiner. Then this union configuration is added to the resulting combined
+ * configuration under the name defined by the {@code ADDITIONAL_NAME}
+ * constant.
+ * </p>
+ * <p>
+ * Implementation note: This class is not thread-safe. Especially the
+ * {@code getConfiguration()} methods should be called by a single thread
+ * only.
+ * </p>
+ *
+ * @since 1.3
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: DefaultConfigurationBuilder.java 1366930 2012-07-29 20:05:36Z oheger $
+ */
+public class DefaultConfigurationBuilder extends XMLConfiguration implements
+        ConfigurationBuilder
+{
+    /**
+     * Constant for the name of the additional configuration. If the
+     * configuration definition file contains an {@code additional}
+     * section, a special union configuration is created and added under this
+     * name to the resulting combined configuration.
+     */
+    public static final String ADDITIONAL_NAME = DefaultConfigurationBuilder.class
+            .getName()
+            + "/ADDITIONAL_CONFIG";
+
+    /**
+     * Constant for the type of error events caused by optional configurations
+     * that cannot be loaded.
+     */
+    public static final int EVENT_ERR_LOAD_OPTIONAL = 51;
+
+    /** Constant for the name of the configuration bean factory. */
+    static final String CONFIG_BEAN_FACTORY_NAME = DefaultConfigurationBuilder.class
+            .getName()
+            + ".CONFIG_BEAN_FACTORY_NAME";
+
+    /** Constant for the reserved name attribute. */
+    static final String ATTR_NAME = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
+            + XMLBeanDeclaration.RESERVED_PREFIX
+            + "name"
+            + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
+
+    /** Constant for the name of the at attribute. */
+    static final String ATTR_ATNAME = "at";
+
+    /** Constant for the reserved at attribute. */
+    static final String ATTR_AT_RES = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
+            + XMLBeanDeclaration.RESERVED_PREFIX
+            + ATTR_ATNAME
+            + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
+
+    /** Constant for the at attribute without the reserved prefix. */
+    static final String ATTR_AT = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
+            + ATTR_ATNAME + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
+
+    /** Constant for the name of the optional attribute. */
+    static final String ATTR_OPTIONALNAME = "optional";
+
+    /** Constant for the reserved optional attribute. */
+    static final String ATTR_OPTIONAL_RES = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
+            + XMLBeanDeclaration.RESERVED_PREFIX
+            + ATTR_OPTIONALNAME
+            + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
+
+    /** Constant for the optional attribute without the reserved prefix. */
+    static final String ATTR_OPTIONAL = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
+            + ATTR_OPTIONALNAME + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
+
+    /** Constant for the file name attribute. */
+    static final String ATTR_FILENAME = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
+            + "fileName" + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
+
+    /** Constant for the forceCreate attribute. */
+    static final String ATTR_FORCECREATE = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
+            + XMLBeanDeclaration.RESERVED_PREFIX
+            + "forceCreate"
+            + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
+
+    /**
+     * Constant for the tag attribute for providers.
+     */
+    static final String KEY_SYSTEM_PROPS = "[@systemProperties]";
+
+    /** Constant for the name of the header section. */
+    static final String SEC_HEADER = "header";
+
+    /** Constant for an expression that selects the union configurations. */
+    static final String KEY_UNION = "additional";
+
+    /** An array with the names of top level configuration sections.*/
+    static final String[] CONFIG_SECTIONS = {
+        "additional", "override", SEC_HEADER
+    };
+
+    /**
+     * Constant for an expression that selects override configurations in the
+     * override section.
+     */
+    static final String KEY_OVERRIDE = "override";
+
+    /**
+     * Constant for the key that points to the list nodes definition of the
+     * override combiner.
+     */
+    static final String KEY_OVERRIDE_LIST = SEC_HEADER
+            + ".combiner.override.list-nodes.node";
+
+    /**
+     * Constant for the key that points to the list nodes definition of the
+     * additional combiner.
+     */
+    static final String KEY_ADDITIONAL_LIST = SEC_HEADER
+            + ".combiner.additional.list-nodes.node";
+
+    /**
+     * Constant for the key for defining providers in the configuration file.
+     */
+    static final String KEY_CONFIGURATION_PROVIDERS = SEC_HEADER
+            + ".providers.provider";
+
+    /**
+     * Constant for the tag attribute for providers.
+     */
+    static final String KEY_PROVIDER_KEY = XMLBeanDeclaration.ATTR_PREFIX + "tag]";
+
+    /**
+     * Constant for the key for defining variable resolvers
+     */
+    static final String KEY_CONFIGURATION_LOOKUPS = SEC_HEADER
+            + ".lookups.lookup";
+
+    /**
+     * Constant for the key for defining entity resolvers
+     */
+    static final String KEY_ENTITY_RESOLVER = SEC_HEADER + ".entity-resolver";
+
+    /**
+     * Constant for the prefix attribute for lookups.
+     */
+    static final String KEY_LOOKUP_KEY = XMLBeanDeclaration.ATTR_PREFIX + "prefix]";
+
+    /**
+     * Constance for the FileSystem.
+     */
+    static final String FILE_SYSTEM = SEC_HEADER + ".fileSystem";
+
+    /**
+     * Constant for the key of the result declaration. This key can point to a
+     * bean declaration, which defines properties of the resulting combined
+     * configuration.
+     */
+    static final String KEY_RESULT = SEC_HEADER + ".result";
+
+    /** Constant for the key of the combiner in the result declaration.*/
+    static final String KEY_COMBINER = KEY_RESULT + ".nodeCombiner";
+
+    /** Constant for the XML file extension. */
+    static final String EXT_XML = ".xml";
+
+    /** Constant for the provider for properties files. */
+    private static final ConfigurationProvider PROPERTIES_PROVIDER = new FileExtensionConfigurationProvider(
+            XMLPropertiesConfiguration.class, PropertiesConfiguration.class,
+            EXT_XML);
+
+    /** Constant for the provider for XML files. */
+    private static final ConfigurationProvider XML_PROVIDER = new XMLConfigurationProvider();
+
+    /** Constant for the provider for JNDI sources. */
+    private static final ConfigurationProvider JNDI_PROVIDER = new ConfigurationProvider(
+            JNDIConfiguration.class);
+
+    /** Constant for the provider for system properties. */
+    private static final ConfigurationProvider SYSTEM_PROVIDER = new ConfigurationProvider(
+            SystemConfiguration.class);
+
+    /** Constant for the provider for ini files. */
+    private static final ConfigurationProvider INI_PROVIDER =
+            new FileConfigurationProvider(HierarchicalINIConfiguration.class);
+
+    /** Constant for the provider for environment properties. */
+    private static final ConfigurationProvider ENV_PROVIDER =
+            new ConfigurationProvider(EnvironmentConfiguration.class);
+
+    /** Constant for the provider for plist files. */
+    private static final ConfigurationProvider PLIST_PROVIDER = new FileExtensionConfigurationProvider(
+            "org.apache.commons.configuration.plist.XMLPropertyListConfiguration",
+            "org.apache.commons.configuration.plist.PropertyListConfiguration",
+            EXT_XML);
+
+    /** Constant for the provider for configuration definition files.*/
+    private static final ConfigurationProvider BUILDER_PROVIDER = new ConfigurationBuilderProvider();
+
+    /** An array with the names of the default tags. */
+    private static final String[] DEFAULT_TAGS = {
+            "properties", "xml", "hierarchicalXml", "jndi", "system", "plist",
+            "configuration", "ini", "env"
+    };
+
+    /** An array with the providers for the default tags. */
+    private static final ConfigurationProvider[] DEFAULT_PROVIDERS = {
+            PROPERTIES_PROVIDER, XML_PROVIDER, XML_PROVIDER, JNDI_PROVIDER,
+            SYSTEM_PROVIDER, PLIST_PROVIDER, BUILDER_PROVIDER, INI_PROVIDER,
+            ENV_PROVIDER
+    };
+
+    /**
+     * The serial version UID.
+     */
+    private static final long serialVersionUID = -3113777854714492123L;
+
+    /**
+     * A specialized {@code StrLookup} object which operates on the combined
+     * configuration constructed by this builder. This object is used as
+     * default lookup for {@code ConfigurationInterpolator} objects assigned to
+     * newly created configuration objects.
+     */
+    private final StrLookup combinedConfigLookup = new StrLookup()
+    {
+        @Override
+        public String lookup(String key)
+        {
+            if (constructedConfiguration != null)
+            {
+                Object value =
+                        constructedConfiguration.resolveContainerStore(key);
+                return (value != null) ? value.toString() : null;
+            }
+            return null;
+        }
+    };
+
+    /** Stores the configuration that is currently constructed.*/
+    private CombinedConfiguration constructedConfiguration;
+
+    /** Stores a map with the registered configuration providers. */
+    private final Map<String, ConfigurationProvider> providers;
+
+    /** Stores the base path to the configuration sources to load. */
+    private String configurationBasePath;
+
+    /**
+     * Creates a new instance of {@code DefaultConfigurationBuilder}. A
+     * configuration definition file is not yet loaded. Use the diverse setter
+     * methods provided by file based configurations to specify the
+     * configuration definition file.
+     */
+    public DefaultConfigurationBuilder()
+    {
+        super();
+        providers = new HashMap<String, ConfigurationProvider>();
+        registerDefaultProviders();
+        registerBeanFactory();
+        setLogger(LogFactory.getLog(getClass()));
+        addErrorLogListener();  // log errors per default
+    }
+
+    /**
+     * Creates a new instance of {@code DefaultConfigurationBuilder} and
+     * sets the specified configuration definition file.
+     *
+     * @param file the configuration definition file
+     */
+    public DefaultConfigurationBuilder(File file)
+    {
+        this();
+        setFile(file);
+    }
+
+    /**
+     * Creates a new instance of {@code DefaultConfigurationBuilder} and
+     * sets the specified configuration definition file.
+     *
+     * @param fileName the name of the configuration definition file
+     * @throws ConfigurationException if an error occurs when the file is loaded
+     */
+    public DefaultConfigurationBuilder(String fileName)
+            throws ConfigurationException
+    {
+        this();
+        setFileName(fileName);
+    }
+
+    /**
+     * Creates a new instance of {@code DefaultConfigurationBuilder} and
+     * sets the specified configuration definition file.
+     *
+     * @param url the URL to the configuration definition file
+     * @throws ConfigurationException if an error occurs when the file is loaded
+     */
+    public DefaultConfigurationBuilder(URL url) throws ConfigurationException
+    {
+        this();
+        setURL(url);
+    }
+
+    /**
+     * Returns the base path for the configuration sources to load. This path is
+     * used to resolve relative paths in the configuration definition file.
+     *
+     * @return the base path for configuration sources
+     */
+    public String getConfigurationBasePath()
+    {
+        return (configurationBasePath != null) ? configurationBasePath
+                : getBasePath();
+    }
+
+    /**
+     * Sets the base path for the configuration sources to load. Normally a base
+     * path need not to be set because it is determined by the location of the
+     * configuration definition file to load. All relative paths in this file
+     * are resolved relative to this file. Setting a base path makes sense if
+     * such relative paths should be otherwise resolved, e.g. if the
+     * configuration file is loaded from the class path and all sub
+     * configurations it refers to are stored in a special config directory.
+     *
+     * @param configurationBasePath the new base path to set
+     */
+    public void setConfigurationBasePath(String configurationBasePath)
+    {
+        this.configurationBasePath = configurationBasePath;
+    }
+
+    /**
+     * Adds a configuration provider for the specified tag. Whenever this tag is
+     * encountered in the configuration definition file this provider will be
+     * called to create the configuration object.
+     *
+     * @param tagName the name of the tag in the configuration definition file
+     * @param provider the provider for this tag
+     */
+    public void addConfigurationProvider(String tagName,
+            ConfigurationProvider provider)
+    {
+        if (tagName == null)
+        {
+            throw new IllegalArgumentException("Tag name must not be null!");
+        }
+        if (provider == null)
+        {
+            throw new IllegalArgumentException("Provider must not be null!");
+        }
+
+        providers.put(tagName, provider);
+    }
+
+    /**
+     * Removes the configuration provider for the specified tag name.
+     *
+     * @param tagName the tag name
+     * @return the removed configuration provider or <b>null</b> if none was
+     * registered for that tag
+     */
+    public ConfigurationProvider removeConfigurationProvider(String tagName)
+    {
+        return providers.remove(tagName);
+    }
+
+    /**
+     * Returns the configuration provider for the given tag.
+     *
+     * @param tagName the name of the tag
+     * @return the provider that was registered for this tag or <b>null</b> if
+     * there is none
+     */
+    public ConfigurationProvider providerForTag(String tagName)
+    {
+        return providers.get(tagName);
+    }
+
+    /**
+     * Returns the configuration provided by this builder. Loads and parses the
+     * configuration definition file and creates instances for the declared
+     * configurations.
+     *
+     * @return the configuration
+     * @throws ConfigurationException if an error occurs
+     */
+    public Configuration getConfiguration() throws ConfigurationException
+    {
+        return getConfiguration(true);
+    }
+
+    /**
+     * Returns the configuration provided by this builder. If the boolean
+     * parameter is <b>true</b>, the configuration definition file will be
+     * loaded. It will then be parsed, and instances for the declared
+     * configurations will be created.
+     *
+     * @param load a flag whether the configuration definition file should be
+     * loaded; a value of <b>false</b> would make sense if the file has already
+     * been created or its content was manipulated using some of the property
+     * accessor methods
+     * @return the configuration
+     * @throws ConfigurationException if an error occurs
+     */
+    public CombinedConfiguration getConfiguration(boolean load)
+            throws ConfigurationException
+    {
+        if (load)
+        {
+            load();
+        }
+
+        initFileSystem();
+        initSystemProperties();
+        configureEntityResolver();
+        registerConfiguredProviders();
+        registerConfiguredLookups();
+
+        CombinedConfiguration result = createResultConfiguration();
+        constructedConfiguration = result;
+
+        List<SubnodeConfiguration> overrides = fetchTopLevelOverrideConfigs();
+        overrides.addAll(fetchChildConfigs(KEY_OVERRIDE));
+        initCombinedConfiguration(result, overrides, KEY_OVERRIDE_LIST);
+
+        List<SubnodeConfiguration> additionals = fetchChildConfigs(KEY_UNION);
+        if (!additionals.isEmpty())
+        {
+            CombinedConfiguration addConfig = createAdditionalsConfiguration(result);
+            result.addConfiguration(addConfig, ADDITIONAL_NAME);
+            initCombinedConfiguration(addConfig, additionals,
+                    KEY_ADDITIONAL_LIST);
+        }
+
+        return result;
+    }
+
+    /**
+     * Creates the resulting combined configuration. This method is called by
+     * {@code getConfiguration()}. It checks whether the
+     * {@code header} section of the configuration definition file
+     * contains a {@code result} element. If this is the case, it will be
+     * used to initialize the properties of the newly created configuration
+     * object.
+     *
+     * @return the resulting configuration object
+     * @throws ConfigurationException if an error occurs
+     */
+    protected CombinedConfiguration createResultConfiguration()
+            throws ConfigurationException
+    {
+        XMLBeanDeclaration decl = new XMLBeanDeclaration(this, KEY_RESULT, true);
+        CombinedConfiguration result = (CombinedConfiguration) BeanHelper
+                .createBean(decl, CombinedConfiguration.class);
+
+        if (getMaxIndex(KEY_COMBINER) < 0)
+        {
+            // No combiner defined => set default
+            result.setNodeCombiner(new OverrideCombiner());
+        }
+
+        return result;
+    }
+
+    /**
+     * Creates the {@code CombinedConfiguration} for the configuration
+     * sources in the <code><additional></code> section. This method is
+     * called when the builder constructs the final configuration. It creates a
+     * new {@code CombinedConfiguration} and initializes some properties
+     * from the result configuration.
+     *
+     * @param resultConfig the result configuration (this is the configuration
+     *        that will be returned by the builder)
+     * @return the {@code CombinedConfiguration} for the additional
+     *         configuration sources
+     * @since 1.7
+     */
+    protected CombinedConfiguration createAdditionalsConfiguration(
+            CombinedConfiguration resultConfig)
+    {
+        CombinedConfiguration addConfig =
+                new CombinedConfiguration(new UnionCombiner());
+        addConfig.setDelimiterParsingDisabled(resultConfig
+                .isDelimiterParsingDisabled());
+        addConfig.setForceReloadCheck(resultConfig.isForceReloadCheck());
+        addConfig.setIgnoreReloadExceptions(resultConfig
+                .isIgnoreReloadExceptions());
+        return addConfig;
+    }
+
+    /**
+     * Initializes a combined configuration for the configurations of a specific
+     * section. This method is called for the override and for the additional
+     * section (if it exists).
+     *
+     * @param config the configuration to be initialized
+     * @param containedConfigs the list with the declarations of the contained
+     * configurations
+     * @param keyListNodes a list with the declaration of list nodes
+     * @throws ConfigurationException if an error occurs
+     */
+    protected void initCombinedConfiguration(CombinedConfiguration config,
+            List<? extends HierarchicalConfiguration> containedConfigs,
+            String keyListNodes) throws ConfigurationException
+    {
+        List<Object> listNodes = getList(keyListNodes);
+        for (Object listNode : listNodes)
+        {
+            config.getNodeCombiner().addListNode((String) listNode);
+        }
+
+        for (HierarchicalConfiguration conf : containedConfigs)
+        {
+            ConfigurationDeclaration decl = new ConfigurationDeclaration(this,
+                    conf);
+            if (getLogger().isDebugEnabled())
+            {
+                getLogger().debug("Creating configuration " + decl.getBeanClassName() + " with name "
+                    + decl.getConfiguration().getString(ATTR_NAME));
+            }
+            AbstractConfiguration newConf = createConfigurationAt(decl);
+            if (newConf != null)
+            {
+                config.addConfiguration(newConf, decl.getConfiguration()
+                        .getString(ATTR_NAME), decl.getAt());
+            }
+        }
+    }
+
+    /**
+     * Registers the default configuration providers supported by this class.
+     * This method will be called during initialization. It registers
+     * configuration providers for the tags that are supported by default.
+     */
+    protected void registerDefaultProviders()
+    {
+        for (int i = 0; i < DEFAULT_TAGS.length; i++)
+        {
+            addConfigurationProvider(DEFAULT_TAGS[i], DEFAULT_PROVIDERS[i]);
+        }
+    }
+
+    /**
+     * Registers providers defined in the configuration.
+     *
+     * @throws ConfigurationException if an error occurs
+     */
+    protected void registerConfiguredProviders() throws ConfigurationException
+    {
+        List<HierarchicalConfiguration> nodes = configurationsAt(KEY_CONFIGURATION_PROVIDERS);
+        for (HierarchicalConfiguration config : nodes)
+        {
+            XMLBeanDeclaration decl = new XMLBeanDeclaration(config);
+            String key = config.getString(KEY_PROVIDER_KEY);
+            addConfigurationProvider(key, (ConfigurationProvider) BeanHelper
+                    .createBean(decl));
+        }
+    }
+
+    /**
+     * Registers StrLookups defined in the configuration.
+     *
+     * @throws ConfigurationException if an error occurs
+     */
+    protected void registerConfiguredLookups() throws ConfigurationException
+    {
+        List<HierarchicalConfiguration> nodes = configurationsAt(KEY_CONFIGURATION_LOOKUPS);
+        for (HierarchicalConfiguration config : nodes)
+        {
+            XMLBeanDeclaration decl = new XMLBeanDeclaration(config);
+            String key = config.getString(KEY_LOOKUP_KEY);
+            StrLookup lookup = (StrLookup) BeanHelper.createBean(decl);
+            BeanHelper.setProperty(lookup, "configuration", this);
+            ConfigurationInterpolator.registerGlobalLookup(key, lookup);
+            this.getInterpolator().registerLookup(key, lookup);
+        }
+    }
+
+    protected void initFileSystem() throws ConfigurationException
+    {
+        if (getMaxIndex(FILE_SYSTEM) == 0)
+        {
+            HierarchicalConfiguration config = configurationAt(FILE_SYSTEM);
+            XMLBeanDeclaration decl = new XMLBeanDeclaration(config);
+            setFileSystem((FileSystem) BeanHelper.createBean(decl));
+        }
+    }
+
+    /**
+     * If a property file is configured add the properties to the System properties.
+     * @throws ConfigurationException if an error occurs.
+     */
+    protected void initSystemProperties() throws ConfigurationException
+    {
+        String fileName = getString(KEY_SYSTEM_PROPS);
+        if (fileName != null)
+        {
+            try
+            {
+               SystemConfiguration.setSystemProperties(getConfigurationBasePath(), fileName);
+            }
+            catch (Exception ex)
+            {
+                throw new ConfigurationException("Error setting system properties from " + fileName, ex);
+            }
+
+        }
+    }
+
+    protected void configureEntityResolver() throws ConfigurationException
+    {
+        if (getMaxIndex(KEY_ENTITY_RESOLVER) == 0)
+        {
+            XMLBeanDeclaration decl = new XMLBeanDeclaration(this, KEY_ENTITY_RESOLVER, true);
+            EntityResolver resolver = (EntityResolver) BeanHelper.createBean(decl, CatalogResolver.class);
+            BeanHelper.setProperty(resolver, "fileSystem", getFileSystem());
+            BeanHelper.setProperty(resolver, "baseDir", getBasePath());
+            BeanHelper.setProperty(resolver, "substitutor", getSubstitutor());
+            setEntityResolver(resolver);
+        }
+    }
+
+    /**
+     * Performs interpolation. This method will not only take this configuration
+     * instance into account (which is the one that loaded the configuration
+     * definition file), but also the so far constructed combined configuration.
+     * So variables can be used that point to properties that are defined in
+     * configuration sources loaded by this builder.
+     *
+     * @param value the value to be interpolated
+     * @return the interpolated value
+     */
+    @Override
+    protected Object interpolate(Object value)
+    {
+        Object result = super.interpolate(value);
+        if (constructedConfiguration != null)
+        {
+            result = constructedConfiguration.interpolate(result);
+        }
+        return result;
+    }
+
+    /**
+     * Creates a configuration object from the specified configuration
+     * declaration.
+     *
+     * @param decl the configuration declaration
+     * @return the new configuration object
+     * @throws ConfigurationException if an error occurs
+     */
+    private AbstractConfiguration createConfigurationAt(
+            ConfigurationDeclaration decl) throws ConfigurationException
+    {
+        try
+        {
+            return (AbstractConfiguration) BeanHelper.createBean(decl);
+        }
+        catch (Exception ex)
+        {
+            // redirect to configuration exceptions
+            throw new ConfigurationException(ex);
+        }
+    }
+
+    /**
+     * Returns a list with {@code SubnodeConfiguration} objects for the
+     * child nodes of the specified configuration node.
+     *
+     * @param node the start node
+     * @return a list with subnode configurations for the node's children
+     */
+    private List<SubnodeConfiguration> fetchChildConfigs(ConfigurationNode node)
+    {
+        List<ConfigurationNode> children = node.getChildren();
+        List<SubnodeConfiguration> result = new ArrayList<SubnodeConfiguration>(children.size());
+        for (ConfigurationNode child : children)
+        {
+            result.add(createSubnodeConfiguration(child));
+        }
+        return result;
+    }
+
+    /**
+     * Returns a list with {@code SubnodeConfiguration} objects for the
+     * child nodes of the node specified by the given key.
+     *
+     * @param key the key (must define exactly one node)
+     * @return a list with subnode configurations for the node's children
+     */
+    private List<SubnodeConfiguration> fetchChildConfigs(String key)
+    {
+        List<ConfigurationNode> nodes = fetchNodeList(key);
+        if (nodes.size() > 0)
+        {
+            return fetchChildConfigs(nodes.get(0));
+        }
+        else
+        {
+            return Collections.emptyList();
+        }
+    }
+
+    /**
+     * Finds the override configurations that are defined as top level elements
+     * in the configuration definition file. This method will fetch the child
+     * elements of the root node and remove the nodes that represent other
+     * configuration sections. The remaining nodes are treated as definitions
+     * for override configurations.
+     *
+     * @return a list with subnode configurations for the top level override
+     * configurations
+     */
+    private List<SubnodeConfiguration> fetchTopLevelOverrideConfigs()
+    {
+        List<SubnodeConfiguration> configs = fetchChildConfigs(getRootNode());
+        for (Iterator<SubnodeConfiguration> it = configs.iterator(); it.hasNext();)
+        {
+            String nodeName = it.next().getRootNode().getName();
+            for (int i = 0; i < CONFIG_SECTIONS.length; i++)
+            {
+                if (CONFIG_SECTIONS[i].equals(nodeName))
+                {
+                    it.remove();
+                    break;
+                }
+            }
+        }
+        return configs;
+    }
+
+    /**
+     * Registers the bean factory used by this class if necessary. This method
+     * is called by the constructor to ensure that the required bean factory is
+     * available.
+     */
+    private void registerBeanFactory()
+    {
+        synchronized (DefaultConfigurationBuilder.class)
+        {
+            if (!BeanHelper.registeredFactoryNames().contains(
+                    CONFIG_BEAN_FACTORY_NAME))
+            {
+                BeanHelper.registerBeanFactory(CONFIG_BEAN_FACTORY_NAME,
+                        new ConfigurationBeanFactory());
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * A base class for creating and initializing configuration sources.
+     * </p>
+     * <p>
+     * Concrete sub classes of this base class are responsible for creating
+     * specific {@code Configuration} objects for the tags in the
+     * configuration definition file. The configuration factory will parse the
+     * definition file and try to find a matching
+     * {@code ConfigurationProvider} for each encountered tag. This
+     * provider is then asked to create a corresponding
+     * {@code Configuration} object. It is up to a concrete
+     * implementation how this object is created and initialized.
+     * </p>
+     * <p>
+     * Note that at the moment only configuration classes derived from
+     * {@link AbstractConfiguration} are supported.
+     * </p>
+     */
+    public static class ConfigurationProvider extends DefaultBeanFactory
+    {
+        /** Stores the class of the configuration to be created. */
+        private Class<?> configurationClass;
+
+        /** Stores the name of the configuration class to be created.*/
+        private String configurationClassName;
+
+        /**
+         * Creates a new uninitialized instance of {@code ConfigurationProvider}.
+         */
+        public ConfigurationProvider()
+        {
+            this((Class<?>) null);
+        }
+
+        /**
+         * Creates a new instance of {@code ConfigurationProvider} and
+         * sets the class of the configuration created by this provider.
+         *
+         * @param configClass the configuration class
+         */
+        public ConfigurationProvider(Class<?> configClass)
+        {
+            setConfigurationClass(configClass);
+        }
+
+        /**
+         * Creates a new instance of {@code ConfigurationProvider} and
+         * sets the name of the class of the configuration created by this
+         * provider.
+         *
+         * @param configClassName the name of the configuration class
+         * @since 1.4
+         */
+        public ConfigurationProvider(String configClassName)
+        {
+            setConfigurationClassName(configClassName);
+        }
+
+        /**
+         * Returns the class of the configuration returned by this provider.
+         *
+         * @return the class of the provided configuration
+         */
+        public Class<?> getConfigurationClass()
+        {
+            return configurationClass;
+        }
+
+        /**
+         * Sets the class of the configuration returned by this provider.
+         *
+         * @param configurationClass the configuration class
+         */
+        public void setConfigurationClass(Class<?> configurationClass)
+        {
+            this.configurationClass = configurationClass;
+        }
+
+        /**
+         * Returns the name of the configuration class returned by this
+         * provider.
+         *
+         * @return the configuration class name
+         * @since 1.4
+         */
+        public String getConfigurationClassName()
+        {
+            return configurationClassName;
+        }
+
+        /**
+         * Sets the name of the configuration class returned by this provider.
+         *
+         * @param configurationClassName the name of the configuration class
+         * @since 1.4
+         */
+        public void setConfigurationClassName(String configurationClassName)
+        {
+            this.configurationClassName = configurationClassName;
+        }
+
+        /**
+         * Returns the configuration. This method is called to fetch the
+         * configuration from the provider. This implementation will call the
+         * inherited {@link
+         * org.apache.commons.configuration.beanutils.DefaultBeanFactory#createBean(Class, BeanDeclaration, Object)
+         * createBean()} method to create a new instance of the
+         * configuration class.
+         *
+         * @param decl the bean declaration with initialization parameters for
+         * the configuration
+         * @return the new configuration object
+         * @throws Exception if an error occurs
+         */
+        public AbstractConfiguration getConfiguration(
+                ConfigurationDeclaration decl) throws Exception
+        {
+            return (AbstractConfiguration) createBean(fetchConfigurationClass(),
+                    decl, null);
+        }
+
+        /**
+         * Returns an uninitialized configuration of the represented type. This
+         * method will be called for optional configurations when the
+         * {@code getConfiguration()} method caused an error and the
+         * {@code forceCreate} attribute is set. A concrete sub class can
+         * here try to create an uninitialized, empty configuration, which may
+         * be possible if the error was created during initialization. This base
+         * implementation just returns <b>null</b>.
+         *
+         * @param decl the bean declaration with initialization parameters for
+         * the configuration
+         * @return the new configuration object
+         * @throws Exception if an error occurs
+         * @since 1.4
+         */
+        public AbstractConfiguration getEmptyConfiguration(
+                ConfigurationDeclaration decl) throws Exception
+        {
+            return null;
+        }
+
+        /**
+         * Returns the configuration class supported by this provider. If a
+         * class object was set, it is returned. Otherwise the method tries to
+         * resolve the class name.
+         *
+         * @return the class of the configuration to be created
+         * @since 1.4
+         */
+        protected synchronized Class<?> fetchConfigurationClass() throws Exception
+        {
+            if (getConfigurationClass() == null)
+            {
+                setConfigurationClass(loadClass(getConfigurationClassName()));
+            }
+            return getConfigurationClass();
+        }
+
+        /**
+         * Loads the class with the specified name dynamically. If the class's
+         * name is <b>null</b>, <b>null</b> will also be returned.
+         *
+         * @param className the name of the class to be loaded
+         * @return the class object
+         * @throws ClassNotFoundException if class loading fails
+         * @since 1.4
+         */
+        protected Class<?> loadClass(String className)
+                throws ClassNotFoundException
+        {
+            return (className != null) ? Class.forName(className, true,
+                    getClass().getClassLoader()) : null;
+        }
+    }
+
+    /**
+     * <p>
+     * A specialized {@code BeanDeclaration} implementation that
+     * represents the declaration of a configuration source.
+     * </p>
+     * <p>
+     * Instances of this class are able to extract all information about a
+     * configuration source from the configuration definition file. The
+     * declaration of a configuration source is very similar to a bean
+     * declaration processed by {@code XMLBeanDeclaration}. There are
+     * very few differences, e.g. some reserved attributes like
+     * {@code optional} and {@code at} and the fact that a bean
+     * factory is never needed.
+     * </p>
+     */
+    public static class ConfigurationDeclaration extends XMLBeanDeclaration
+    {
+        /** Stores a reference to the associated configuration builder. */
+        private DefaultConfigurationBuilder configurationBuilder;
+
+        /**
+         * Creates a new instance of {@code ConfigurationDeclaration} and
+         * initializes it.
+         *
+         * @param builder the associated configuration builder
+         * @param config the configuration this declaration is based onto
+         */
+        public ConfigurationDeclaration(DefaultConfigurationBuilder builder,
+                HierarchicalConfiguration config)
+        {
+            super(config);
+            configurationBuilder = builder;
+        }
+
+        /**
+         * Returns the associated configuration builder.
+         *
+         * @return the configuration builder
+         */
+        public DefaultConfigurationBuilder getConfigurationBuilder()
+        {
+            return configurationBuilder;
+        }
+
+        /**
+         * Returns the value of the {@code at} attribute.
+         *
+         * @return the value of the {@code at} attribute (can be <b>null</b>)
+         */
+        public String getAt()
+        {
+            String result = this.getConfiguration().getString(ATTR_AT_RES);
+            return (result == null) ? this.getConfiguration().getString(ATTR_AT)
+                    : result;
+        }
+
+        /**
+         * Returns a flag whether this is an optional configuration.
+         *
+         * @return a flag if this declaration points to an optional
+         * configuration
+         */
+        public boolean isOptional()
+        {
+            Boolean value = this.getConfiguration().getBoolean(ATTR_OPTIONAL_RES,
+                    null);
+            if (value == null)
+            {
+                value = this.getConfiguration().getBoolean(ATTR_OPTIONAL,
+                        Boolean.FALSE);
+            }
+            return value.booleanValue();
+        }
+
+        /**
+         * Returns a flag whether this configuration should always be created
+         * and added to the resulting combined configuration. This flag is
+         * evaluated only for optional configurations whose normal creation has
+         * caused an error. If for such a configuration the
+         * {@code forceCreate} attribute is set and the corresponding
+         * configuration provider supports this mode, an empty configuration
+         * will be created and added to the resulting combined configuration.
+         *
+         * @return the value of the {@code forceCreate} attribute
+         * @since 1.4
+         */
+        public boolean isForceCreate()
+        {
+            return this.getConfiguration().getBoolean(ATTR_FORCECREATE, false);
+        }
+
+        /**
+         * Returns the name of the bean factory. For configuration source
+         * declarations always a reserved factory is used. This factory's name
+         * is returned by this implementation.
+         *
+         * @return the name of the bean factory
+         */
+        @Override
+        public String getBeanFactoryName()
+        {
+            return CONFIG_BEAN_FACTORY_NAME;
+        }
+
+        /**
+         * Returns the bean's class name. This implementation will always return
+         * <b>null</b>.
+         *
+         * @return the name of the bean's class
+         */
+        @Override
+        public String getBeanClassName()
+        {
+            return null;
+        }
+
+        /**
+         * Checks whether the given node is reserved. This method will take
+         * further reserved attributes into account
+         *
+         * @param nd the node
+         * @return a flag whether this node is reserved
+         */
+        @Override
+        protected boolean isReservedNode(ConfigurationNode nd)
+        {
+            if (super.isReservedNode(nd))
+            {
+                return true;
+            }
+
+            return nd.isAttribute()
+                    && ((ATTR_ATNAME.equals(nd.getName()) && nd.getParentNode()
+                            .getAttributeCount(RESERVED_PREFIX + ATTR_ATNAME) == 0) || (ATTR_OPTIONALNAME
+                            .equals(nd.getName()) && nd.getParentNode()
+                            .getAttributeCount(RESERVED_PREFIX + ATTR_OPTIONALNAME) == 0));
+        }
+
+        /**
+         * Performs interpolation. This implementation will delegate
+         * interpolation to the configuration builder, which takes care that the
+         * currently constructed configuration is taken into account, too.
+         *
+         * @param value the value to be interpolated
+         * @return the interpolated value
+         */
+        @Override
+        protected Object interpolate(Object value)
+        {
+            return getConfigurationBuilder().interpolate(value);
+        }
+    }
+
+    /**
+     * A specialized {@code BeanFactory} implementation that handles
+     * configuration declarations. This class will retrieve the correct
+     * configuration provider and delegate the task of creating the
+     * configuration to this object.
+     */
+    static class ConfigurationBeanFactory implements BeanFactory
+    {
+        /** The logger. */
+        private Log logger = LogFactory.getLog(DefaultConfigurationBuilder.class);
+
+        /**
+         * Creates an instance of a bean class. This implementation expects that
+         * the passed in bean declaration is a declaration for a configuration.
+         * It will determine the responsible configuration provider and delegate
+         * the call to this instance. If creation of the configuration fails
+         * and the {@code optional} attribute is set, the exception will
+         * be ignored. If the {@code forceCreate} attribute is set, too,
+         * the provider is asked to create an empty configuration. A return
+         * value of <b>null</b> means that no configuration could be created.
+         *
+         * @param beanClass the bean class (will be ignored)
+         * @param data the declaration
+         * @param param an additional parameter (will be ignored)
+         * @return the newly created configuration
+         * @throws Exception if an error occurs
+         */
+        public Object createBean(Class<?> beanClass, BeanDeclaration data,
+                Object param) throws Exception
+        {
+            ConfigurationDeclaration decl = (ConfigurationDeclaration) data;
+            String tagName = decl.getNode().getName();
+            ConfigurationProvider provider = decl.getConfigurationBuilder()
+                    .providerForTag(tagName);
+            if (provider == null)
+            {
+                throw new ConfigurationRuntimeException(
+                        "No ConfigurationProvider registered for tag "
+                                + tagName);
+            }
+
+            try
+            {
+                AbstractConfiguration config = provider.getConfiguration(decl);
+                installInterpolator(decl, config);
+                return config;
+            }
+            catch (Exception ex)
+            {
+                // If this is an optional configuration, ignore the exception
+                if (!decl.isOptional())
+                {
+                    throw ex;
+                }
+                else
+                {
+                    if (logger.isDebugEnabled())
+                    {
+                        logger.debug("Load failed for optional configuration " + tagName + ": "
+                            + ex.getMessage());
+                    }
+                    // Notify registered error listeners
+                    decl.getConfigurationBuilder().fireError(
+                            EVENT_ERR_LOAD_OPTIONAL,
+                            decl.getConfiguration().getString(ATTR_NAME), null,
+                            ex);
+
+                    if (decl.isForceCreate())
+                    {
+                        try
+                        {
+                            return provider.getEmptyConfiguration(decl);
+                        }
+                        catch (Exception ex2)
+                        {
+                            // Ignore exception, return null in this case
+                            logger.warn("Could not create instance of optional configuration "
+                                    + tagName, ex2);
+                        }
+                    }
+                    return null;
+                }
+            }
+        }
+
+        /**
+         * Returns the default class for this bean factory.
+         *
+         * @return the default class
+         */
+        public Class<?> getDefaultBeanClass()
+        {
+            // Here some valid class must be returned, otherwise BeanHelper
+            // will complain that the bean's class cannot be determined
+            return Configuration.class;
+        }
+
+        /**
+         * Installs a specialized {@code ConfigurationInterpolator} on a newly
+         * created configuration which also takes the combined configuration
+         * created by the builder into account. With this
+         * {@code ConfigurationInterpolator} the interpolation facilities of
+         * this child configuration are extended to include all other
+         * configurations created by this builder.
+         *
+         * @param decl the {@code ConfigurationDeclaration}
+         * @param config the newly created configuration instance
+         */
+        private void installInterpolator(ConfigurationDeclaration decl,
+                AbstractConfiguration config)
+        {
+            ConfigurationInterpolator parent = new ConfigurationInterpolator();
+            parent.setDefaultLookup(decl.getConfigurationBuilder().combinedConfigLookup);
+            config.getInterpolator().setParentInterpolator(parent);
+        }
+    }
+
+    /**
+     * A specialized provider implementation that deals with file based
+     * configurations. Ensures that the base path is correctly set and that the
+     * load() method gets called.
+     */
+    public static class FileConfigurationProvider extends ConfigurationProvider
+    {
+        /**
+         * Creates a new instance of {@code FileConfigurationProvider}.
+         */
+        public FileConfigurationProvider()
+        {
+            super();
+        }
+
+        /**
+         * Creates a new instance of {@code FileConfigurationProvider}
+         * and sets the configuration class.
+         *
+         * @param configClass the class for the configurations to be created
+         */
+        public FileConfigurationProvider(Class<?> configClass)
+        {
+            super(configClass);
+        }
+
+        /**
+         * Creates a new instance of {@code FileConfigurationProvider}
+         * and sets the configuration class name.
+         *
+         * @param configClassName the name of the configuration to be created
+         * @since 1.4
+         */
+        public FileConfigurationProvider(String configClassName)
+        {
+            super(configClassName);
+        }
+
+        /**
+         * Creates the configuration. After that {@code load()} will be
+         * called. If this configuration is marked as optional, exceptions will
+         * be ignored.
+         *
+         * @param decl the declaration
+         * @return the new configuration
+         * @throws Exception if an error occurs
+         */
+        @Override
+        public AbstractConfiguration getConfiguration(
+                ConfigurationDeclaration decl) throws Exception
+        {
+            AbstractConfiguration result = getEmptyConfiguration(decl);
+            if (result instanceof FileSystemBased)
+            {
+                DefaultConfigurationBuilder builder = decl.getConfigurationBuilder();
+                if (builder.getFileSystem() != null)
+                {
+                    ((FileSystemBased) result).setFileSystem(builder.getFileSystem());
+                }
+            }
+            ((FileConfiguration) result).load();
+            return result;
+        }
+
+        /**
+         * Returns an uninitialized file configuration. This method will be
+         * called for optional configurations when the
+         * {@code getConfiguration()} method caused an error and the
+         * {@code forceCreate} attribute is set. It will create the
+         * configuration of the represented type, but the {@code load()}
+         * method won't be called. This way non-existing configuration files can
+         * be handled gracefully: If loading a the file fails, an empty
+         * configuration will be created that is already configured with the
+         * correct file name.
+         *
+         * @param decl the bean declaration with initialization parameters for
+         * the configuration
+         * @return the new configuration object
+         * @throws Exception if an error occurs
+         * @since 1.4
+         */
+        @Override
+        public AbstractConfiguration getEmptyConfiguration(
+                ConfigurationDeclaration decl) throws Exception
+        {
+            AbstractConfiguration config = super.getConfiguration(decl);
+
+            /**
+             * Some wrapper classes may need to pass the EntityResolver to XMLConfigurations
+             * they construct buy may not be an XMLConfiguration.
+             */
+            if (config instanceof EntityResolverSupport)
+            {
+                DefaultConfigurationBuilder builder = decl.getConfigurationBuilder();
+                EntityResolver resolver = builder.getEntityResolver();
+                ((EntityResolverSupport) config).setEntityResolver(resolver);
+            }
+
+            return config;
+        }
+
+        /**
+         * Initializes the bean instance. Ensures that the file configuration's
+         * base path will be initialized with the base path of the factory so
+         * that relative path names can be correctly resolved.
+         *
+         * @param bean the bean to be initialized
+         * @param data the declaration
+         * @throws Exception if an error occurs
+         */
+        @Override
+        protected void initBeanInstance(Object bean, BeanDeclaration data)
+                throws Exception
+        {
+            FileConfiguration config = (FileConfiguration) bean;
+            config.setBasePath(((ConfigurationDeclaration) data)
+                    .getConfigurationBuilder().getConfigurationBasePath());
+            super.initBeanInstance(bean, data);
+        }
+    }
+
+    /**
+     * A specialized configuration provider for XML configurations. This
+     * implementation acts like a {@code FileConfigurationProvider}, but
+     * it will copy all entity IDs that have been registered for the
+     * configuration builder to the new XML configuration before it is loaded.
+     *
+     * @since 1.6
+     */
+    public static class XMLConfigurationProvider extends FileConfigurationProvider
+    {
+        /**
+         * Creates a new instance of {@code XMLConfigurationProvider}.
+         */
+        public XMLConfigurationProvider()
+        {
+            super(XMLConfiguration.class);
+        }
+
+        /**
+         * Returns a new empty configuration instance. This implementation
+         * performs some additional initialization specific to XML
+         * configurations.
+         *
+         * @param decl the configuration declaration
+         * @return the new configuration
+         * @throws Exception if an error occurs
+         */
+        @Override
+        public AbstractConfiguration getEmptyConfiguration(
+                ConfigurationDeclaration decl) throws Exception
+        {
+            XMLConfiguration config = (XMLConfiguration) super
+                    .getEmptyConfiguration(decl);
+
+            DefaultConfigurationBuilder builder = decl
+                    .getConfigurationBuilder();
+            EntityResolver resolver = builder.getEntityResolver();
+            if (resolver instanceof EntityRegistry)
+            {
+                // copy the registered entities
+                config.getRegisteredEntities().putAll(
+                    builder.getRegisteredEntities());
+            }
+            else
+            {
+                config.setEntityResolver(resolver);
+            }
+            return config;
+        }
+    }
+
+    /**
+     * A specialized configuration provider for file based configurations that
+     * can handle configuration sources whose concrete type depends on the
+     * extension of the file to be loaded. One example is the
+     * {@code properties} tag: if the file ends with ".xml" a
+     * XMLPropertiesConfiguration object must be created, otherwise a
+     * PropertiesConfiguration object.
+     */
+    static class FileExtensionConfigurationProvider extends
+            FileConfigurationProvider
+    {
+        /**
+         * Stores the class to be created when the file extension matches.
+         */
+        private Class<?> matchingClass;
+
+        /**
+         * Stores the name of the class to be created when the file extension
+         * matches.
+         */
+        private String matchingClassName;
+
+        /**
+         * Stores the class to be created when the file extension does not
+         * match.
+         */
+        private Class<?> defaultClass;
+
+        /**
+         * Stores the name of the class to be created when the file extension
+         * does not match.
+         */
+        private String defaultClassName;
+
+        /** Stores the file extension to be checked against. */
+        private String fileExtension;
+
+        /**
+         * Creates a new instance of
+         * {@code FileExtensionConfigurationProvider} and initializes it.
+         *
+         * @param matchingClass the class to be created when the file extension
+         * matches
+         * @param defaultClass the class to be created when the file extension
+         * does not match
+         * @param extension the file extension to be checked against
+         */
+        public FileExtensionConfigurationProvider(Class<?> matchingClass,
+                Class<?> defaultClass, String extension)
+        {
+            this.matchingClass = matchingClass;
+            this.defaultClass = defaultClass;
+            fileExtension = extension;
+        }
+
+        /**
+         * Creates a new instance of
+         * {@code FileExtensionConfigurationProvider} and initializes it
+         * with the names of the classes to be created.
+         *
+         * @param matchingClassName the name of the class to be created when the
+         * file extension matches
+         * @param defaultClassName the name of the class to be created when the
+         * file extension does not match
+         * @param extension the file extension to be checked against
+         * @since 1.4
+         */
+        public FileExtensionConfigurationProvider(String matchingClassName,
+                String defaultClassName, String extension)
+        {
+            this.matchingClassName = matchingClassName;
+            this.defaultClassName = defaultClassName;
+            fileExtension = extension;
+        }
+
+        /**
+         * Returns the matching class object, no matter whether it was defined
+         * as a class or as a class name.
+         *
+         * @return the matching class object
+         * @throws Exception if an error occurs
+         * @since 1.4
+         */
+        protected synchronized Class<?> fetchMatchingClass() throws Exception
+        {
+            if (matchingClass == null)
+            {
+                matchingClass = loadClass(matchingClassName);
+            }
+            return matchingClass;
+        }
+
+        /**
+         * Returns the default class object, no matter whether it was defined as
+         * a class or as a class name.
+         *
+         * @return the default class object
+         * @throws Exception if an error occurs
+         * @since 1.4
+         */
+        protected synchronized Class<?> fetchDefaultClass() throws Exception
+        {
+            if (defaultClass == null)
+            {
+                defaultClass = loadClass(defaultClassName);
+            }
+            return defaultClass;
+        }
+
+        /**
+         * Creates the configuration object. The class is determined by the file
+         * name's extension.
+         *
+         * @param beanClass the class
+         * @param data the bean declaration
+         * @return the new bean
+         * @throws Exception if an error occurs
+         */
+        @Override
+        protected Object createBeanInstance(Class<?> beanClass,
+                BeanDeclaration data) throws Exception
+        {
+            String fileName = ((ConfigurationDeclaration) data)
+                    .getConfiguration().getString(ATTR_FILENAME);
+            if (fileName != null
+                    && fileName.toLowerCase().trim().endsWith(fileExtension))
+            {
+                return super.createBeanInstance(fetchMatchingClass(), data);
+            }
+            else
+            {
+                return super.createBeanInstance(fetchDefaultClass(), data);
+            }
+        }
+    }
+
+    /**
+     * A specialized configuration provider class that allows to include other
+     * configuration definition files.
+     */
+    static class ConfigurationBuilderProvider extends ConfigurationProvider
+    {
+        /**
+         * Creates a new instance of {@code ConfigurationBuilderProvider}.
+         */
+        public ConfigurationBuilderProvider()
+        {
+            super(DefaultConfigurationBuilder.class);
+        }
+
+        /**
+         * Creates the configuration. First creates a configuration builder
+         * object. Then returns the configuration created by this builder.
+         *
+         * @param decl the configuration declaration
+         * @return the configuration
+         * @exception Exception if an error occurs
+         */
+        @Override
+        public AbstractConfiguration getConfiguration(
+                ConfigurationDeclaration decl) throws Exception
+        {
+            DefaultConfigurationBuilder builder = (DefaultConfigurationBuilder) super
+                    .getConfiguration(decl);
+            return builder.getConfiguration(true);
+        }
+
+        /**
+         * Returns an empty configuration in case of an optional configuration
+         * could not be created. This implementation returns an empty combined
+         * configuration.
+         *
+         * @param decl the configuration declaration
+         * @return the configuration
+         * @exception Exception if an error occurs
+         * @since 1.4
+         */
+        @Override
+        public AbstractConfiguration getEmptyConfiguration(
+                ConfigurationDeclaration decl) throws Exception
+        {
+            return new CombinedConfiguration();
+        }
+
+        /**
+         * {@inheritDoc} This implementation ensures that the configuration
+         * builder created by this provider inherits the properties from the
+         * current configuration builder.
+         */
+        @Override
+        protected void initBeanInstance(Object bean, BeanDeclaration data)
+                throws Exception
+        {
+            ConfigurationDeclaration decl = (ConfigurationDeclaration) data;
+            initChildBuilder(decl.getConfigurationBuilder(),
+                    (DefaultConfigurationBuilder) bean);
+            super.initBeanInstance(bean, data);
+        }
+
+        /**
+         * Initializes the given child configuration builder from its parent
+         * builder. This method copies the values of some properties from the
+         * parent builder to the child builder so that the child inherits
+         * properties from its parent.
+         *
+         * @param parent the parent builder
+         * @param child the child builder
+         */
+        private static void initChildBuilder(
+                DefaultConfigurationBuilder parent,
+                DefaultConfigurationBuilder child)
+        {
+            child.setAttributeSplittingDisabled(parent
+                    .isAttributeSplittingDisabled());
+            child.setBasePath(parent.getBasePath());
+            child.setDelimiterParsingDisabled(parent
+                    .isDelimiterParsingDisabled());
+            child.setListDelimiter(parent.getListDelimiter());
+            child.setThrowExceptionOnMissing(parent.isThrowExceptionOnMissing());
+            child.setLogger(parent.getLogger());
+
+            child.clearConfigurationListeners();
+            for (ConfigurationListener l : parent.getConfigurationListeners())
+            {
+                child.addConfigurationListener(l);
+            }
+            child.clearErrorListeners();
+            for (ConfigurationErrorListener l : parent.getErrorListeners())
+            {
+                child.addErrorListener(l);
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/DefaultFileSystem.java b/src/main/java/org/apache/commons/configuration/DefaultFileSystem.java
new file mode 100644
index 0000000..ac6e8ff
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/DefaultFileSystem.java
@@ -0,0 +1,373 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * FileSystem that uses java.io.File or HttpClient
+ * @since 1.7
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a>
+ */
+public class DefaultFileSystem extends FileSystem
+{
+    /**
+     * The Log for diagnostic messages.
+     */
+    private Log log = LogFactory.getLog(DefaultFileSystem.class);
+
+    @Override
+    public InputStream getInputStream(String basePath, String fileName)
+        throws ConfigurationException
+    {
+        try
+        {
+            URL url = ConfigurationUtils.locate(this, basePath, fileName);
+
+            if (url == null)
+            {
+                throw new ConfigurationException("Cannot locate configuration source " + fileName);
+            }
+            return getInputStream(url);
+        }
+        catch (ConfigurationException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new ConfigurationException("Unable to load the configuration file " + fileName, e);
+        }
+    }
+
+    @Override
+    public InputStream getInputStream(URL url) throws ConfigurationException
+    {
+        // throw an exception if the target URL is a directory
+        File file = ConfigurationUtils.fileFromURL(url);
+        if (file != null && file.isDirectory())
+        {
+            throw new ConfigurationException("Cannot load a configuration from a directory");
+        }
+
+        try
+        {
+            return url.openStream();
+        }
+        catch (Exception e)
+        {
+            throw new ConfigurationException("Unable to load the configuration from the URL " + url, e);
+        }
+    }
+
+    @Override
+    public OutputStream getOutputStream(URL url) throws ConfigurationException
+    {
+        // file URLs have to be converted to Files since FileURLConnection is
+        // read only (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4191800)
+        File file = ConfigurationUtils.fileFromURL(url);
+        if (file != null)
+        {
+            return getOutputStream(file);
+        }
+        else
+        {
+            // for non file URLs save through an URLConnection
+            OutputStream out;
+            try
+            {
+                URLConnection connection = url.openConnection();
+                connection.setDoOutput(true);
+
+                // use the PUT method for http URLs
+                if (connection instanceof HttpURLConnection)
+                {
+                    HttpURLConnection conn = (HttpURLConnection) connection;
+                    conn.setRequestMethod("PUT");
+                }
+
+                out = connection.getOutputStream();
+
+                // check the response code for http URLs and throw an exception if an error occured
+                if (connection instanceof HttpURLConnection)
+                {
+                    out = new HttpOutputStream(out, (HttpURLConnection) connection);
+                }
+                return out;
+            }
+            catch (IOException e)
+            {
+                throw new ConfigurationException("Could not save to URL " + url, e);
+            }
+        }
+    }
+
+    @Override
+    public OutputStream getOutputStream(File file) throws ConfigurationException
+    {
+        try
+        {
+            // create the file if necessary
+            createPath(file);
+            return new FileOutputStream(file);
+        }
+        catch (FileNotFoundException e)
+        {
+            throw new ConfigurationException("Unable to save to file " + file, e);
+        }
+    }
+
+    @Override
+    public String getPath(File file, URL url, String basePath, String fileName)
+    {
+        String path = null;
+        // if resource was loaded from jar file may be null
+        if (file != null)
+        {
+            path = file.getAbsolutePath();
+        }
+
+        // try to see if file was loaded from a jar
+        if (path == null)
+        {
+            if (url != null)
+            {
+                path = url.getPath();
+            }
+            else
+            {
+                try
+                {
+                    path = getURL(basePath, fileName).getPath();
+                }
+                catch (Exception e)
+                {
+                    // simply ignore it and return null
+                    if (log.isDebugEnabled())
+                    {
+                        log.debug(String.format("Could not determine URL for "
+                                + "basePath = %s, fileName = %s.", basePath,
+                                fileName), e);
+                    }
+                }
+            }
+        }
+
+        return path;
+    }
+
+    @Override
+    public String getBasePath(String path)
+    {
+        URL url;
+        try
+        {
+            url = getURL(null, path);
+            return ConfigurationUtils.getBasePath(url);
+        }
+        catch (Exception e)
+        {
+            return null;
+        }
+    }
+
+    @Override
+    public String getFileName(String path)
+    {
+        URL url;
+        try
+        {
+            url = getURL(null, path);
+            return ConfigurationUtils.getFileName(url);
+        }
+        catch (Exception e)
+        {
+            return null;
+        }
+    }
+
+
+    @Override
+    public URL getURL(String basePath, String file) throws MalformedURLException
+    {
+        File f = new File(file);
+        if (f.isAbsolute()) // already absolute?
+        {
+            return ConfigurationUtils.toURL(f);
+        }
+
+        try
+        {
+            if (basePath == null)
+            {
+                return new URL(file);
+            }
+            else
+            {
+                URL base = new URL(basePath);
+                return new URL(base, file);
+            }
+        }
+        catch (MalformedURLException uex)
+        {
+            return ConfigurationUtils.toURL(ConfigurationUtils.constructFile(basePath, file));
+        }
+    }
+
+
+    @Override
+    public URL locateFromURL(String basePath, String fileName)
+    {
+        try
+        {
+            URL url;
+            if (basePath == null)
+            {
+                return new URL(fileName);
+                //url = new URL(name);
+            }
+            else
+            {
+                URL baseURL = new URL(basePath);
+                url = new URL(baseURL, fileName);
+
+                // check if the file exists
+                InputStream in = null;
+                try
+                {
+                    in = url.openStream();
+                }
+                finally
+                {
+                    if (in != null)
+                    {
+                        in.close();
+                    }
+                }
+                return url;
+            }
+        }
+        catch (IOException e)
+        {
+            if (log.isDebugEnabled())
+            {
+                log.debug("Could not locate file " + fileName + " at " + basePath + ": " + e.getMessage());
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Create the path to the specified file.
+     *
+     * @param file the target file
+     */
+    private void createPath(File file)
+    {
+        if (file != null)
+        {
+            // create the path to the file if the file doesn't exist
+            if (!file.exists())
+            {
+                File parent = file.getParentFile();
+                if (parent != null && !parent.exists())
+                {
+                    parent.mkdirs();
+                }
+            }
+        }
+    }
+    /**
+     * Wraps the output stream so errors can be detected in the HTTP response.
+     * @since 1.7
+     * @author <a
+     * href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a>
+     */
+    private static class HttpOutputStream extends VerifiableOutputStream
+    {
+        /** The wrapped OutputStream */
+        private final OutputStream stream;
+
+        /** The HttpURLConnection */
+        private final HttpURLConnection connection;
+
+        public HttpOutputStream(OutputStream stream, HttpURLConnection connection)
+        {
+            this.stream = stream;
+            this.connection = connection;
+        }
+
+        @Override
+        public void write(byte[] bytes) throws IOException
+        {
+            stream.write(bytes);
+        }
+
+        @Override
+        public void write(byte[] bytes, int i, int i1) throws IOException
+        {
+            stream.write(bytes, i, i1);
+        }
+
+        @Override
+        public void flush() throws IOException
+        {
+            stream.flush();
+        }
+
+        @Override
+        public void close() throws IOException
+        {
+            stream.close();
+        }
+
+        @Override
+        public void write(int i) throws IOException
+        {
+            stream.write(i);
+        }
+
+        @Override
+        public String toString()
+        {
+            return stream.toString();
+        }
+
+        @Override
+        public void verify() throws IOException
+        {
+            if (connection.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST)
+            {
+                throw new IOException("HTTP Error " + connection.getResponseCode()
+                        + " " + connection.getResponseMessage());
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/DynamicCombinedConfiguration.java b/src/main/java/org/apache/commons/configuration/DynamicCombinedConfiguration.java
new file mode 100644
index 0000000..5a92918
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/DynamicCombinedConfiguration.java
@@ -0,0 +1,935 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.commons.configuration.event.ConfigurationErrorListener;
+import org.apache.commons.configuration.event.ConfigurationListener;
+import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.ExpressionEngine;
+import org.apache.commons.configuration.tree.NodeCombiner;
+import org.apache.commons.lang.text.StrSubstitutor;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * DynamicCombinedConfiguration allows a set of CombinedConfigurations to be used. Each CombinedConfiguration
+ * is referenced by a key that is dynamically constructed from a key pattern on each call. The key pattern
+ * will be resolved using the configured ConfigurationInterpolator.
+ * @since 1.6
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: DynamicCombinedConfiguration.java 1534064 2013-10-21 08:44:33Z henning $
+ */
+public class DynamicCombinedConfiguration extends CombinedConfiguration
+{
+    /**
+     * Prevent recursion while resolving unprefixed properties.
+     */
+    private static ThreadLocal<Boolean> recursive = new ThreadLocal<Boolean>()
+    {
+        @Override
+        protected synchronized Boolean initialValue()
+        {
+            return Boolean.FALSE;
+        }
+    };
+
+    /** The CombinedConfigurations */
+    private final ConcurrentMap<String, CombinedConfiguration> configs =
+            new ConcurrentHashMap<String, CombinedConfiguration>();
+
+    /** Stores a list with the contained configurations. */
+    private List<ConfigData> configurations = new ArrayList<ConfigData>();
+
+    /** Stores a map with the named configurations. */
+    private Map<String, AbstractConfiguration> namedConfigurations =
+            new HashMap<String, AbstractConfiguration>();
+
+    /** The key pattern for the CombinedConfiguration map */
+    private String keyPattern;
+
+    /** Stores the combiner. */
+    private NodeCombiner nodeCombiner;
+
+    /** The name of the logger to use for each CombinedConfiguration */
+    private String loggerName = DynamicCombinedConfiguration.class.getName();
+
+    /** The object for handling variable substitution in key patterns. */
+    private StrSubstitutor localSubst = new StrSubstitutor(new ConfigurationInterpolator());
+
+    /**
+     * Creates a new instance of {@code DynamicCombinedConfiguration} and
+     * initializes the combiner to be used.
+     *
+     * @param comb the node combiner (can be <b>null</b>, then a union combiner
+     * is used as default)
+     */
+    public DynamicCombinedConfiguration(NodeCombiner comb)
+    {
+        super();
+        setNodeCombiner(comb);
+        setIgnoreReloadExceptions(false);
+        setLogger(LogFactory.getLog(DynamicCombinedConfiguration.class));
+    }
+
+    /**
+     * Creates a new instance of {@code DynamicCombinedConfiguration} that uses
+     * a union combiner.
+     *
+     * @see org.apache.commons.configuration.tree.UnionCombiner
+     */
+    public DynamicCombinedConfiguration()
+    {
+        super();
+        setIgnoreReloadExceptions(false);
+        setLogger(LogFactory.getLog(DynamicCombinedConfiguration.class));
+    }
+
+    public void setKeyPattern(String pattern)
+    {
+        this.keyPattern = pattern;
+    }
+
+    public String getKeyPattern()
+    {
+        return this.keyPattern;
+    }
+
+    /**
+     * Set the name of the Logger to use on each CombinedConfiguration.
+     * @param name The Logger name.
+     */
+    public void setLoggerName(String name)
+    {
+        this.loggerName = name;
+    }
+
+    /**
+     * Returns the node combiner that is used for creating the combined node
+     * structure.
+     *
+     * @return the node combiner
+     */
+    @Override
+    public NodeCombiner getNodeCombiner()
+    {
+        return nodeCombiner;
+    }
+
+    /**
+     * Sets the node combiner. This object will be used when the combined node
+     * structure is to be constructed. It must not be <b>null</b>, otherwise an
+     * {@code IllegalArgumentException} exception is thrown. Changing the
+     * node combiner causes an invalidation of this combined configuration, so
+     * that the new combiner immediately takes effect.
+     *
+     * @param nodeCombiner the node combiner
+     */
+    @Override
+    public void setNodeCombiner(NodeCombiner nodeCombiner)
+    {
+        if (nodeCombiner == null)
+        {
+            throw new IllegalArgumentException(
+                    "Node combiner must not be null!");
+        }
+        this.nodeCombiner = nodeCombiner;
+        invalidateAll();
+    }
+    /**
+     * Adds a new configuration to this combined configuration. It is possible
+     * (but not mandatory) to give the new configuration a name. This name must
+     * be unique, otherwise a {@code ConfigurationRuntimeException} will
+     * be thrown. With the optional {@code at} argument you can specify
+     * where in the resulting node structure the content of the added
+     * configuration should appear. This is a string that uses dots as property
+     * delimiters (independent on the current expression engine). For instance
+     * if you pass in the string {@code "database.tables"},
+     * all properties of the added configuration will occur in this branch.
+     *
+     * @param config the configuration to add (must not be <b>null</b>)
+     * @param name the name of this configuration (can be <b>null</b>)
+     * @param at the position of this configuration in the combined tree (can be
+     * <b>null</b>)
+     */
+    @Override
+    public void addConfiguration(AbstractConfiguration config, String name,
+            String at)
+    {
+        ConfigData cd = new ConfigData(config, name, at);
+        configurations.add(cd);
+        if (name != null)
+        {
+            namedConfigurations.put(name, config);
+        }
+    }
+       /**
+     * Returns the number of configurations that are contained in this combined
+     * configuration.
+     *
+     * @return the number of contained configurations
+     */
+    @Override
+    public int getNumberOfConfigurations()
+    {
+        return configurations.size();
+    }
+
+    /**
+     * Returns the configuration at the specified index. The contained
+     * configurations are numbered in the order they were added to this combined
+     * configuration. The index of the first configuration is 0.
+     *
+     * @param index the index
+     * @return the configuration at this index
+     */
+    @Override
+    public Configuration getConfiguration(int index)
+    {
+        ConfigData cd = configurations.get(index);
+        return cd.getConfiguration();
+    }
+
+    /**
+     * Returns the configuration with the given name. This can be <b>null</b>
+     * if no such configuration exists.
+     *
+     * @param name the name of the configuration
+     * @return the configuration with this name
+     */
+    @Override
+    public Configuration getConfiguration(String name)
+    {
+        return namedConfigurations.get(name);
+    }
+
+    /**
+     * Returns a set with the names of all configurations contained in this
+     * combined configuration. Of course here are only these configurations
+     * listed, for which a name was specified when they were added.
+     *
+     * @return a set with the names of the contained configurations (never
+     * <b>null</b>)
+     */
+    @Override
+    public Set<String> getConfigurationNames()
+    {
+        return namedConfigurations.keySet();
+    }
+
+    /**
+     * Removes the configuration with the specified name.
+     *
+     * @param name the name of the configuration to be removed
+     * @return the removed configuration (<b>null</b> if this configuration
+     * was not found)
+     */
+    @Override
+    public Configuration removeConfiguration(String name)
+    {
+        Configuration conf = getConfiguration(name);
+        if (conf != null)
+        {
+            removeConfiguration(conf);
+        }
+        return conf;
+    }
+
+    /**
+     * Removes the specified configuration from this combined configuration.
+     *
+     * @param config the configuration to be removed
+     * @return a flag whether this configuration was found and could be removed
+     */
+    @Override
+    public boolean removeConfiguration(Configuration config)
+    {
+        for (int index = 0; index < getNumberOfConfigurations(); index++)
+        {
+            if (configurations.get(index).getConfiguration() == config)
+            {
+                removeConfigurationAt(index);
+
+            }
+        }
+
+        return super.removeConfiguration(config);
+    }
+
+    /**
+     * Removes the configuration at the specified index.
+     *
+     * @param index the index
+     * @return the removed configuration
+     */
+    @Override
+    public Configuration removeConfigurationAt(int index)
+    {
+        ConfigData cd = configurations.remove(index);
+        if (cd.getName() != null)
+        {
+            namedConfigurations.remove(cd.getName());
+        }
+        return super.removeConfigurationAt(index);
+    }
+    /**
+     * Returns the configuration root node of this combined configuration. This
+     * method will construct a combined node structure using the current node
+     * combiner if necessary.
+     *
+     * @return the combined root node
+     */
+    @Override
+    public ConfigurationNode getRootNode()
+    {
+        return getCurrentConfig().getRootNode();
+    }
+
+    @Override
+    public void setRootNode(ConfigurationNode rootNode)
+    {
+        if (configs != null)
+        {
+            this.getCurrentConfig().setRootNode(rootNode);
+        }
+        else
+        {
+            super.setRootNode(rootNode);
+        }
+    }
+
+    @Override
+    public void addProperty(String key, Object value)
+    {
+        this.getCurrentConfig().addProperty(key, value);
+    }
+
+    @Override
+    public void clear()
+    {
+        if (configs != null)
+        {
+            this.getCurrentConfig().clear();
+        }
+    }
+
+    @Override
+    public void clearProperty(String key)
+    {
+        this.getCurrentConfig().clearProperty(key);
+    }
+
+    @Override
+    public boolean containsKey(String key)
+    {
+        return this.getCurrentConfig().containsKey(key);
+    }
+
+    @Override
+    public BigDecimal getBigDecimal(String key, BigDecimal defaultValue)
+    {
+        return this.getCurrentConfig().getBigDecimal(key, defaultValue);
+    }
+
+    @Override
+    public BigDecimal getBigDecimal(String key)
+    {
+        return this.getCurrentConfig().getBigDecimal(key);
+    }
+
+    @Override
+    public BigInteger getBigInteger(String key, BigInteger defaultValue)
+    {
+        return this.getCurrentConfig().getBigInteger(key, defaultValue);
+    }
+
+    @Override
+    public BigInteger getBigInteger(String key)
+    {
+        return this.getCurrentConfig().getBigInteger(key);
+    }
+
+    @Override
+    public boolean getBoolean(String key, boolean defaultValue)
+    {
+        return this.getCurrentConfig().getBoolean(key, defaultValue);
+    }
+
+    @Override
+    public Boolean getBoolean(String key, Boolean defaultValue)
+    {
+        return this.getCurrentConfig().getBoolean(key, defaultValue);
+    }
+
+    @Override
+    public boolean getBoolean(String key)
+    {
+        return this.getCurrentConfig().getBoolean(key);
+    }
+
+    @Override
+    public byte getByte(String key, byte defaultValue)
+    {
+        return this.getCurrentConfig().getByte(key, defaultValue);
+    }
+
+    @Override
+    public Byte getByte(String key, Byte defaultValue)
+    {
+        return this.getCurrentConfig().getByte(key, defaultValue);
+    }
+
+    @Override
+    public byte getByte(String key)
+    {
+        return this.getCurrentConfig().getByte(key);
+    }
+
+    @Override
+    public double getDouble(String key, double defaultValue)
+    {
+        return this.getCurrentConfig().getDouble(key, defaultValue);
+    }
+
+    @Override
+    public Double getDouble(String key, Double defaultValue)
+    {
+        return this.getCurrentConfig().getDouble(key, defaultValue);
+    }
+
+    @Override
+    public double getDouble(String key)
+    {
+        return this.getCurrentConfig().getDouble(key);
+    }
+
+    @Override
+    public float getFloat(String key, float defaultValue)
+    {
+        return this.getCurrentConfig().getFloat(key, defaultValue);
+    }
+
+    @Override
+    public Float getFloat(String key, Float defaultValue)
+    {
+        return this.getCurrentConfig().getFloat(key, defaultValue);
+    }
+
+    @Override
+    public float getFloat(String key)
+    {
+        return this.getCurrentConfig().getFloat(key);
+    }
+
+    @Override
+    public int getInt(String key, int defaultValue)
+    {
+        return this.getCurrentConfig().getInt(key, defaultValue);
+    }
+
+    @Override
+    public int getInt(String key)
+    {
+        return this.getCurrentConfig().getInt(key);
+    }
+
+    @Override
+    public Integer getInteger(String key, Integer defaultValue)
+    {
+        return this.getCurrentConfig().getInteger(key, defaultValue);
+    }
+
+    @Override
+    public Iterator<String> getKeys()
+    {
+        return this.getCurrentConfig().getKeys();
+    }
+
+    @Override
+    public Iterator<String> getKeys(String prefix)
+    {
+        return this.getCurrentConfig().getKeys(prefix);
+    }
+
+    @Override
+    public List<Object> getList(String key, List<?> defaultValue)
+    {
+        return this.getCurrentConfig().getList(key, defaultValue);
+    }
+
+    @Override
+    public List<Object> getList(String key)
+    {
+        return this.getCurrentConfig().getList(key);
+    }
+
+    @Override
+    public long getLong(String key, long defaultValue)
+    {
+        return this.getCurrentConfig().getLong(key, defaultValue);
+    }
+
+    @Override
+    public Long getLong(String key, Long defaultValue)
+    {
+        return this.getCurrentConfig().getLong(key, defaultValue);
+    }
+
+    @Override
+    public long getLong(String key)
+    {
+        return this.getCurrentConfig().getLong(key);
+    }
+
+    @Override
+    public Properties getProperties(String key)
+    {
+        return this.getCurrentConfig().getProperties(key);
+    }
+
+    @Override
+    public Object getProperty(String key)
+    {
+        return this.getCurrentConfig().getProperty(key);
+    }
+
+    @Override
+    public short getShort(String key, short defaultValue)
+    {
+        return this.getCurrentConfig().getShort(key, defaultValue);
+    }
+
+    @Override
+    public Short getShort(String key, Short defaultValue)
+    {
+        return this.getCurrentConfig().getShort(key, defaultValue);
+    }
+
+    @Override
+    public short getShort(String key)
+    {
+        return this.getCurrentConfig().getShort(key);
+    }
+
+    @Override
+    public String getString(String key, String defaultValue)
+    {
+        return this.getCurrentConfig().getString(key, defaultValue);
+    }
+
+    @Override
+    public String getString(String key)
+    {
+        return this.getCurrentConfig().getString(key);
+    }
+
+    @Override
+    public String[] getStringArray(String key)
+    {
+        return this.getCurrentConfig().getStringArray(key);
+    }
+
+    @Override
+    public boolean isEmpty()
+    {
+        return this.getCurrentConfig().isEmpty();
+    }
+
+    @Override
+    public void setProperty(String key, Object value)
+    {
+        if (configs != null)
+        {
+            this.getCurrentConfig().setProperty(key, value);
+        }
+    }
+
+    @Override
+    public Configuration subset(String prefix)
+    {
+        return this.getCurrentConfig().subset(prefix);
+    }
+
+    @Override
+    public Node getRoot()
+    {
+        return this.getCurrentConfig().getRoot();
+    }
+
+    @Override
+    public void setRoot(Node node)
+    {
+        if (configs != null)
+        {
+            this.getCurrentConfig().setRoot(node);
+        }
+        else
+        {
+            super.setRoot(node);
+        }
+    }
+
+    @Override
+    public ExpressionEngine getExpressionEngine()
+    {
+        return super.getExpressionEngine();
+    }
+
+    @Override
+    public void setExpressionEngine(ExpressionEngine expressionEngine)
+    {
+        super.setExpressionEngine(expressionEngine);
+    }
+
+    @Override
+    public void addNodes(String key, Collection<? extends ConfigurationNode> nodes)
+    {
+        this.getCurrentConfig().addNodes(key, nodes);
+    }
+
+    @Override
+    public SubnodeConfiguration configurationAt(String key, boolean supportUpdates)
+    {
+        return this.getCurrentConfig().configurationAt(key, supportUpdates);
+    }
+
+    @Override
+    public SubnodeConfiguration configurationAt(String key)
+    {
+        return this.getCurrentConfig().configurationAt(key);
+    }
+
+    @Override
+    public List<HierarchicalConfiguration> configurationsAt(String key)
+    {
+        return this.getCurrentConfig().configurationsAt(key);
+    }
+
+    @Override
+    public void clearTree(String key)
+    {
+        this.getCurrentConfig().clearTree(key);
+    }
+
+    @Override
+    public int getMaxIndex(String key)
+    {
+        return this.getCurrentConfig().getMaxIndex(key);
+    }
+
+    @Override
+    public Configuration interpolatedConfiguration()
+    {
+        return this.getCurrentConfig().interpolatedConfiguration();
+    }
+
+
+    /**
+     * Returns the configuration source, in which the specified key is defined.
+     * This method will determine the configuration node that is identified by
+     * the given key. The following constellations are possible:
+     * <ul>
+     * <li>If no node object is found for this key, <b>null</b> is returned.</li>
+     * <li>If the key maps to multiple nodes belonging to different
+     * configuration sources, a {@code IllegalArgumentException} is
+     * thrown (in this case no unique source can be determined).</li>
+     * <li>If exactly one node is found for the key, the (child) configuration
+     * object, to which the node belongs is determined and returned.</li>
+     * <li>For keys that have been added directly to this combined
+     * configuration and that do not belong to the namespaces defined by
+     * existing child configurations this configuration will be returned.</li>
+     * </ul>
+     *
+     * @param key the key of a configuration property
+     * @return the configuration, to which this property belongs or <b>null</b>
+     * if the key cannot be resolved
+     * @throws IllegalArgumentException if the key maps to multiple properties
+     * and the source cannot be determined, or if the key is <b>null</b>
+     */
+    @Override
+    public Configuration getSource(String key)
+    {
+        if (key == null)
+        {
+            throw new IllegalArgumentException("Key must not be null!");
+        }
+        return getCurrentConfig().getSource(key);
+    }
+
+    @Override
+    public void addConfigurationListener(ConfigurationListener l)
+    {
+        super.addConfigurationListener(l);
+
+        for (CombinedConfiguration cc : configs.values())
+        {
+            cc.addConfigurationListener(l);
+        }
+    }
+
+    @Override
+    public boolean removeConfigurationListener(ConfigurationListener l)
+    {
+        for (CombinedConfiguration cc : configs.values())
+        {
+            cc.removeConfigurationListener(l);
+        }
+        return super.removeConfigurationListener(l);
+    }
+
+    @Override
+    public Collection<ConfigurationListener> getConfigurationListeners()
+    {
+        return super.getConfigurationListeners();
+    }
+
+    @Override
+    public void clearConfigurationListeners()
+    {
+        for (CombinedConfiguration cc : configs.values())
+        {
+            cc.clearConfigurationListeners();
+        }
+        super.clearConfigurationListeners();
+    }
+
+    @Override
+    public void addErrorListener(ConfigurationErrorListener l)
+    {
+        for (CombinedConfiguration cc : configs.values())
+        {
+            cc.addErrorListener(l);
+        }
+        super.addErrorListener(l);
+    }
+
+    @Override
+    public boolean removeErrorListener(ConfigurationErrorListener l)
+    {
+        for (CombinedConfiguration cc : configs.values())
+        {
+            cc.removeErrorListener(l);
+        }
+        return super.removeErrorListener(l);
+    }
+
+    @Override
+    public void clearErrorListeners()
+    {
+        for (CombinedConfiguration cc : configs.values())
+        {
+            cc.clearErrorListeners();
+        }
+        super.clearErrorListeners();
+    }
+
+    @Override
+    public Collection<ConfigurationErrorListener> getErrorListeners()
+    {
+        return super.getErrorListeners();
+    }
+
+    /**
+     * Returns a copy of this object. This implementation performs a deep clone,
+     * i.e. all contained configurations will be cloned, too. For this to work,
+     * all contained configurations must be cloneable. Registered event
+     * listeners won't be cloned. The clone will use the same node combiner than
+     * the original.
+     *
+     * @return the copied object
+     */
+    @Override
+    public Object clone()
+    {
+        return super.clone();
+    }
+
+    /**
+     * Invalidates the current combined configuration. This means that the next time a
+     * property is accessed the combined node structure must be re-constructed.
+     * Invalidation of a combined configuration also means that an event of type
+     * {@code EVENT_COMBINED_INVALIDATE} is fired. Note that while other
+     * events most times appear twice (once before and once after an update),
+     * this event is only fired once (after update).
+     */
+    @Override
+    public void invalidate()
+    {
+        getCurrentConfig().invalidate();
+    }
+
+    public void invalidateAll()
+    {
+        if (configs == null)
+        {
+            return;
+        }
+        for (CombinedConfiguration cc : configs.values())
+        {
+            cc.invalidate();
+        }
+    }
+
+    /*
+     * Don't allow resolveContainerStore to be called recursively.
+     * @param key The key to resolve.
+     * @return The value of the key.
+     */
+    @Override
+    protected Object resolveContainerStore(String key)
+    {
+        if (recursive.get().booleanValue())
+        {
+            return null;
+        }
+        recursive.set(Boolean.TRUE);
+        try
+        {
+            return super.resolveContainerStore(key);
+        }
+        finally
+        {
+            recursive.set(Boolean.FALSE);
+        }
+    }
+
+    private CombinedConfiguration getCurrentConfig()
+    {
+        String key = localSubst.replace(keyPattern);
+        CombinedConfiguration config = configs.get(key);
+        // The double-checked works here due to the Thread guarantees of ConcurrentMap.
+        if (config == null)
+        {
+            synchronized (configs)
+            {
+                config = configs.get(key);
+                if (config == null)
+                {
+                    config = new CombinedConfiguration(getNodeCombiner());
+                    if (loggerName != null)
+                    {
+                        Log log = LogFactory.getLog(loggerName);
+                        if (log != null)
+                        {
+                            config.setLogger(log);
+                        }
+                    }
+                    config.setIgnoreReloadExceptions(isIgnoreReloadExceptions());
+                    config.setExpressionEngine(this.getExpressionEngine());
+                    config.setDelimiterParsingDisabled(isDelimiterParsingDisabled());
+                    config.setConversionExpressionEngine(getConversionExpressionEngine());
+                    config.setListDelimiter(getListDelimiter());
+                    for (ConfigurationErrorListener listener : getErrorListeners())
+                    {
+                        config.addErrorListener(listener);
+                    }
+                    for (ConfigurationListener listener : getConfigurationListeners())
+                    {
+                        config.addConfigurationListener(listener);
+                    }
+                    config.setForceReloadCheck(isForceReloadCheck());
+                    for (ConfigData data : configurations)
+                    {
+                        config.addConfiguration(data.getConfiguration(), data.getName(), data.getAt());
+                    }
+                    configs.put(key, config);
+                }
+            }
+        }
+        if (getLogger().isDebugEnabled())
+        {
+            getLogger().debug("Returning config for " + key + ": " + config);
+        }
+        return config;
+    }
+
+    /**
+     * Internal class that identifies each Configuration.
+     */
+    static class ConfigData
+    {
+        /** Stores a reference to the configuration. */
+        private AbstractConfiguration configuration;
+
+        /** Stores the name under which the configuration is stored. */
+        private String name;
+
+        /** Stores the at string.*/
+        private String at;
+
+        /**
+         * Creates a new instance of {@code ConfigData} and initializes
+         * it.
+         *
+         * @param config the configuration
+         * @param n the name
+         * @param at the at position
+         */
+        public ConfigData(AbstractConfiguration config, String n, String at)
+        {
+            configuration = config;
+            name = n;
+            this.at = at;
+        }
+
+        /**
+         * Returns the stored configuration.
+         *
+         * @return the configuration
+         */
+        public AbstractConfiguration getConfiguration()
+        {
+            return configuration;
+        }
+
+        /**
+         * Returns the configuration's name.
+         *
+         * @return the name
+         */
+        public String getName()
+        {
+            return name;
+        }
+
+        /**
+         * Returns the at position of this configuration.
+         *
+         * @return the at position
+         */
+        public String getAt()
+        {
+            return at;
+        }
+
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/EnvironmentConfiguration.java b/src/main/java/org/apache/commons/configuration/EnvironmentConfiguration.java
new file mode 100644
index 0000000..e6c03db
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/EnvironmentConfiguration.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.util.HashMap;
+
+/**
+ * <p>A Configuration implementation that reads the platform specific
+ * environment variables using the map returned by {@link System#getenv()}.</p>
+ *
+ * <p>This configuration implementation is read-only. It allows read access to the
+ * defined OS environment variables, but their values cannot be changed. Any
+ * attempts to add or remove a property will throw an
+ * {@link UnsupportedOperationException}</p>
+ *
+ * <p>Usage of this class is easy: After an instance has been created the get
+ * methods provided by the {@code Configuration} interface can be used
+ * for querying environment variables, e.g.:</p>
+ *
+ * <pre>
+ * Configuration envConfig = new EnvironmentConfiguration();
+ * System.out.println("JAVA_HOME=" + envConfig.getString("JAVA_HOME");
+ * </pre>
+ *
+ * @author <a href="mailto:nicolas.deloof at gmail.com">Nicolas De Loof</a>
+ * @version $Id: EnvironmentConfiguration.java 1210171 2011-12-04 18:32:07Z oheger $
+ * @since 1.5
+ */
+public class EnvironmentConfiguration extends MapConfiguration
+{
+    /**
+     * Create a Configuration based on the environment variables.
+     *
+     * @see System#getenv()
+     */
+    public EnvironmentConfiguration()
+    {
+        super(new HashMap<String, Object>(System.getenv()));
+    }
+
+    /**
+     * Adds a property to this configuration. Because this configuration is
+     * read-only, this operation is not allowed and will cause an exception.
+     *
+     * @param key the key of the property to be added
+     * @param value the property value
+     */
+    @Override
+    protected void addPropertyDirect(String key, Object value)
+    {
+        throw new UnsupportedOperationException("EnvironmentConfiguration is read-only!");
+    }
+
+    /**
+     * Removes a property from this configuration. Because this configuration is
+     * read-only, this operation is not allowed and will cause an exception.
+     *
+     * @param key the key of the property to be removed
+     */
+    @Override
+    public void clearProperty(String key)
+    {
+        throw new UnsupportedOperationException("EnvironmentConfiguration is read-only!");
+    }
+
+    /**
+     * Removes all properties from this configuration. Because this
+     * configuration is read-only, this operation is not allowed and will cause
+     * an exception.
+     */
+    @Override
+    public void clear()
+    {
+        throw new UnsupportedOperationException("EnvironmentConfiguration is read-only!");
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/FileConfiguration.java b/src/main/java/org/apache/commons/configuration/FileConfiguration.java
new file mode 100644
index 0000000..8ebf8e1
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/FileConfiguration.java
@@ -0,0 +1,293 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URL;
+
+import org.apache.commons.configuration.reloading.ReloadingStrategy;
+
+/**
+ * A persistent configuration loaded and saved to a file.
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: FileConfiguration.java 1209883 2011-12-03 10:56:57Z oheger $
+ * @since 1.0-rc2
+ */
+public interface FileConfiguration extends Configuration
+{
+    /**
+     * Load the configuration from the underlying URL. If the URL is not
+     * specified, it attempts to locate the specified file name.
+     *
+     * @throws ConfigurationException if an error occurs during the load operation
+     */
+    void load() throws ConfigurationException;
+
+    /**
+     * Locate the specified file and load the configuration.
+     *
+     * @param fileName the name of the file loaded
+     *
+     * @throws ConfigurationException if an error occurs during the load operation
+     */
+    void load(String fileName) throws ConfigurationException;
+
+    /**
+     * Load the configuration from the specified file.
+     *
+     * @param file the loaded file
+     *
+     * @throws ConfigurationException if an error occurs during the load operation
+     */
+    void load(File file) throws ConfigurationException;
+
+    /**
+     * Load the configuration from the specified URL.
+     *
+     * @param url the URL of the file loaded
+     *
+     * @throws ConfigurationException if an error occurs during the load operation
+     */
+    void load(URL url) throws ConfigurationException;
+
+    /**
+     * Load the configuration from the specified stream, using the encoding
+     * returned by {@link #getEncoding()}.
+     *
+     * @param in the input stream
+     *
+     * @throws ConfigurationException if an error occurs during the load operation
+     */
+    void load(InputStream in) throws ConfigurationException;
+
+    /**
+     * Load the configuration from the specified stream, using the specified
+     * encoding. If the encoding is null the default encoding is used.
+     *
+     * @param in the input stream
+     * @param encoding the encoding used. {@code null} to use the default encoding
+     *
+     * @throws ConfigurationException if an error occurs during the load operation
+     */
+    void load(InputStream in, String encoding) throws ConfigurationException;
+
+    /**
+     * Load the configuration from the specified reader.
+     *
+     * @param in the reader
+     *
+     * @throws ConfigurationException if an error occurs during the load operation
+     */
+    void load(Reader in) throws ConfigurationException;
+
+    /**
+     * Save the configuration.
+     *
+     * @throws ConfigurationException if an error occurs during the save operation
+     */
+    void save() throws ConfigurationException;
+
+    /**
+     * Save the configuration to the specified file.
+     *
+     * @param fileName the name of the file to be saved
+     *
+     * @throws ConfigurationException if an error occurs during the save operation
+     */
+    void save(String fileName) throws ConfigurationException;
+
+    /**
+     * Save the configuration to the specified file.
+     *
+     * @param file specifies the file to be saved
+     *
+     * @throws ConfigurationException if an error occurs during the save operation
+     */
+    void save(File file) throws ConfigurationException;
+
+    /**
+     * Save the configuration to the specified URL.
+     *
+     * @param url the URL
+     *
+     * @throws ConfigurationException if an error occurs during the save operation
+     */
+    void save(URL url) throws ConfigurationException;
+
+    /**
+     * Save the configuration to the specified stream, using the encoding
+     * returned by {@link #getEncoding()}.
+     *
+     * @param out the output stream
+     *
+     * @throws ConfigurationException if an error occurs during the save operation
+     */
+    void save(OutputStream out) throws ConfigurationException;
+
+    /**
+     * Save the configuration to the specified stream, using the specified
+     * encoding. If the encoding is null the default encoding is used.
+     *
+     * @param out the output stream
+     * @param encoding the encoding to be used
+     * @throws ConfigurationException if an error occurs during the save operation
+     */
+    void save(OutputStream out, String encoding) throws ConfigurationException;
+
+    /**
+     * Save the configuration to the specified writer.
+     *
+     * @param out the writer
+     *
+     * @throws ConfigurationException if an error occurs during the save operation
+     */
+    void save(Writer out) throws ConfigurationException;
+
+    /**
+     * Return the name of the file.
+     *
+     * @return the file name
+     */
+    String getFileName();
+
+    /**
+     * Set the name of the file.
+     *
+     * @param fileName the name of the file
+     */
+    void setFileName(String fileName);
+
+    /**
+     * Returns the base path. One way to specify the location of a configuration
+     * source is by setting its base path and its file name. This method returns
+     * this base path. The concrete value returned by this method depends on the
+     * way the location of the configuration file was set. If methods like
+     * {@code setFile()} or {@code setURL()} were used, the base
+     * path typically points to the parent directory of the configuration file
+     * (e.g. for the URL {@code file:/temp/test.properties} the base path
+     * will be {@code file:/temp/}). If the base path was explicitly set
+     * using {@code setBasePath()}, this method will return the exact
+     * value specified here without further modifications.
+     *
+     * @return the base path
+     * @see AbstractFileConfiguration#setBasePath(String)
+     */
+    String getBasePath();
+
+    /**
+     * Sets the base path. The methods {@code setBasePath()} and
+     * {@code setFileName()} can be used together to specify the location
+     * of the configuration file to be loaded. If relative file names are to
+     * be resolved (e.g. for the include files supported by
+     * {@code PropertiesConfiguration}), this base path will be used.
+     *
+     * @param basePath the base path.
+     */
+    void setBasePath(String basePath);
+
+    /**
+     * Return the file where the configuration is stored.
+     *
+     * @return the configuration file
+     */
+    File getFile();
+
+    /**
+     * Set the file where the configuration is stored.
+     *
+     * @param file the file
+     */
+    void setFile(File file);
+
+    /**
+     * Return the URL where the configuration is stored.
+     *
+     * @return the URL of the configuration
+     */
+    URL getURL();
+
+    /**
+     * The URL where the configuration is stored.
+     *
+     * @param url the URL
+     */
+    void setURL(URL url);
+
+    /**
+     * Enable or disable the automatically saving of modified properties to the disk.
+     *
+     * @param autoSave {@code true} to enable, {@code false} to disable
+     * @since 1.1
+     */
+    void setAutoSave(boolean autoSave);
+
+    /**
+     * Tells if properties are automatically saved to the disk.
+     *
+     * @return {@code true} if auto-saving is enabled, {@code false} otherwise
+     * @since 1.1
+     */
+    boolean isAutoSave();
+
+    /**
+     * Return the reloading strategy.
+     *
+     * @return the reloading strategy currently used
+     * @since 1.1
+     */
+    ReloadingStrategy getReloadingStrategy();
+
+    /**
+     * Set the reloading strategy.
+     *
+     * @param strategy the reloading strategy to use
+     * @since 1.1
+     */
+    void setReloadingStrategy(ReloadingStrategy strategy);
+
+    /**
+     * Reload the configuration.
+     *
+     * @since 1.1
+     */
+    void reload();
+
+    /**
+     * Return the encoding used to store the configuration file. If the value
+     * is null the default encoding is used.
+     *
+     * @return the current encoding
+     * @since 1.1
+     */
+    String getEncoding();
+
+    /**
+     * Set the encoding used to store the configuration file. Set the encoding
+     * to null to use the default encoding.
+     *
+     * @param encoding the encoding to use
+     * @since 1.1
+     */
+    void setEncoding(String encoding);
+
+}
diff --git a/src/main/java/org/apache/commons/configuration/FileOptionsProvider.java b/src/main/java/org/apache/commons/configuration/FileOptionsProvider.java
new file mode 100644
index 0000000..d4a789e
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/FileOptionsProvider.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import java.util.Map;
+
+/**
+ * Some FileSystems allow options to be passed on File operations. Users of commons
+ * configuration can implement this interface and register it with the FileSystem.
+ * @since 1.7
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a>
+ * @version $Id: FileOptionsProvider.java 1209884 2011-12-03 10:57:38Z oheger $
+ */
+public interface FileOptionsProvider
+{
+    /**
+     * Key used to identify the user to be associated with the current file operations.
+     * The value associated with this key is a String identifying the current user.
+     */
+    String CURRENT_USER = "currentUser";
+
+    /**
+     * Key used to indicate whether Webdav versioning support should be enabled.
+     * The value associated with this key is a Boolean where True indicates versioning should
+     * be enabled.
+     */
+    String VERSIONING = "versioning";
+
+    /**
+     * Key used to identify the proxy host to connect through.
+     * The value associated with this key is a String identifying the host name of the proxy.
+     */
+    String PROXY_HOST = "proxyHost";
+
+    /**
+     * Key used to identify the proxy port to connect through.
+     * The value associated with this key is an Integer identifying the port on the proxy.
+     */
+    String PROXY_PORT = "proxyPort";
+
+    /**
+     * Key used to identify the maximum number of connections allowed to a single host.
+     * The value associated with this key is an Integer identifying the maximum number of
+     * connections allowed to a single host.
+     */
+    String MAX_HOST_CONNECTIONS = "maxHostConnections";
+
+    /**
+     * Key used to identify the maximum number of connections allowed to all hosts.
+     * The value associated with this key is an Integer identifying the maximum number of
+     * connections allowed to all hosts.
+     */
+    String MAX_TOTAL_CONNECTIONS = "maxTotalConnections";
+
+    /**
+     *
+     * @return Options to be used for this file.
+     */
+    Map<String, Object> getOptions();
+}
diff --git a/src/main/java/org/apache/commons/configuration/FileSystem.java b/src/main/java/org/apache/commons/configuration/FileSystem.java
new file mode 100644
index 0000000..4740807
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/FileSystem.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.logging.impl.NoOpLog;
+
+/**
+ * Abstract layer to allow various types of file systems.
+ * @since 1.7
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a>
+ * @version $Id: FileSystem.java 1209996 2011-12-03 20:24:21Z oheger $
+ */
+public abstract class FileSystem
+{
+    /** The name of the system property that can be used to set the file system class name */
+    private static final String FILE_SYSTEM = "org.apache.commons.configuration.filesystem";
+
+    /** The default file system */
+    private static FileSystem fileSystem;
+
+    /** The Logger */
+    private Log log;
+
+    /** FileSystem options provider */
+    private FileOptionsProvider optionsProvider;
+
+    public FileSystem()
+    {
+        setLogger(null);
+    }
+
+    /**
+     * Returns the logger used by this FileSystem.
+     *
+     * @return the logger
+     */
+    public Log getLogger()
+    {
+        return log;
+    }
+
+    /**
+     * Allows to set the logger to be used by this FileSystem. This
+     * method makes it possible for clients to exactly control logging behavior.
+     * Per default a logger is set that will ignore all log messages. Derived
+     * classes that want to enable logging should call this method during their
+     * initialization with the logger to be used.
+     *
+     * @param log the new logger
+     */
+    public void setLogger(Log log)
+    {
+        this.log = (log != null) ? log : new NoOpLog();
+    }
+
+    static
+    {
+        String fsClassName = System.getProperty(FILE_SYSTEM);
+        if (fsClassName != null)
+        {
+            Log log = LogFactory.getLog(FileSystem.class);
+
+            try
+            {
+                Class<?> clazz = Class.forName(fsClassName);
+                if (FileSystem.class.isAssignableFrom(clazz))
+                {
+                    fileSystem = (FileSystem) clazz.newInstance();
+                    if (log.isDebugEnabled())
+                    {
+                        log.debug("Using " + fsClassName);
+                    }
+                }
+            }
+            catch (InstantiationException ex)
+            {
+                log.error("Unable to create " + fsClassName, ex);
+            }
+            catch (IllegalAccessException ex)
+            {
+                log.error("Unable to create " + fsClassName, ex);
+            }
+            catch (ClassNotFoundException ex)
+            {
+                log.error("Unable to create " + fsClassName, ex);
+            }
+        }
+
+        if (fileSystem == null)
+        {
+            fileSystem = new DefaultFileSystem();
+        }
+    }
+
+    /**
+     * Set the FileSystem to use.
+     * @param fs The FileSystem
+     * @throws NullPointerException if the FileSystem parameter is null.
+     */
+    public static void setDefaultFileSystem(FileSystem fs) throws NullPointerException
+    {
+        if (fs == null)
+        {
+            throw new NullPointerException("A FileSystem implementation is required");
+        }
+        fileSystem = fs;
+    }
+
+    /**
+     * Reset the FileSystem to the default.
+     */
+    public static void resetDefaultFileSystem()
+    {
+        fileSystem = new DefaultFileSystem();
+    }
+
+    /**
+     * Retrieve the FileSystem being used.
+     * @return The FileSystem.
+     */
+    public static FileSystem getDefaultFileSystem()
+    {
+        return fileSystem;
+    }
+
+    /**
+     * Set the FileOptionsProvider
+     * @param provider The FileOptionsProvider
+     */
+    public void setFileOptionsProvider(FileOptionsProvider provider)
+    {
+        this.optionsProvider = provider;
+    }
+
+    public FileOptionsProvider getFileOptionsProvider()
+    {
+        return this.optionsProvider;
+    }
+
+    public abstract InputStream getInputStream(String basePath, String fileName)
+            throws ConfigurationException;
+
+    public abstract InputStream getInputStream(URL url) throws ConfigurationException;
+
+    public abstract OutputStream getOutputStream(URL url) throws ConfigurationException;
+
+    public abstract OutputStream getOutputStream(File file) throws ConfigurationException;
+
+    public abstract String getPath(File file, URL url, String basePath, String fileName);
+
+    public abstract String getBasePath(String path);
+
+    public abstract String getFileName(String path);
+
+    public abstract URL locateFromURL(String basePath, String fileName);
+
+    public abstract URL getURL(String basePath, String fileName) throws MalformedURLException;
+}
diff --git a/src/main/java/org/apache/commons/configuration/FileSystemBased.java b/src/main/java/org/apache/commons/configuration/FileSystemBased.java
new file mode 100644
index 0000000..a304a16
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/FileSystemBased.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+/**
+ * Interface used to install or locate the FileSystem
+ * @since 1.7
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a>
+ */
+public interface FileSystemBased
+{
+    void setFileSystem(FileSystem fileSystem);
+
+    void resetFileSystem();
+
+    FileSystem getFileSystem();
+}
diff --git a/src/main/java/org/apache/commons/configuration/HierarchicalConfiguration.java b/src/main/java/org/apache/commons/configuration/HierarchicalConfiguration.java
new file mode 100644
index 0000000..a125ca9
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/HierarchicalConfiguration.java
@@ -0,0 +1,1769 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
+
+import org.apache.commons.configuration.event.ConfigurationEvent;
+import org.apache.commons.configuration.event.ConfigurationListener;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.ConfigurationNodeVisitorAdapter;
+import org.apache.commons.configuration.tree.DefaultConfigurationNode;
+import org.apache.commons.configuration.tree.DefaultExpressionEngine;
+import org.apache.commons.configuration.tree.ExpressionEngine;
+import org.apache.commons.configuration.tree.NodeAddData;
+import org.apache.commons.configuration.tree.ViewNode;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * <p>A specialized configuration class that extends its base class by the
+ * ability of keeping more structure in the stored properties.</p><p>There
+ * are some sources of configuration data that cannot be stored very well in a
+ * {@code BaseConfiguration} object because then their structure is lost.
+ * This is especially true for XML documents. This class can deal with such
+ * structured configuration sources by storing the properties in a tree-like
+ * organization.</p><p>The internal used storage form allows for a more
+ * sophisticated access to single properties. As an example consider the
+ * following XML document:</p><p>
+ *
+ * <pre>
+ * <database>
+ *   <tables>
+ *     <table>
+ *       <name>users</name>
+ *       <fields>
+ *         <field>
+ *           <name>lid</name>
+ *           <type>long</name>
+ *         </field>
+ *         <field>
+ *           <name>usrName</name>
+ *           <type>java.lang.String</type>
+ *         </field>
+ *        ...
+ *       </fields>
+ *     </table>
+ *     <table>
+ *       <name>documents</name>
+ *       <fields>
+ *         <field>
+ *           <name>docid</name>
+ *           <type>long</type>
+ *         </field>
+ *         ...
+ *       </fields>
+ *     </table>
+ *     ...
+ *   </tables>
+ * </database>
+ * </pre>
+ *
+ * </p><p>If this document is parsed and stored in a
+ * {@code HierarchicalConfiguration} object (which can be done by one of
+ * the sub classes), there are enhanced possibilities of accessing properties.
+ * The keys for querying information can contain indices that select a certain
+ * element if there are multiple hits.</p><p>For instance the key
+ * {@code tables.table(0).name} can be used to find out the name of the
+ * first table. In opposite {@code tables.table.name} would return a
+ * collection with the names of all available tables. Similarly the key
+ * {@code tables.table(1).fields.field.name} returns a collection with
+ * the names of all fields of the second table. If another index is added after
+ * the {@code field} element, a single field can be accessed:
+ * {@code tables.table(1).fields.field(0).name}.</p><p>There is a
+ * {@code getMaxIndex()} method that returns the maximum allowed index
+ * that can be added to a given property key. This method can be used to iterate
+ * over all values defined for a certain property.</p>
+ * <p>Since the 1.3 release of <em>Commons Configuration</em> hierarchical
+ * configurations support an <em>expression engine</em>. This expression engine
+ * is responsible for evaluating the passed in configuration keys and map them
+ * to the stored properties. The examples above are valid for the default
+ * expression engine, which is used when a new {@code HierarchicalConfiguration}
+ * instance is created. With the {@code setExpressionEngine()} method a
+ * different expression engine can be set. For instance with
+ * {@link org.apache.commons.configuration.tree.xpath.XPathExpressionEngine}
+ * there is an expression engine available that supports configuration keys in
+ * XPATH syntax.</p>
+ * <p>In addition to the events common for all configuration classes hierarchical
+ * configurations support some more events that correspond to some specific
+ * methods and features:
+ * <dl><dt><em>EVENT_ADD_NODES</em></dt><dd>The {@code addNodes()} method
+ * was called; the event object contains the key, to which the nodes were added,
+ * and a collection with the new nodes as value.</dd>
+ * <dt><em>EVENT_CLEAR_TREE</em></dt><dd>The {@code clearTree()} method was
+ * called; the event object stores the key of the removed sub tree.</dd>
+ * <dt><em>EVENT_SUBNODE_CHANGED</em></dt><dd>A {@code SubnodeConfiguration}
+ * that was created from this configuration has been changed. The value property
+ * of the event object contains the original event object as it was sent by the
+ * subnode configuration.</dd></dl></p>
+ * <p><em>Note:</em>Configuration objects of this type can be read concurrently
+ * by multiple threads. However if one of these threads modifies the object,
+ * synchronization has to be performed manually.</p>
+ *
+ * @author Oliver Heger
+ * @version $Id: HierarchicalConfiguration.java 1330666 2012-04-26 06:12:30Z oheger $
+ */
+public class HierarchicalConfiguration extends AbstractConfiguration implements Serializable, Cloneable
+{
+    /**
+     * Constant for the clear tree event.
+     * @since 1.3
+     */
+    public static final int EVENT_CLEAR_TREE = 10;
+
+    /**
+     * Constant for the add nodes event.
+     * @since 1.3
+     */
+    public static final int EVENT_ADD_NODES = 11;
+
+    /**
+     * Constant for the subnode configuration modified event.
+     * @since 1.5
+     */
+    public static final int EVENT_SUBNODE_CHANGED = 12;
+
+    /**
+     * The serial version UID.
+     */
+    private static final long serialVersionUID = 3373812230395363192L;
+
+    /** Stores the default expression engine to be used for new objects.*/
+    private static ExpressionEngine defaultExpressionEngine;
+
+    /** Stores the root node of this configuration. This field is required for
+     * backwards compatibility only.
+     */
+    private Node root;
+
+    /** Stores the root configuration node.*/
+    private ConfigurationNode rootNode;
+
+    /** Stores the expression engine for this instance.*/
+    private transient ExpressionEngine expressionEngine;
+
+    /**
+     * Creates a new instance of {@code HierarchicalConfiguration}.
+     */
+    public HierarchicalConfiguration()
+    {
+        setRootNode(new Node());
+    }
+
+    /**
+     * Creates a new instance of {@code HierarchicalConfiguration} and
+     * copies all data contained in the specified configuration into the new
+     * one.
+     *
+     * @param c the configuration that is to be copied (if <b>null</b>, this
+     * constructor will behave like the standard constructor)
+     * @since 1.4
+     */
+    public HierarchicalConfiguration(HierarchicalConfiguration c)
+    {
+        this();
+        if (c != null)
+        {
+            CloneVisitor visitor = new CloneVisitor();
+            c.getRootNode().visit(visitor);
+            setRootNode(visitor.getClone());
+        }
+    }
+
+    /**
+     * Returns the object to synchronize on a reload. This class is not
+     * reloadable so this object isn't important
+     *
+     * @return the lock object
+     */
+    public Object getReloadLock()
+    {
+        return this;
+    }
+
+    /**
+     * Returns the root node of this hierarchical configuration. This method
+     * exists for backwards compatibility only. New code should use the
+     * {@link #getRootNode()} method instead, which operates on
+     * the preferred data type {@code ConfigurationNode}.
+     *
+     * @return the root node
+     */
+    public Node getRoot()
+    {
+        if (root == null && rootNode != null)
+        {
+            // Dynamically create a snapshot of the root node
+            return new Node(rootNode);
+        }
+
+        return root;
+    }
+
+    /**
+     * Sets the root node of this hierarchical configuration. This method
+     * exists for backwards compatibility only. New code should use the
+     * {@link #setRootNode(ConfigurationNode)} method instead,
+     * which operates on the preferred data type {@code ConfigurationNode}.
+     *
+     * @param node the root node
+     */
+    public void setRoot(Node node)
+    {
+        if (node == null)
+        {
+            throw new IllegalArgumentException("Root node must not be null!");
+        }
+        root = node;
+        rootNode = null;
+    }
+
+    /**
+     * Returns the root node of this hierarchical configuration.
+     *
+     * @return the root node
+     * @since 1.3
+     */
+    public ConfigurationNode getRootNode()
+    {
+        return (rootNode != null) ? rootNode : root;
+    }
+
+    /**
+     * Sets the root node of this hierarchical configuration.
+     *
+     * @param rootNode the root node
+     * @since 1.3
+     */
+    public void setRootNode(ConfigurationNode rootNode)
+    {
+        if (rootNode == null)
+        {
+            throw new IllegalArgumentException("Root node must not be null!");
+        }
+        this.rootNode = rootNode;
+
+        // For backward compatibility also set the old root field.
+        root = (rootNode instanceof Node) ? (Node) rootNode : null;
+    }
+
+    /**
+     * Returns the default expression engine.
+     *
+     * @return the default expression engine
+     * @since 1.3
+     */
+    public static synchronized ExpressionEngine getDefaultExpressionEngine()
+    {
+        if (defaultExpressionEngine == null)
+        {
+            defaultExpressionEngine = new DefaultExpressionEngine();
+        }
+        return defaultExpressionEngine;
+    }
+
+    /**
+     * Sets the default expression engine. This expression engine will be used
+     * if no specific engine was set for an instance. It is shared between all
+     * hierarchical configuration instances. So modifying its properties will
+     * impact all instances, for which no specific engine is set.
+     *
+     * @param engine the new default expression engine
+     * @since 1.3
+     */
+    public static synchronized void setDefaultExpressionEngine(ExpressionEngine engine)
+    {
+        if (engine == null)
+        {
+            throw new IllegalArgumentException(
+                    "Default expression engine must not be null!");
+        }
+        defaultExpressionEngine = engine;
+    }
+
+    /**
+     * Returns the expression engine used by this configuration. This method
+     * will never return <b>null</b>; if no specific expression engine was set,
+     * the default expression engine will be returned.
+     *
+     * @return the current expression engine
+     * @since 1.3
+     */
+    public ExpressionEngine getExpressionEngine()
+    {
+        return (expressionEngine != null) ? expressionEngine
+                : getDefaultExpressionEngine();
+    }
+
+    /**
+     * Sets the expression engine to be used by this configuration. All property
+     * keys this configuration has to deal with will be interpreted by this
+     * engine.
+     *
+     * @param expressionEngine the new expression engine; can be <b>null</b>,
+     * then the default expression engine will be used
+     * @since 1.3
+     */
+    public void setExpressionEngine(ExpressionEngine expressionEngine)
+    {
+        this.expressionEngine = expressionEngine;
+    }
+
+    /**
+     * Fetches the specified property. This task is delegated to the associated
+     * expression engine.
+     *
+     * @param key the key to be looked up
+     * @return the found value
+     */
+    public Object getProperty(String key)
+    {
+        List<ConfigurationNode> nodes = fetchNodeList(key);
+
+        if (nodes.size() == 0)
+        {
+            return null;
+        }
+        else
+        {
+            List<Object> list = new ArrayList<Object>();
+            for (ConfigurationNode node : nodes)
+            {
+                if (node.getValue() != null)
+                {
+                    list.add(node.getValue());
+                }
+            }
+
+            if (list.size() < 1)
+            {
+                return null;
+            }
+            else
+            {
+                return (list.size() == 1) ? list.get(0) : list;
+            }
+        }
+    }
+
+    /**
+     * Adds the property with the specified key. This task will be delegated to
+     * the associated {@code ExpressionEngine}, so the passed in key
+     * must match the requirements of this implementation.
+     *
+     * @param key the key of the new property
+     * @param obj the value of the new property
+     */
+    @Override
+    protected void addPropertyDirect(String key, Object obj)
+    {
+        NodeAddData data = getExpressionEngine().prepareAdd(getRootNode(), key);
+        ConfigurationNode node = processNodeAddData(data);
+        node.setValue(obj);
+    }
+
+    /**
+     * Adds a collection of nodes at the specified position of the configuration
+     * tree. This method works similar to {@code addProperty()}, but
+     * instead of a single property a whole collection of nodes can be added -
+     * and thus complete configuration sub trees. E.g. with this method it is
+     * possible to add parts of another {@code HierarchicalConfiguration}
+     * object to this object. (However be aware that a
+     * {@code ConfigurationNode} object can only belong to a single
+     * configuration. So if nodes from one configuration are directly added to
+     * another one using this method, the structure of the source configuration
+     * will be broken. In this case you should clone the nodes to be added
+     * before calling {@code addNodes()}.) If the passed in key refers to
+     * an existing and unique node, the new nodes are added to this node.
+     * Otherwise a new node will be created at the specified position in the
+     * hierarchy.
+     *
+     * @param key the key where the nodes are to be added; can be <b>null </b>,
+     * then they are added to the root node
+     * @param nodes a collection with the {@code Node} objects to be
+     * added
+     */
+    public void addNodes(String key, Collection<? extends ConfigurationNode> nodes)
+    {
+        if (nodes == null || nodes.isEmpty())
+        {
+            return;
+        }
+
+        fireEvent(EVENT_ADD_NODES, key, nodes, true);
+        ConfigurationNode parent;
+        List<ConfigurationNode> target = fetchNodeList(key);
+        if (target.size() == 1)
+        {
+            // existing unique key
+            parent = target.get(0);
+        }
+        else
+        {
+            // otherwise perform an add operation
+            parent = processNodeAddData(getExpressionEngine().prepareAdd(
+                    getRootNode(), key));
+        }
+
+        if (parent.isAttribute())
+        {
+            throw new IllegalArgumentException(
+                    "Cannot add nodes to an attribute node!");
+        }
+
+        for (ConfigurationNode child : nodes)
+        {
+            if (child.isAttribute())
+            {
+                parent.addAttribute(child);
+            }
+            else
+            {
+                parent.addChild(child);
+            }
+            clearReferences(child);
+        }
+        fireEvent(EVENT_ADD_NODES, key, nodes, false);
+    }
+
+    /**
+     * Checks if this configuration is empty. Empty means that there are no keys
+     * with any values, though there can be some (empty) nodes.
+     *
+     * @return a flag if this configuration is empty
+     */
+    public boolean isEmpty()
+    {
+        return !nodeDefined(getRootNode());
+    }
+
+    /**
+     * Creates a new {@code Configuration} object containing all keys
+     * that start with the specified prefix. This implementation will return a
+     * {@code HierarchicalConfiguration} object so that the structure of
+     * the keys will be saved. The nodes selected by the prefix (it is possible
+     * that multiple nodes are selected) are mapped to the root node of the
+     * returned configuration, i.e. their children and attributes will become
+     * children and attributes of the new root node. However a value of the root
+     * node is only set if exactly one of the selected nodes contain a value (if
+     * multiple nodes have a value, there is simply no way to decide how these
+     * values are merged together). Note that the returned
+     * {@code Configuration} object is not connected to its source
+     * configuration: updates on the source configuration are not reflected in
+     * the subset and vice versa.
+     *
+     * @param prefix the prefix of the keys for the subset
+     * @return a new configuration object representing the selected subset
+     */
+    @Override
+    public Configuration subset(String prefix)
+    {
+        Collection<ConfigurationNode> nodes = fetchNodeList(prefix);
+        if (nodes.isEmpty())
+        {
+            return new HierarchicalConfiguration();
+        }
+
+        final HierarchicalConfiguration parent = this;
+        HierarchicalConfiguration result = new HierarchicalConfiguration()
+        {
+            // Override interpolate to always interpolate on the parent
+            @Override
+            protected Object interpolate(Object value)
+            {
+                return parent.interpolate(value);
+            }
+        };
+        CloneVisitor visitor = new CloneVisitor();
+
+        // Initialize the new root node
+        Object value = null;
+        int valueCount = 0;
+        for (ConfigurationNode nd : nodes)
+        {
+            if (nd.getValue() != null)
+            {
+                value = nd.getValue();
+                valueCount++;
+            }
+            nd.visit(visitor);
+
+            for (ConfigurationNode c : visitor.getClone().getChildren())
+            {
+                result.getRootNode().addChild(c);
+            }
+            for (ConfigurationNode attr : visitor.getClone().getAttributes())
+            {
+                result.getRootNode().addAttribute(attr);
+            }
+        }
+
+        // Determine the value of the new root
+        if (valueCount == 1)
+        {
+            result.getRootNode().setValue(value);
+        }
+        return (result.isEmpty()) ? new HierarchicalConfiguration() : result;
+    }
+
+    /**
+     * <p>
+     * Returns a hierarchical subnode configuration object that wraps the
+     * configuration node specified by the given key. This method provides an
+     * easy means of accessing sub trees of a hierarchical configuration. In the
+     * returned configuration the sub tree can directly be accessed, it becomes
+     * the root node of this configuration. Because of this the passed in key
+     * must select exactly one configuration node; otherwise an
+     * {@code IllegalArgumentException} will be thrown.
+     * </p>
+     * <p>
+     * The difference between this method and the
+     * {@link #subset(String)} method is that
+     * {@code subset()} supports arbitrary subsets of configuration nodes
+     * while {@code configurationAt()} only returns a single sub tree.
+     * Please refer to the documentation of the
+     * {@code SubnodeConfiguration} class to obtain further information
+     * about subnode configurations and when they should be used.
+     * </p>
+     * <p>
+     * With the {@code supportUpdate} flag the behavior of the returned
+     * {@code SubnodeConfiguration} regarding updates of its parent
+     * configuration can be determined. A subnode configuration operates on the
+     * same nodes as its parent, so changes at one configuration are normally
+     * directly visible for the other configuration. There are however changes
+     * of the parent configuration, which are not recognized by the subnode
+     * configuration per default. An example for this is a reload operation (for
+     * file-based configurations): Here the complete node set of the parent
+     * configuration is replaced, but the subnode configuration still references
+     * the old nodes. If such changes should be detected by the subnode
+     * configuration, the {@code supportUpdates} flag must be set to
+     * <b>true</b>. This causes the subnode configuration to reevaluate the key
+     * used for its creation each time it is accessed. This guarantees that the
+     * subnode configuration always stays in sync with its key, even if the
+     * parent configuration's data significantly changes. If such a change
+     * makes the key invalid - because it now no longer points to exactly one
+     * node -, the subnode configuration is not reconstructed, but keeps its
+     * old data. It is then quasi detached from its parent.
+     * </p>
+     *
+     * @param key the key that selects the sub tree
+     * @param supportUpdates a flag whether the returned subnode configuration
+     * should be able to handle updates of its parent
+     * @return a hierarchical configuration that contains this sub tree
+     * @see SubnodeConfiguration
+     * @since 1.5
+     */
+    public SubnodeConfiguration configurationAt(String key,
+            boolean supportUpdates)
+    {
+        List<ConfigurationNode> nodes = fetchNodeList(key);
+        if (nodes.size() != 1)
+        {
+            throw new IllegalArgumentException(
+                    "Passed in key must select exactly one node: " + key);
+        }
+        return supportUpdates ? createSubnodeConfiguration(
+                nodes.get(0), key)
+                : createSubnodeConfiguration(nodes.get(0));
+    }
+
+    /**
+     * Returns a hierarchical subnode configuration for the node specified by
+     * the given key. This is a short form for {@code configurationAt(key,
+     * <b>false</b>)}.
+     *
+     * @param key the key that selects the sub tree
+     * @return a hierarchical configuration that contains this sub tree
+     * @see SubnodeConfiguration
+     * @since 1.3
+     */
+    public SubnodeConfiguration configurationAt(String key)
+    {
+        return configurationAt(key, false);
+    }
+
+    /**
+     * Returns a list of sub configurations for all configuration nodes selected
+     * by the given key. This method will evaluate the passed in key (using the
+     * current {@code ExpressionEngine}) and then create a subnode
+     * configuration for each returned node (like
+     * {@link #configurationAt(String)}}). This is especially
+     * useful when dealing with list-like structures. As an example consider the
+     * configuration that contains data about database tables and their fields.
+     * If you need access to all fields of a certain table, you can simply do
+     *
+     * <pre>
+     * List fields = config.configurationsAt("tables.table(0).fields.field");
+     * for(Iterator it = fields.iterator(); it.hasNext();)
+     * {
+     *     HierarchicalConfiguration sub = (HierarchicalConfiguration) it.next();
+     *     // now the children and attributes of the field node can be
+     *     // directly accessed
+     *     String fieldName = sub.getString("name");
+     *     String fieldType = sub.getString("type");
+     *     ...
+     * </pre>
+     *
+     * @param key the key for selecting the desired nodes
+     * @return a list with hierarchical configuration objects; each
+     * configuration represents one of the nodes selected by the passed in key
+     * @since 1.3
+     */
+    public List<HierarchicalConfiguration> configurationsAt(String key)
+    {
+        List<ConfigurationNode> nodes = fetchNodeList(key);
+        List<HierarchicalConfiguration> configs = new ArrayList<HierarchicalConfiguration>(nodes.size());
+        for (ConfigurationNode node : nodes)
+        {
+            configs.add(createSubnodeConfiguration(node));
+        }
+        return configs;
+    }
+
+    /**
+     * Creates a subnode configuration for the specified node. This method is
+     * called by {@code configurationAt()} and
+     * {@code configurationsAt()}.
+     *
+     * @param node the node, for which a subnode configuration is to be created
+     * @return the configuration for the given node
+     * @since 1.3
+     */
+    protected SubnodeConfiguration createSubnodeConfiguration(ConfigurationNode node)
+    {
+        SubnodeConfiguration result = new SubnodeConfiguration(this, node);
+        registerSubnodeConfiguration(result);
+        return result;
+    }
+
+    /**
+     * Creates a new subnode configuration for the specified node and sets its
+     * construction key. A subnode configuration created this way will be aware
+     * of structural changes of its parent.
+     *
+     * @param node the node, for which a subnode configuration is to be created
+     * @param subnodeKey the key used to construct the configuration
+     * @return the configuration for the given node
+     * @since 1.5
+     */
+    protected SubnodeConfiguration createSubnodeConfiguration(
+            ConfigurationNode node, String subnodeKey)
+    {
+        SubnodeConfiguration result = createSubnodeConfiguration(node);
+        result.setSubnodeKey(subnodeKey);
+        return result;
+    }
+
+    /**
+     * This method is always called when a subnode configuration created from
+     * this configuration has been modified. This implementation transforms the
+     * received event into an event of type {@code EVENT_SUBNODE_CHANGED}
+     * and notifies the registered listeners.
+     *
+     * @param event the event describing the change
+     * @since 1.5
+     */
+    protected void subnodeConfigurationChanged(ConfigurationEvent event)
+    {
+        fireEvent(EVENT_SUBNODE_CHANGED, null, event, event.isBeforeUpdate());
+    }
+
+    /**
+     * Registers this instance at the given subnode configuration. This
+     * implementation will register a change listener, so that modifications of
+     * the subnode configuration can be tracked.
+     *
+     * @param config the subnode configuration
+     * @since 1.5
+     */
+    void registerSubnodeConfiguration(SubnodeConfiguration config)
+    {
+        config.addConfigurationListener(new ConfigurationListener()
+        {
+            public void configurationChanged(ConfigurationEvent event)
+            {
+                subnodeConfigurationChanged(event);
+            }
+        });
+    }
+
+    /**
+     * Checks if the specified key is contained in this configuration. Note that
+     * for this configuration the term "contained" means that the key
+     * has an associated value. If there is a node for this key that has no
+     * value but children (either defined or undefined), this method will still
+     * return <b>false </b>.
+     *
+     * @param key the key to be chekced
+     * @return a flag if this key is contained in this configuration
+     */
+    public boolean containsKey(String key)
+    {
+        return getProperty(key) != null;
+    }
+
+    /**
+     * Sets the value of the specified property.
+     *
+     * @param key the key of the property to set
+     * @param value the new value of this property
+     */
+    @Override
+    public void setProperty(String key, Object value)
+    {
+        fireEvent(EVENT_SET_PROPERTY, key, value, true);
+
+        // Update the existing nodes for this property
+        Iterator<ConfigurationNode> itNodes = fetchNodeList(key).iterator();
+        Iterator<?> itValues;
+        if (!isDelimiterParsingDisabled() || !(value instanceof String))
+        {
+            itValues = PropertyConverter.toIterator(value, getListDelimiter());
+        }
+        else
+        {
+            itValues = Collections.singleton(value).iterator();
+        }
+
+        while (itNodes.hasNext() && itValues.hasNext())
+        {
+            itNodes.next().setValue(itValues.next());
+        }
+
+        // Add additional nodes if necessary
+        while (itValues.hasNext())
+        {
+            addPropertyDirect(key, itValues.next());
+        }
+
+        // Remove remaining nodes
+        while (itNodes.hasNext())
+        {
+            clearNode(itNodes.next());
+        }
+
+        fireEvent(EVENT_SET_PROPERTY, key, value, false);
+    }
+
+    /**
+     * Clears this configuration. This is a more efficient implementation than
+     * the one inherited from the base class. It directly removes all data from
+     * the root node.
+     */
+    @Override
+    public void clear()
+    {
+        fireEvent(EVENT_CLEAR, null, null, true);
+        getRootNode().removeAttributes();
+        getRootNode().removeChildren();
+        getRootNode().setValue(null);
+        fireEvent(EVENT_CLEAR, null, null, false);
+    }
+
+    /**
+     * Removes all values of the property with the given name and of keys that
+     * start with this name. So if there is a property with the key
+     * "foo" and a property with the key "foo.bar", a call
+     * of {@code clearTree("foo")} would remove both properties.
+     *
+     * @param key the key of the property to be removed
+     */
+    public void clearTree(String key)
+    {
+        fireEvent(EVENT_CLEAR_TREE, key, null, true);
+        List<ConfigurationNode> nodes = fetchNodeList(key);
+
+        for (ConfigurationNode node : nodes)
+        {
+            removeNode(node);
+        }
+        fireEvent(EVENT_CLEAR_TREE, key, nodes, false);
+    }
+
+    /**
+     * Removes the property with the given key. Properties with names that start
+     * with the given key (i.e. properties below the specified key in the
+     * hierarchy) won't be affected.
+     *
+     * @param key the key of the property to be removed
+     */
+    @Override
+    public void clearProperty(String key)
+    {
+        fireEvent(EVENT_CLEAR_PROPERTY, key, null, true);
+        List<ConfigurationNode> nodes = fetchNodeList(key);
+
+        for (ConfigurationNode node : nodes)
+        {
+            clearNode(node);
+        }
+
+        fireEvent(EVENT_CLEAR_PROPERTY, key, null, false);
+    }
+
+    /**
+     * Returns an iterator with all keys defined in this configuration.
+     * Note that the keys returned by this method will not contain any
+     * indices. This means that some structure will be lost.</p>
+     *
+     * @return an iterator with the defined keys in this configuration
+     */
+    public Iterator<String> getKeys()
+    {
+        DefinedKeysVisitor visitor = new DefinedKeysVisitor();
+        getRootNode().visit(visitor);
+
+        return visitor.getKeyList().iterator();
+    }
+
+    /**
+     * Returns an iterator with all keys defined in this configuration that
+     * start with the given prefix. The returned keys will not contain any
+     * indices. This implementation tries to locate a node whose key is the same
+     * as the passed in prefix. Then the subtree of this node is traversed, and
+     * the keys of all nodes encountered (including attributes) are added to the
+     * result set.
+     *
+     * @param prefix the prefix of the keys to start with
+     * @return an iterator with the found keys
+     */
+    @Override
+    public Iterator<String> getKeys(String prefix)
+    {
+        DefinedKeysVisitor visitor = new DefinedKeysVisitor(prefix);
+        if (containsKey(prefix))
+        {
+            // explicitly add the prefix
+            visitor.getKeyList().add(prefix);
+        }
+
+        List<ConfigurationNode> nodes = fetchNodeList(prefix);
+
+        for (ConfigurationNode node : nodes)
+        {
+            for (ConfigurationNode c : node.getChildren())
+            {
+                c.visit(visitor);
+            }
+            for (ConfigurationNode attr : node.getAttributes())
+            {
+                attr.visit(visitor);
+            }
+        }
+
+        return visitor.getKeyList().iterator();
+    }
+
+    /**
+     * Returns the maximum defined index for the given key. This is useful if
+     * there are multiple values for this key. They can then be addressed
+     * separately by specifying indices from 0 to the return value of this
+     * method.
+     *
+     * @param key the key to be checked
+     * @return the maximum defined index for this key
+     */
+    public int getMaxIndex(String key)
+    {
+        return fetchNodeList(key).size() - 1;
+    }
+
+    /**
+     * Creates a copy of this object. This new configuration object will contain
+     * copies of all nodes in the same structure. Registered event listeners
+     * won't be cloned; so they are not registered at the returned copy.
+     *
+     * @return the copy
+     * @since 1.2
+     */
+    @Override
+    public Object clone()
+    {
+        try
+        {
+            HierarchicalConfiguration copy = (HierarchicalConfiguration) super
+                    .clone();
+
+            // clone the nodes, too
+            CloneVisitor v = new CloneVisitor();
+            getRootNode().visit(v);
+            copy.setRootNode(v.getClone());
+
+            return copy;
+        }
+        catch (CloneNotSupportedException cex)
+        {
+            // should not happen
+            throw new ConfigurationRuntimeException(cex);
+        }
+    }
+
+    /**
+     * Returns a configuration with the same content as this configuration, but
+     * with all variables replaced by their actual values. This implementation
+     * is specific for hierarchical configurations. It clones the current
+     * configuration and runs a specialized visitor on the clone, which performs
+     * interpolation on the single configuration nodes.
+     *
+     * @return a configuration with all variables interpolated
+     * @since 1.5
+     */
+    @Override
+    public Configuration interpolatedConfiguration()
+    {
+        HierarchicalConfiguration c = (HierarchicalConfiguration) clone();
+        c.getRootNode().visit(new ConfigurationNodeVisitorAdapter()
+        {
+            @Override
+            public void visitAfterChildren(ConfigurationNode node)
+            {
+                node.setValue(interpolate(node.getValue()));
+            }
+        });
+        return c;
+    }
+
+    /**
+     * Helper method for fetching a list of all nodes that are addressed by the
+     * specified key.
+     *
+     * @param key the key
+     * @return a list with all affected nodes (never <b>null </b>)
+     */
+    protected List<ConfigurationNode> fetchNodeList(String key)
+    {
+        return getExpressionEngine().query(getRootNode(), key);
+    }
+
+    /**
+     * Recursive helper method for fetching a property. This method processes
+     * all facets of a configuration key, traverses the tree of properties and
+     * fetches the the nodes of all matching properties.
+     *
+     * @param keyPart the configuration key iterator
+     * @param node the actual node
+     * @param nodes here the found nodes are stored
+     * @deprecated Property keys are now evaluated by the expression engine
+     * associated with the configuration; this method will no longer be called.
+     * If you want to modify the way properties are looked up, consider
+     * implementing you own {@code ExpressionEngine} implementation.
+     */
+    @Deprecated
+    protected void findPropertyNodes(ConfigurationKey.KeyIterator keyPart,
+            Node node, Collection<ConfigurationNode> nodes)
+    {
+    }
+
+    /**
+     * Checks if the specified node is defined.
+     *
+     * @param node the node to be checked
+     * @return a flag if this node is defined
+     * @deprecated Use the method {@link #nodeDefined(ConfigurationNode)}
+     * instead.
+     */
+    @Deprecated
+    protected boolean nodeDefined(Node node)
+    {
+        return nodeDefined((ConfigurationNode) node);
+    }
+
+    /**
+     * Checks if the specified node is defined.
+     *
+     * @param node the node to be checked
+     * @return a flag if this node is defined
+     */
+    protected boolean nodeDefined(ConfigurationNode node)
+    {
+        DefinedVisitor visitor = new DefinedVisitor();
+        node.visit(visitor);
+        return visitor.isDefined();
+    }
+
+    /**
+     * Removes the specified node from this configuration. This method ensures
+     * that parent nodes that become undefined by this operation are also
+     * removed.
+     *
+     * @param node the node to be removed
+     * @deprecated Use the method {@link #removeNode(ConfigurationNode)}
+     * instead.
+     */
+    @Deprecated
+    protected void removeNode(Node node)
+    {
+        removeNode((ConfigurationNode) node);
+    }
+
+    /**
+     * Removes the specified node from this configuration. This method ensures
+     * that parent nodes that become undefined by this operation are also
+     * removed.
+     *
+     * @param node the node to be removed
+     */
+    protected void removeNode(ConfigurationNode node)
+    {
+        ConfigurationNode parent = node.getParentNode();
+        if (parent != null)
+        {
+            parent.removeChild(node);
+            if (!nodeDefined(parent))
+            {
+                removeNode(parent);
+            }
+        }
+    }
+
+    /**
+     * Clears the value of the specified node. If the node becomes undefined by
+     * this operation, it is removed from the hierarchy.
+     *
+     * @param node the node to be cleared
+     * @deprecated Use the method {@link #clearNode(ConfigurationNode)}
+     * instead
+     */
+    @Deprecated
+    protected void clearNode(Node node)
+    {
+        clearNode((ConfigurationNode) node);
+    }
+
+    /**
+     * Clears the value of the specified node. If the node becomes undefined by
+     * this operation, it is removed from the hierarchy.
+     *
+     * @param node the node to be cleared
+     */
+    protected void clearNode(ConfigurationNode node)
+    {
+        node.setValue(null);
+        if (!nodeDefined(node))
+        {
+            removeNode(node);
+        }
+    }
+
+    /**
+     * Returns a reference to the parent node of an add operation. Nodes for new
+     * properties can be added as children of this node. If the path for the
+     * specified key does not exist so far, it is created now.
+     *
+     * @param keyIt the iterator for the key of the new property
+     * @param startNode the node to start the search with
+     * @return the parent node for the add operation
+     * @deprecated Adding new properties is now to a major part delegated to the
+     * {@code ExpressionEngine} associated with this configuration instance.
+     * This method will no longer be called. Developers who want to modify the
+     * process of adding new properties should consider implementing their own
+     * expression engine.
+     */
+    @Deprecated
+    protected Node fetchAddNode(ConfigurationKey.KeyIterator keyIt, Node startNode)
+    {
+        return null;
+    }
+
+    /**
+     * Finds the last existing node for an add operation. This method traverses
+     * the configuration tree along the specified key. The last existing node on
+     * this path is returned.
+     *
+     * @param keyIt the key iterator
+     * @param node the actual node
+     * @return the last existing node on the given path
+     * @deprecated Adding new properties is now to a major part delegated to the
+     * {@code ExpressionEngine} associated with this configuration instance.
+     * This method will no longer be called. Developers who want to modify the
+     * process of adding new properties should consider implementing their own
+     * expression engine.
+     */
+    @Deprecated
+    protected Node findLastPathNode(ConfigurationKey.KeyIterator keyIt, Node node)
+    {
+        return null;
+    }
+
+    /**
+     * Creates the missing nodes for adding a new property. This method ensures
+     * that there are corresponding nodes for all components of the specified
+     * configuration key.
+     *
+     * @param keyIt the key iterator
+     * @param root the base node of the path to be created
+     * @return the last node of the path
+     * @deprecated Adding new properties is now to a major part delegated to the
+     * {@code ExpressionEngine} associated with this configuration instance.
+     * This method will no longer be called. Developers who want to modify the
+     * process of adding new properties should consider implementing their own
+     * expression engine.
+     */
+    @Deprecated
+    protected Node createAddPath(ConfigurationKey.KeyIterator keyIt, Node root)
+    {
+        return null;
+    }
+
+    /**
+     * Creates a new {@code Node} object with the specified name. This
+     * method can be overloaded in derived classes if a specific node type is
+     * needed. This base implementation always returns a new object of the
+     * {@code Node} class.
+     *
+     * @param name the name of the new node
+     * @return the new node
+     */
+    protected Node createNode(String name)
+    {
+        return new Node(name);
+    }
+
+    /**
+     * Helper method for processing a node add data object obtained from the
+     * expression engine. This method will create all new nodes.
+     *
+     * @param data the data object
+     * @return the new node
+     * @since 1.3
+     */
+    private ConfigurationNode processNodeAddData(NodeAddData data)
+    {
+        ConfigurationNode node = data.getParent();
+
+        // Create missing nodes on the path
+        for (String name : data.getPathNodes())
+        {
+            ConfigurationNode child = createNode(name);
+            node.addChild(child);
+            node = child;
+        }
+
+        // Add new target node
+        ConfigurationNode child = createNode(data.getNewNodeName());
+        if (data.isAttribute())
+        {
+            node.addAttribute(child);
+        }
+        else
+        {
+            node.addChild(child);
+        }
+        return child;
+    }
+
+    /**
+     * Clears all reference fields in a node structure. A configuration node can
+     * store a so-called "reference". The meaning of this data is
+     * determined by a concrete sub class. Typically such references are
+     * specific for a configuration instance. If this instance is cloned or
+     * copied, they must be cleared. This can be done using this method.
+     *
+     * @param node the root node of the node hierarchy, in which the references
+     * are to be cleared
+     * @since 1.4
+     */
+    protected static void clearReferences(ConfigurationNode node)
+    {
+        node.visit(new ConfigurationNodeVisitorAdapter()
+        {
+            @Override
+            public void visitBeforeChildren(ConfigurationNode node)
+            {
+                node.setReference(null);
+            }
+        });
+    }
+
+    /**
+     * Transforms the specified object into a Node. This method treats view
+     * nodes in a special way. This is necessary because ViewNode does not
+     * extend HierarchicalConfiguration.Node; thus the API for the node visitor
+     * is slightly different. Therefore a view node is transformed into a
+     * special compatibility Node object.
+     *
+     * @param obj the original node object
+     * @return the node to be used
+     */
+    private static Node getNodeFor(Object obj)
+    {
+        Node nd;
+        if (obj instanceof ViewNode)
+        {
+            final ViewNode viewNode = (ViewNode) obj;
+            nd = new Node(viewNode)
+            {
+                @Override
+                public void setReference(Object reference)
+                {
+                    super.setReference(reference);
+                    // also set the reference at the original node
+                    viewNode.setReference(reference);
+                }
+            };
+        }
+        else
+        {
+            nd = (Node) obj;
+        }
+        return nd;
+    }
+
+    /**
+     * A data class for storing (hierarchical) property information. A property
+     * can have a value and an arbitrary number of child properties. From
+     * version 1.3 on this class is only a thin wrapper over the
+     * {@link org.apache.commons.configuration.tree.DefaultConfigurationNode DefaultconfigurationNode}
+     * class that exists mainly for the purpose of backwards compatibility.
+     */
+    public static class Node extends DefaultConfigurationNode implements Serializable
+    {
+        /**
+         * The serial version UID.
+         */
+        private static final long serialVersionUID = -6357500633536941775L;
+
+        /**
+         * Creates a new instance of {@code Node}.
+         */
+        public Node()
+        {
+            super();
+        }
+
+        /**
+         * Creates a new instance of {@code Node} and sets the name.
+         *
+         * @param name the node's name
+         */
+        public Node(String name)
+        {
+            super(name);
+        }
+
+        /**
+         * Creates a new instance of {@code Node} and sets the name and the value.
+         *
+         * @param name the node's name
+         * @param value the value
+         */
+        public Node(String name, Object value)
+        {
+            super(name, value);
+        }
+
+        /**
+         * Creates a new instance of {@code Node} based on the given
+         * source node. All properties of the source node, including its
+         * children and attributes, will be copied.
+         *
+         * @param src the node to be copied
+         */
+        public Node(ConfigurationNode src)
+        {
+            this(src.getName(), src.getValue());
+            setReference(src.getReference());
+            for (ConfigurationNode nd : src.getChildren())
+            {
+                // Don't change the parent node
+                ConfigurationNode parent = nd.getParentNode();
+                addChild(nd);
+                nd.setParentNode(parent);
+            }
+
+            for (ConfigurationNode nd : src.getAttributes())
+            {
+                // Don't change the parent node
+                ConfigurationNode parent = nd.getParentNode();
+                addAttribute(nd);
+                nd.setParentNode(parent);
+            }
+        }
+
+        /**
+         * Returns the parent of this node.
+         *
+         * @return this node's parent (can be <b>null</b>)
+         */
+        public Node getParent()
+        {
+            return (Node) getParentNode();
+        }
+
+        /**
+         * Sets the parent of this node.
+         *
+         * @param node the parent node
+         */
+        public void setParent(Node node)
+        {
+            setParentNode(node);
+        }
+
+        /**
+         * Adds the given node to the children of this node.
+         *
+         * @param node the child to be added
+         */
+        public void addChild(Node node)
+        {
+            addChild((ConfigurationNode) node);
+        }
+
+        /**
+         * Returns a flag whether this node has child elements.
+         *
+         * @return <b>true</b> if there is a child node, <b>false</b> otherwise
+         */
+        public boolean hasChildren()
+        {
+            return getChildrenCount() > 0 || getAttributeCount() > 0;
+        }
+
+        /**
+         * Removes the specified child from this node.
+         *
+         * @param child the child node to be removed
+         * @return a flag if the child could be found
+         */
+        public boolean remove(Node child)
+        {
+            return child.isAttribute() ? removeAttribute(child) : removeChild(child);
+        }
+
+        /**
+         * Removes all children with the given name.
+         *
+         * @param name the name of the children to be removed
+         * @return a flag if children with this name existed
+         */
+        public boolean remove(String name)
+        {
+            boolean childrenRemoved = removeChild(name);
+            boolean attrsRemoved = removeAttribute(name);
+            return childrenRemoved || attrsRemoved;
+        }
+
+        /**
+         * A generic method for traversing this node and all of its children.
+         * This method sends the passed in visitor to this node and all of its
+         * children.
+         *
+         * @param visitor the visitor
+         * @param key here a configuration key with the name of the root node of
+         * the iteration can be passed; if this key is not <b>null </b>, the
+         * full paths to the visited nodes are builded and passed to the
+         * visitor's {@code visit()} methods
+         */
+        public void visit(NodeVisitor visitor, ConfigurationKey key)
+        {
+            int length = 0;
+            if (key != null)
+            {
+                length = key.length();
+                if (getName() != null)
+                {
+                    key
+                            .append(StringUtils
+                                    .replace(
+                                            isAttribute() ? ConfigurationKey
+                                                    .constructAttributeKey(getName())
+                                                    : getName(),
+                                            String
+                                                    .valueOf(ConfigurationKey.PROPERTY_DELIMITER),
+                                            ConfigurationKey.ESCAPED_DELIMITER));
+                }
+            }
+
+            visitor.visitBeforeChildren(this, key);
+
+            for (Iterator<ConfigurationNode> it = getChildren().iterator(); it.hasNext()
+                    && !visitor.terminate();)
+            {
+                Object obj = it.next();
+                getNodeFor(obj).visit(visitor, key);
+            }
+            for (Iterator<ConfigurationNode> it = getAttributes().iterator(); it.hasNext()
+                    && !visitor.terminate();)
+            {
+                Object obj = it.next();
+                getNodeFor(obj).visit(visitor, key);
+            }
+
+            visitor.visitAfterChildren(this, key);
+            if (key != null)
+            {
+                key.setLength(length);
+            }
+        }
+    }
+
+    /**
+     * <p>Definition of a visitor class for traversing a node and all of its
+     * children.</p><p>This class defines the interface of a visitor for
+     * {@code Node} objects and provides a default implementation. The
+     * method {@code visit()} of {@code Node} implements a generic
+     * iteration algorithm based on the <em>Visitor</em> pattern. By providing
+     * different implementations of visitors it is possible to collect different
+     * data during the iteration process.</p>
+     *
+     */
+    public static class NodeVisitor
+    {
+        /**
+         * Visits the specified node. This method is called during iteration for
+         * each node before its children have been visited.
+         *
+         * @param node the actual node
+         * @param key the key of this node (may be <b>null </b>)
+         */
+        public void visitBeforeChildren(Node node, ConfigurationKey key)
+        {
+        }
+
+        /**
+         * Visits the specified node after its children have been processed.
+         * This gives a visitor the opportunity of collecting additional data
+         * after the child nodes have been visited.
+         *
+         * @param node the node to be visited
+         * @param key the key of this node (may be <b>null </b>)
+         */
+        public void visitAfterChildren(Node node, ConfigurationKey key)
+        {
+        }
+
+        /**
+         * Returns a flag that indicates if iteration should be stopped. This
+         * method is called after each visited node. It can be useful for
+         * visitors that search a specific node. If this node is found, the
+         * whole process can be stopped. This base implementation always returns
+         * <b>false </b>.
+         *
+         * @return a flag if iteration should be stopped
+         */
+        public boolean terminate()
+        {
+            return false;
+        }
+    }
+
+    /**
+     * A specialized visitor that checks if a node is defined.
+     * "Defined" in this terms means that the node or at least one of
+     * its sub nodes is associated with a value.
+     *
+     */
+    static class DefinedVisitor extends ConfigurationNodeVisitorAdapter
+    {
+        /** Stores the defined flag. */
+        private boolean defined;
+
+        /**
+         * Checks if iteration should be stopped. This can be done if the first
+         * defined node is found.
+         *
+         * @return a flag if iteration should be stopped
+         */
+        @Override
+        public boolean terminate()
+        {
+            return isDefined();
+        }
+
+        /**
+         * Visits the node. Checks if a value is defined.
+         *
+         * @param node the actual node
+         */
+        @Override
+        public void visitBeforeChildren(ConfigurationNode node)
+        {
+            defined = node.getValue() != null;
+        }
+
+        /**
+         * Returns the defined flag.
+         *
+         * @return the defined flag
+         */
+        public boolean isDefined()
+        {
+            return defined;
+        }
+    }
+
+    /**
+     * A specialized visitor that fills a list with keys that are defined in a
+     * node hierarchy.
+     */
+    class DefinedKeysVisitor extends ConfigurationNodeVisitorAdapter
+    {
+        /** Stores the list to be filled. */
+        private Set<String> keyList;
+
+        /** A stack with the keys of the already processed nodes. */
+        private Stack<String> parentKeys;
+
+        /**
+         * Default constructor.
+         */
+        public DefinedKeysVisitor()
+        {
+            keyList = new LinkedHashSet<String>();
+            parentKeys = new Stack<String>();
+        }
+
+        /**
+         * Creates a new {@code DefinedKeysVisitor} instance and sets the
+         * prefix for the keys to fetch.
+         *
+         * @param prefix the prefix
+         */
+        public DefinedKeysVisitor(String prefix)
+        {
+            this();
+            parentKeys.push(prefix);
+        }
+
+        /**
+         * Returns the list with all defined keys.
+         *
+         * @return the list with the defined keys
+         */
+        public Set<String> getKeyList()
+        {
+            return keyList;
+        }
+
+        /**
+         * Visits the node after its children has been processed. Removes this
+         * node's key from the stack.
+         *
+         * @param node the node
+         */
+        @Override
+        public void visitAfterChildren(ConfigurationNode node)
+        {
+            parentKeys.pop();
+        }
+
+        /**
+         * Visits the specified node. If this node has a value, its key is added
+         * to the internal list.
+         *
+         * @param node the node to be visited
+         */
+        @Override
+        public void visitBeforeChildren(ConfigurationNode node)
+        {
+            String parentKey = parentKeys.isEmpty() ? null
+                    : (String) parentKeys.peek();
+            String key = getExpressionEngine().nodeKey(node, parentKey);
+            parentKeys.push(key);
+            if (node.getValue() != null)
+            {
+                keyList.add(key);
+            }
+        }
+    }
+
+    /**
+     * A specialized visitor that is able to create a deep copy of a node
+     * hierarchy.
+     */
+    static class CloneVisitor extends ConfigurationNodeVisitorAdapter
+    {
+        /** A stack with the actual object to be copied. */
+        private Stack<ConfigurationNode> copyStack;
+
+        /** Stores the result of the clone process. */
+        private ConfigurationNode result;
+
+        /**
+         * Creates a new instance of {@code CloneVisitor}.
+         */
+        public CloneVisitor()
+        {
+            copyStack = new Stack<ConfigurationNode>();
+        }
+
+        /**
+         * Visits the specified node after its children have been processed.
+         *
+         * @param node the node
+         */
+        @Override
+        public void visitAfterChildren(ConfigurationNode node)
+        {
+            ConfigurationNode copy = copyStack.pop();
+            if (copyStack.isEmpty())
+            {
+                result = copy;
+            }
+        }
+
+        /**
+         * Visits and copies the specified node.
+         *
+         * @param node the node
+         */
+        @Override
+        public void visitBeforeChildren(ConfigurationNode node)
+        {
+            ConfigurationNode copy = (ConfigurationNode) node.clone();
+            copy.setParentNode(null);
+
+            if (!copyStack.isEmpty())
+            {
+                if (node.isAttribute())
+                {
+                    copyStack.peek().addAttribute(copy);
+                }
+                else
+                {
+                    copyStack.peek().addChild(copy);
+                }
+            }
+
+            copyStack.push(copy);
+        }
+
+        /**
+         * Returns the result of the clone process. This is the root node of the
+         * cloned node hierarchy.
+         *
+         * @return the cloned root node
+         */
+        public ConfigurationNode getClone()
+        {
+            return result;
+        }
+    }
+
+    /**
+     * A specialized visitor base class that can be used for storing the tree of
+     * configuration nodes. The basic idea is that each node can be associated
+     * with a reference object. This reference object has a concrete meaning in
+     * a derived class, e.g. an entry in a JNDI context or an XML element. When
+     * the configuration tree is set up, the {@code load()} method is
+     * responsible for setting the reference objects. When the configuration
+     * tree is later modified, new nodes do not have a defined reference object.
+     * This visitor class processes all nodes and finds the ones without a
+     * defined reference object. For those nodes the {@code insert()}
+     * method is called, which must be defined in concrete sub classes. This
+     * method can perform all steps to integrate the new node into the original
+     * structure.
+     *
+     */
+    protected abstract static class BuilderVisitor extends NodeVisitor
+    {
+        /**
+         * Visits the specified node before its children have been traversed.
+         *
+         * @param node the node to visit
+         * @param key the current key
+         */
+        @Override
+        public void visitBeforeChildren(Node node, ConfigurationKey key)
+        {
+            Collection<ConfigurationNode> subNodes = new LinkedList<ConfigurationNode>(node.getChildren());
+            subNodes.addAll(node.getAttributes());
+            Iterator<ConfigurationNode> children = subNodes.iterator();
+            Node sibling1 = null;
+            Node nd = null;
+
+            while (children.hasNext())
+            {
+                // find the next new node
+                do
+                {
+                    sibling1 = nd;
+                    Object obj = children.next();
+                    nd = getNodeFor(obj);
+                } while (nd.getReference() != null && children.hasNext());
+
+                if (nd.getReference() == null)
+                {
+                    // find all following new nodes
+                    List<Node> newNodes = new LinkedList<Node>();
+                    newNodes.add(nd);
+                    while (children.hasNext())
+                    {
+                        Object obj = children.next();
+                        nd = getNodeFor(obj);
+                        if (nd.getReference() == null)
+                        {
+                            newNodes.add(nd);
+                        }
+                        else
+                        {
+                            break;
+                        }
+                    }
+
+                    // Insert all new nodes
+                    Node sibling2 = (nd.getReference() == null) ? null : nd;
+                    for (Node insertNode : newNodes)
+                    {
+                        if (insertNode.getReference() == null)
+                        {
+                            Object ref = insert(insertNode, node, sibling1, sibling2);
+                            if (ref != null)
+                            {
+                                insertNode.setReference(ref);
+                            }
+                            sibling1 = insertNode;
+                        }
+                    }
+                }
+            }
+        }
+
+        /**
+         * Inserts a new node into the structure constructed by this builder.
+         * This method is called for each node that has been added to the
+         * configuration tree after the configuration has been loaded from its
+         * source. These new nodes have to be inserted into the original
+         * structure. The passed in nodes define the position of the node to be
+         * inserted: its parent and the siblings between to insert. The return
+         * value is interpreted as the new reference of the affected
+         * {@code Node} object; if it is not <b>null </b>, it is passed
+         * to the node's {@code setReference()} method.
+         *
+         * @param newNode the node to be inserted
+         * @param parent the parent node
+         * @param sibling1 the sibling after which the node is to be inserted;
+         * can be <b>null </b> if the new node is going to be the first child
+         * node
+         * @param sibling2 the sibling before which the node is to be inserted;
+         * can be <b>null </b> if the new node is going to be the last child
+         * node
+         * @return the reference object for the node to be inserted
+         */
+        protected abstract Object insert(Node newNode, Node parent, Node sibling1, Node sibling2);
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/HierarchicalConfigurationConverter.java b/src/main/java/org/apache/commons/configuration/HierarchicalConfigurationConverter.java
new file mode 100644
index 0000000..de04775
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/HierarchicalConfigurationConverter.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * <p>A base class for converters that transform a normal configuration
+ * object into a hierarchical configuration.</p>
+ * <p>This class provides a default mechanism for iterating over the keys in a
+ * configuration and to throw corresponding element start and end events. By
+ * handling these events a hierarchy can be constructed that is equivalent to
+ * the keys in the original configuration.</p>
+ * <p>Concrete sub classes will implement event handlers that generate SAX
+ * events for XML processing or construct a
+ * {@code HierarchicalConfiguration} root node. All in all with this class
+ * it is possible to treat a default configuration as if it was a hierarchical
+ * configuration, which can be sometimes useful.</p>
+ * @see HierarchicalConfiguration
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a>
+ * @version $Id: HierarchicalConfigurationConverter.java 1234985 2012-01-23 21:09:09Z oheger $
+ */
+abstract class HierarchicalConfigurationConverter
+{
+    /**
+     * Processes the specified configuration object. This method implements
+     * the iteration over the configuration's keys. All defined keys are
+     * translated into a set of element start and end events represented by
+     * calls to the {@code elementStart()} and
+     * {@code elementEnd()} methods.
+     *
+     * @param config the configuration to be processed
+     */
+    public void process(Configuration config)
+    {
+        if (config != null)
+        {
+            ConfigurationKey keyEmpty = new ConfigurationKey();
+            ConfigurationKey keyLast = keyEmpty;
+            Set<String> keySet = new HashSet<String>();
+
+            for (Iterator<String> it = config.getKeys(); it.hasNext();)
+            {
+                String key = it.next();
+                if (keySet.contains(key))
+                {
+                    // this key has already been processed by openElements
+                    continue;
+                }
+                ConfigurationKey keyAct = new ConfigurationKey(key);
+                closeElements(keyLast, keyAct);
+                String elem = openElements(keyLast, keyAct, config, keySet);
+                fireValue(elem, config.getProperty(key));
+                keyLast = keyAct;
+            }
+
+            // close all open
+            closeElements(keyLast, keyEmpty);
+        }
+    }
+
+    /**
+     * An event handler method that is called when an element starts.
+     * Concrete sub classes must implement it to perform a proper event
+     * handling.
+     *
+     * @param name the name of the new element
+     * @param value the element's value; can be <b>null</b> if the element
+     * does not have any value
+     */
+    protected abstract void elementStart(String name, Object value);
+
+    /**
+     * An event handler method that is called when an element ends. For each
+     * call of {@code elementStart()} there will be a corresponding call
+     * of this method. Concrete sub classes must implement it to perform a
+     * proper event handling.
+     *
+     * @param name the name of the ending element
+     */
+    protected abstract void elementEnd(String name);
+
+    /**
+     * Fires all necessary element end events for the specified keys. This
+     * method is called for each key obtained from the configuration to be
+     * converted. It calculates the common part of the actual and the last
+     * processed key and thus determines how many elements must be
+     * closed.
+     *
+     * @param keyLast the last processed key
+     * @param keyAct the actual key
+     */
+    protected void closeElements(ConfigurationKey keyLast, ConfigurationKey keyAct)
+    {
+        ConfigurationKey keyDiff = keyAct.differenceKey(keyLast);
+        Iterator<String> it = reverseIterator(keyDiff);
+        if (it.hasNext())
+        {
+            // Skip first because it has already been closed by fireValue()
+            it.next();
+        }
+
+        while (it.hasNext())
+        {
+            elementEnd(it.next());
+        }
+    }
+
+    /**
+     * Helper method for determining a reverse iterator for the specified key.
+     * This implementation returns an iterator that returns the parts of the
+     * given key in reverse order, ignoring indices.
+     *
+     * @param key the key
+     * @return a reverse iterator for the parts of this key
+     */
+    protected Iterator<String> reverseIterator(ConfigurationKey key)
+    {
+        List<String> list = new ArrayList<String>();
+        for (ConfigurationKey.KeyIterator it = key.iterator(); it.hasNext();)
+        {
+            list.add(it.nextKey());
+        }
+
+        Collections.reverse(list);
+        return list.iterator();
+    }
+
+    /**
+     * Fires all necessary element start events for the specified key. This
+     * method is called for each key obtained from the configuration to be
+     * converted. It ensures that all elements "between" the last key and the
+     * actual key are opened and their values are set.
+     *
+     * @param keyLast the last processed key
+     * @param keyAct the actual key
+     * @param config the configuration to process
+     * @param keySet the set with the processed keys
+     * @return the name of the last element on the path
+     */
+    protected String openElements(ConfigurationKey keyLast, ConfigurationKey keyAct,
+            Configuration config, Set<String> keySet)
+    {
+        ConfigurationKey.KeyIterator it = keyLast.differenceKey(keyAct).iterator();
+        ConfigurationKey k = keyLast.commonKey(keyAct);
+        for (it.nextKey(); it.hasNext(); it.nextKey())
+        {
+            k.append(it.currentKey(true));
+            elementStart(it.currentKey(true), config.getProperty(k.toString()));
+            keySet.add(k.toString());
+        }
+        return it.currentKey(true);
+    }
+
+    /**
+     * Fires all necessary element start events with the actual element values.
+     * This method is called for each key obtained from the configuration to be
+     * processed with the last part of the key as argument. The value can be
+     * either a single value or a collection.
+     *
+     * @param name the name of the actual element
+     * @param value the element's value
+     */
+    protected void fireValue(String name, Object value)
+    {
+        if (value instanceof Collection)
+        {
+            Collection<?> valueCol = (Collection<?>) value;
+            for (Object v : valueCol)
+            {
+                fireValue(name, v);
+            }
+        }
+        else
+        {
+            elementStart(name, value);
+            elementEnd(name);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/HierarchicalConfigurationXMLReader.java b/src/main/java/org/apache/commons/configuration/HierarchicalConfigurationXMLReader.java
new file mode 100644
index 0000000..adb2e3e
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/HierarchicalConfigurationXMLReader.java
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import org.apache.commons.configuration.HierarchicalConfiguration.Node;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.xml.sax.Attributes;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * <p>A specialized SAX2 XML parser that "parses" hierarchical
+ * configuration objects.</p>
+ * <p>This class mimics to be a SAX conform XML parser. Instead of parsing
+ * XML documents it processes a {@code Configuration} object and
+ * generates SAX events for the single properties defined there. This enables
+ * the whole world of XML processing for configuration objects.</p>
+ * <p>The {@code HierarchicalConfiguration} object to be parsed can be
+ * specified using a constructor or the {@code setConfiguration()} method.
+ * This object will be processed by the {@code parse()} methods. Note
+ * that these methods ignore their argument.</p>
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a>
+ * @version $Id: HierarchicalConfigurationXMLReader.java 1209998 2011-12-03 20:31:16Z oheger $
+ */
+public class HierarchicalConfigurationXMLReader extends ConfigurationXMLReader
+{
+    /** Stores the configuration object to be parsed.*/
+    private HierarchicalConfiguration configuration;
+
+    /**
+     * Creates a new instance of {@code HierarchicalConfigurationXMLReader}.
+     */
+    public HierarchicalConfigurationXMLReader()
+    {
+        super();
+    }
+
+    /**
+     * Creates a new instance of {@code HierarchicalConfigurationXMLReader} and
+     * sets the configuration to be parsed.
+     *
+     * @param config the configuration object
+     */
+    public HierarchicalConfigurationXMLReader(HierarchicalConfiguration config)
+    {
+        this();
+        setConfiguration(config);
+    }
+
+    /**
+     * Returns the configuration object to be parsed.
+     *
+     * @return the configuration object to be parsed
+     */
+    public HierarchicalConfiguration getConfiguration()
+    {
+        return configuration;
+    }
+
+    /**
+     * Sets the configuration object to be parsed.
+     *
+     * @param config the configuration object to be parsed
+     */
+    public void setConfiguration(HierarchicalConfiguration config)
+    {
+        configuration = config;
+    }
+
+    /**
+     * Returns the configuration object to be processed.
+     *
+     * @return the actual configuration object
+     */
+    @Override
+    public Configuration getParsedConfiguration()
+    {
+        return getConfiguration();
+    }
+
+    /**
+     * Processes the actual configuration object to generate SAX parsing events.
+     */
+    @Override
+    protected void processKeys()
+    {
+        getConfiguration().getRoot().visit(new SAXVisitor(), null);
+    }
+
+    /**
+     * A specialized visitor class for generating SAX events for a
+     * hierarchical node structure.
+     *
+     */
+    class SAXVisitor extends HierarchicalConfiguration.NodeVisitor
+    {
+        /** Constant for the attribute type.*/
+        private static final String ATTR_TYPE = "CDATA";
+
+        /**
+         * Visits the specified node after its children have been processed.
+         *
+         * @param node the actual node
+         * @param key the key of this node
+         */
+        @Override
+        public void visitAfterChildren(Node node, ConfigurationKey key)
+        {
+            if (!isAttributeNode(node))
+            {
+                fireElementEnd(nodeName(node));
+            }
+        }
+
+        /**
+         * Visits the specified node.
+         *
+         * @param node the actual node
+         * @param key the key of this node
+         */
+        @Override
+        public void visitBeforeChildren(Node node, ConfigurationKey key)
+        {
+            if (!isAttributeNode(node))
+            {
+                fireElementStart(nodeName(node), fetchAttributes(node));
+
+                if (node.getValue() != null)
+                {
+                    fireCharacters(node.getValue().toString());
+                }
+            }
+        }
+
+        /**
+         * Checks if iteration should be terminated. This implementation stops
+         * iteration after an exception has occurred.
+         *
+         * @return a flag if iteration should be stopped
+         */
+        @Override
+        public boolean terminate()
+        {
+            return getException() != null;
+        }
+
+        /**
+         * Returns an object with all attributes for the specified node.
+         *
+         * @param node the actual node
+         * @return an object with all attributes of this node
+         */
+        protected Attributes fetchAttributes(Node node)
+        {
+            AttributesImpl attrs = new AttributesImpl();
+
+            for (ConfigurationNode child : node.getAttributes())
+            {
+                if (child.getValue() != null)
+                {
+                    String attr = child.getName();
+                    attrs.addAttribute(NS_URI, attr, attr, ATTR_TYPE, child.getValue().toString());
+                }
+            }
+
+            return attrs;
+        }
+
+        /**
+         * Helper method for determining the name of a node. If a node has no
+         * name (which is true for the root node), the specified default name
+         * will be used.
+         *
+         * @param node the node to be checked
+         * @return the name for this node
+         */
+        private String nodeName(Node node)
+        {
+            return (node.getName() == null) ? getRootName() : node.getName();
+        }
+
+        /**
+         * Checks if the specified node is an attribute node. In the node
+         * hierarchy attributes are stored as normal child nodes, but with
+         * special names.
+         *
+         * @param node the node to be checked
+         * @return a flag if this is an attribute node
+         */
+        private boolean isAttributeNode(Node node)
+        {
+            return node.isAttribute();
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/HierarchicalINIConfiguration.java b/src/main/java/org/apache/commons/configuration/HierarchicalINIConfiguration.java
new file mode 100644
index 0000000..3a47b00
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/HierarchicalINIConfiguration.java
@@ -0,0 +1,884 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.ViewNode;
+
+/**
+ * <p>
+ * A specialized hierarchical configuration implementation for parsing ini
+ * files.
+ * </p>
+ * <p>
+ * An initialization or ini file is a configuration file typically found on
+ * Microsoft's Windows operating system and contains data for Windows based
+ * applications.
+ * </p>
+ * <p>
+ * Although popularized by Windows, ini files can be used on any system or
+ * platform due to the fact that they are merely text files that can easily be
+ * parsed and modified by both humans and computers.
+ * </p>
+ * <p>
+ * A typical ini file could look something like:
+ * </p>
+ * <pre>
+ * [section1]
+ * ; this is a comment!
+ * var1 = foo
+ * var2 = bar
+ *
+ * [section2]
+ * var1 = doo
+ * </pre>
+ * <p>
+ * The format of ini files is fairly straight forward and is composed of three
+ * components:<br>
+ * <ul>
+ * <li><b>Sections:</b> Ini files are split into sections, each section starting
+ * with a section declaration. A section declaration starts with a '[' and ends
+ * with a ']'. Sections occur on one line only.</li>
+ * <li><b>Parameters:</b> Items in a section are known as parameters. Parameters
+ * have a typical {@code key = value} format.</li>
+ * <li><b>Comments:</b> Lines starting with a ';' are assumed to be comments.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * There are various implementations of the ini file format by various vendors
+ * which has caused a number of differences to appear. As far as possible this
+ * configuration tries to be lenient and support most of the differences.
+ * </p>
+ * <p>
+ * Some of the differences supported are as follows:
+ * <ul>
+ * <li><b>Comments:</b> The '#' character is also accepted as a comment
+ * signifier.</li>
+ * <li><b>Key value separator:</b> The ':' character is also accepted in place of
+ * '=' to separate keys and values in parameters, for example
+ * {@code var1 : foo}.</li>
+ * <li><b>Duplicate sections:</b> Typically duplicate sections are not allowed,
+ * this configuration does however support this feature. In the event of a duplicate
+ * section, the two section's values are merged so that there is only a single
+ * section. <strong>Note</strong>: This also affects the internal data of the
+ * configuration. If it is saved, only a single section is written!</li>
+ * <li><b>Duplicate parameters:</b> Typically duplicate parameters are only
+ * allowed if they are in two different sections, thus they are local to
+ * sections; this configuration simply merges duplicates; if a section has a
+ * duplicate parameter the values are then added to the key as a list.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * Global parameters are also allowed; any parameters declared before a section
+ * is declared are added to a global section. It is important to note that this
+ * global section does not have a name.
+ * </p>
+ * <p>
+ * In all instances, a parameter's key is prepended with its section name and a
+ * '.' (period). Thus a parameter named "var1" in "section1" will have the key
+ * {@code section1.var1} in this configuration. (This is the default
+ * behavior. Because this is a hierarchical configuration you can change this by
+ * setting a different {@link org.apache.commons.configuration.tree.ExpressionEngine}.)
+ * </p>
+ * <p>
+ * <h3>Implementation Details:</h3> Consider the following ini file:<br>
+ * <pre>
+ *  default = ok
+ *
+ *  [section1]
+ *  var1 = foo
+ *  var2 = doodle
+ *
+ *  [section2]
+ *  ; a comment
+ *  var1 = baz
+ *  var2 = shoodle
+ *  bad =
+ *  = worse
+ *
+ *  [section3]
+ *  # another comment
+ *  var1 : foo
+ *  var2 : bar
+ *  var5 : test1
+ *
+ *  [section3]
+ *  var3 = foo
+ *  var4 = bar
+ *  var5 = test2
+ *
+ *  [sectionSeparators]
+ *  passwd : abc=def
+ *  a:b = "value"
+ *  </pre>
+ * </p>
+ * <p>
+ * This ini file will be parsed without error. Note:
+ * <ul>
+ * <li>The parameter named "default" is added to the global section, it's value
+ * is accessed simply using {@code getProperty("default")}.</li>
+ * <li>Section 1's parameters can be accessed using
+ * {@code getProperty("section1.var1")}.</li>
+ * <li>The parameter named "bad" simply adds the parameter with an empty value.</li>
+ * <li>The empty key with value "= worse" is added using a key consisting of a
+ * single space character. This key is still added to section 2 and the value
+ * can be accessed using {@code getProperty("section2. ")}, notice the
+ * period '.' and the space following the section name.</li>
+ * <li>Section three uses both '=' and ':' to separate keys and values.</li>
+ * <li>Section 3 has a duplicate key named "var5". The value for this key is
+ * [test1, test2], and is represented as a List.</li>
+ * <li>The section called <em>sectionSeparators</em> demonstrates how the
+ * configuration deals with multiple occurrences of separator characters. Per
+ * default the first separator character in a line is detected and used to
+ * split the key from the value. Therefore the first property definition in this
+ * section has the key {@code passwd} and the value {@code abc=def}.
+ * This default behavior can be changed by using quotes. If there is a separator
+ * character before the first quote character (ignoring whitespace), this
+ * character is used as separator. Thus the second property definition in the
+ * section has the key {@code a:b} and the value {@code value}.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * Internally, this configuration maps the content of the represented ini file
+ * to its node structure in the following way:
+ * <ul>
+ * <li>Sections are represented by direct child nodes of the root node.</li>
+ * <li>For the content of a section, corresponding nodes are created as children
+ * of the section node.</li>
+ * </ul>
+ * This explains how the keys for the properties can be constructed. You can
+ * also use other methods of {@link HierarchicalConfiguration} for querying or
+ * manipulating the hierarchy of configuration nodes, for instance the
+ * {@code configurationAt()} method for obtaining the data of a specific
+ * section. However, be careful that the storage scheme described above is not
+ * violated (e.g. by adding multiple levels of nodes or inserting duplicate
+ * section nodes). Otherwise, the special methods for ini configurations may not
+ * work correctly!
+ * </p>
+ * <p>
+ * The set of sections in this configuration can be retrieved using the
+ * {@code getSections()} method. For obtaining a
+ * {@code SubnodeConfiguration} with the content of a specific section the
+ * {@code getSection()} method can be used.
+ * </p>
+ * <p>
+ * <em>Note:</em> Configuration objects of this type can be read concurrently by
+ * multiple threads. However if one of these threads modifies the object,
+ * synchronization has to be performed manually.
+ * </p>
+ *
+ * @author <a
+ *         href="http://commons.apache.org/configuration/team-list.html">Commons
+ *         Configuration team</a>
+ * @version $Id: HierarchicalINIConfiguration.java 1234362 2012-01-21 16:59:48Z oheger $
+ * @since 1.6
+ */
+public class HierarchicalINIConfiguration extends
+        AbstractHierarchicalFileConfiguration
+{
+    /**
+     * The characters that signal the start of a comment line.
+     */
+    protected static final String COMMENT_CHARS = "#;";
+
+    /**
+     * The characters used to separate keys from values.
+     */
+    protected static final String SEPARATOR_CHARS = "=:";
+
+    /**
+     * The serial version UID.
+     */
+    private static final long serialVersionUID = 2548006161386850670L;
+
+    /**
+     * Constant for the line separator.
+     */
+    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
+    /**
+     * The characters used for quoting values.
+     */
+    private static final String QUOTE_CHARACTERS = "\"'";
+
+    /**
+     * The line continuation character.
+     */
+    private static final String LINE_CONT = "\\";
+
+    /**
+     * Create a new empty INI Configuration.
+     */
+    public HierarchicalINIConfiguration()
+    {
+        super();
+    }
+
+    /**
+     * Create and load the ini configuration from the given file.
+     *
+     * @param filename The name pr path of the ini file to load.
+     * @throws ConfigurationException If an error occurs while loading the file
+     */
+    public HierarchicalINIConfiguration(String filename)
+            throws ConfigurationException
+    {
+        super(filename);
+    }
+
+    /**
+     * Create and load the ini configuration from the given file.
+     *
+     * @param file The ini file to load.
+     * @throws ConfigurationException If an error occurs while loading the file
+     */
+    public HierarchicalINIConfiguration(File file)
+            throws ConfigurationException
+    {
+        super(file);
+    }
+
+    /**
+     * Create and load the ini configuration from the given url.
+     *
+     * @param url The url of the ini file to load.
+     * @throws ConfigurationException If an error occurs while loading the file
+     */
+    public HierarchicalINIConfiguration(URL url) throws ConfigurationException
+    {
+        super(url);
+    }
+
+    /**
+     * Save the configuration to the specified writer.
+     *
+     * @param writer - The writer to save the configuration to.
+     * @throws ConfigurationException If an error occurs while writing the
+     *         configuration
+     */
+    public void save(Writer writer) throws ConfigurationException
+    {
+        PrintWriter out = new PrintWriter(writer);
+        Iterator<String> it = getSections().iterator();
+        while (it.hasNext())
+        {
+            String section = it.next();
+            Configuration subset;
+            if (section != null)
+            {
+                out.print("[");
+                out.print(section);
+                out.print("]");
+                out.println();
+                subset = createSubnodeConfiguration(getSectionNode(section));
+            }
+            else
+            {
+                subset = getSection(null);
+            }
+
+            Iterator<String> keys = subset.getKeys();
+            while (keys.hasNext())
+            {
+                String key = keys.next();
+                Object value = subset.getProperty(key);
+                if (value instanceof Collection)
+                {
+                    Iterator<?> values = ((Collection<?>) value).iterator();
+                    while (values.hasNext())
+                    {
+                        value = values.next();
+                        out.print(key);
+                        out.print(" = ");
+                        out.print(formatValue(value.toString()));
+                        out.println();
+                    }
+                }
+                else
+                {
+                    out.print(key);
+                    out.print(" = ");
+                    out.print(formatValue(value.toString()));
+                    out.println();
+                }
+            }
+
+            out.println();
+        }
+
+        out.flush();
+    }
+
+    /**
+     * Load the configuration from the given reader. Note that the
+     * {@code clear()} method is not called so the configuration read in will
+     * be merged with the current configuration.
+     *
+     * @param reader The reader to read the configuration from.
+     * @throws ConfigurationException If an error occurs while reading the
+     *         configuration
+     */
+    public void load(Reader reader) throws ConfigurationException
+    {
+        try
+        {
+            BufferedReader bufferedReader = new BufferedReader(reader);
+            ConfigurationNode sectionNode = getRootNode();
+
+            String line = bufferedReader.readLine();
+            while (line != null)
+            {
+                line = line.trim();
+                if (!isCommentLine(line))
+                {
+                    if (isSectionLine(line))
+                    {
+                        String section = line.substring(1, line.length() - 1);
+                        sectionNode = getSectionNode(section);
+                    }
+
+                    else
+                    {
+                        String key = "";
+                        String value = "";
+                        int index = findSeparator(line);
+                        if (index >= 0)
+                        {
+                            key = line.substring(0, index);
+                            value = parseValue(line.substring(index + 1), bufferedReader);
+                        }
+                        else
+                        {
+                            key = line;
+                        }
+                        key = key.trim();
+                        if (key.length() < 1)
+                        {
+                            // use space for properties with no key
+                            key = " ";
+                        }
+                        createValueNodes(sectionNode, key, value);
+                    }
+                }
+
+                line = bufferedReader.readLine();
+            }
+        }
+        catch (IOException e)
+        {
+            throw new ConfigurationException(
+                    "Unable to load the configuration", e);
+        }
+    }
+
+    /**
+     * Creates the node(s) for the given key value-pair. If delimiter parsing is
+     * enabled, the value string is split if possible, and for each single value
+     * a node is created. Otherwise only a single node is added to the section.
+     *
+     * @param sectionNode the section node new nodes have to be added
+     * @param key the key
+     * @param value the value string
+     */
+    private void createValueNodes(ConfigurationNode sectionNode, String key,
+            String value)
+    {
+        Collection<String> values;
+        if (isDelimiterParsingDisabled())
+        {
+            values = Collections.singleton(value);
+        }
+        else
+        {
+            values = PropertyConverter.split(value, getListDelimiter(), false);
+        }
+
+        for (String v : values)
+        {
+            ConfigurationNode node = createNode(key);
+            node.setValue(v);
+            sectionNode.addChild(node);
+        }
+    }
+
+    /**
+     * Parse the value to remove the quotes and ignoring the comment. Example:
+     *
+     * <pre>
+     * "value" ; comment -> value
+     * </pre>
+     *
+     * <pre>
+     * 'value' ; comment -> value
+     * </pre>
+     * Note that a comment character is only recognized if there is at least one
+     * whitespace character before it. So it can appear in the property value,
+     * e.g.:
+     * <pre>
+     * C:\\Windows;C:\\Windows\\system32
+     * </pre>
+     *
+     * @param val the value to be parsed
+     * @param reader the reader (needed if multiple lines have to be read)
+     * @throws IOException if an IO error occurs
+     */
+    private static String parseValue(String val, BufferedReader reader) throws IOException
+    {
+        StringBuilder propertyValue = new StringBuilder();
+        boolean lineContinues;
+        String value = val.trim();
+
+        do
+        {
+            boolean quoted = value.startsWith("\"") || value.startsWith("'");
+            boolean stop = false;
+            boolean escape = false;
+
+            char quote = quoted ? value.charAt(0) : 0;
+
+            int i = quoted ? 1 : 0;
+
+            StringBuilder result = new StringBuilder();
+            char lastChar = 0;
+            while (i < value.length() && !stop)
+            {
+                char c = value.charAt(i);
+
+                if (quoted)
+                {
+                    if ('\\' == c && !escape)
+                    {
+                        escape = true;
+                    }
+                    else if (!escape && quote == c)
+                    {
+                        stop = true;
+                    }
+                    else if (escape && quote == c)
+                    {
+                        escape = false;
+                        result.append(c);
+                    }
+                    else
+                    {
+                        if (escape)
+                        {
+                            escape = false;
+                            result.append('\\');
+                        }
+
+                        result.append(c);
+                    }
+                }
+                else
+                {
+                    if (isCommentChar(c) && Character.isWhitespace(lastChar))
+                    {
+                        stop = true;
+                    }
+                    else
+                    {
+                        result.append(c);
+                    }
+                }
+
+                i++;
+                lastChar = c;
+            }
+
+            String v = result.toString();
+            if (!quoted)
+            {
+                v = v.trim();
+                lineContinues = lineContinues(v);
+                if (lineContinues)
+                {
+                    // remove trailing "\"
+                    v = v.substring(0, v.length() - 1).trim();
+                }
+            }
+            else
+            {
+                lineContinues = lineContinues(value, i);
+            }
+            propertyValue.append(v);
+
+            if (lineContinues)
+            {
+                propertyValue.append(LINE_SEPARATOR);
+                value = reader.readLine();
+            }
+        } while (lineContinues && value != null);
+
+        return propertyValue.toString();
+    }
+
+    /**
+     * Tests whether the specified string contains a line continuation marker.
+     *
+     * @param line the string to check
+     * @return a flag whether this line continues
+     */
+    private static boolean lineContinues(String line)
+    {
+        String s = line.trim();
+        return s.equals(LINE_CONT)
+                || (s.length() > 2 && s.endsWith(LINE_CONT) && Character
+                        .isWhitespace(s.charAt(s.length() - 2)));
+    }
+
+    /**
+     * Tests whether the specified string contains a line continuation marker
+     * after the specified position. This method parses the string to remove a
+     * comment that might be present. Then it checks whether a line continuation
+     * marker can be found at the end.
+     *
+     * @param line the line to check
+     * @param pos the start position
+     * @return a flag whether this line continues
+     */
+    private static boolean lineContinues(String line, int pos)
+    {
+        String s;
+
+        if (pos >= line.length())
+        {
+            s = line;
+        }
+        else
+        {
+            int end = pos;
+            while (end < line.length() && !isCommentChar(line.charAt(end)))
+            {
+                end++;
+            }
+            s = line.substring(pos, end);
+        }
+
+        return lineContinues(s);
+    }
+
+    /**
+     * Tests whether the specified character is a comment character.
+     *
+     * @param c the character
+     * @return a flag whether this character starts a comment
+     */
+    private static boolean isCommentChar(char c)
+    {
+        return COMMENT_CHARS.indexOf(c) >= 0;
+    }
+
+    /**
+     * Tries to find the index of the separator character in the given string.
+     * This method checks for the presence of separator characters in the given
+     * string. If multiple characters are found, the first one is assumed to be
+     * the correct separator. If there are quoting characters, they are taken
+     * into account, too.
+     *
+     * @param line the line to be checked
+     * @return the index of the separator character or -1 if none is found
+     */
+    private static int findSeparator(String line)
+    {
+        int index =
+                findSeparatorBeforeQuote(line,
+                        findFirstOccurrence(line, QUOTE_CHARACTERS));
+        if (index < 0)
+        {
+            index = findFirstOccurrence(line, SEPARATOR_CHARS);
+        }
+        return index;
+    }
+
+    /**
+     * Checks for the occurrence of the specified separators in the given line.
+     * The index of the first separator is returned.
+     *
+     * @param line the line to be investigated
+     * @param separators a string with the separator characters to look for
+     * @return the lowest index of a separator character or -1 if no separator
+     *         is found
+     */
+    private static int findFirstOccurrence(String line, String separators)
+    {
+        int index = -1;
+
+        for (int i = 0; i < separators.length(); i++)
+        {
+            char sep = separators.charAt(i);
+            int pos = line.indexOf(sep);
+            if (pos >= 0)
+            {
+                if (index < 0 || pos < index)
+                {
+                    index = pos;
+                }
+            }
+        }
+
+        return index;
+    }
+
+    /**
+     * Searches for a separator character directly before a quoting character.
+     * If the first non-whitespace character before a quote character is a
+     * separator, it is considered the "real" separator in this line - even if
+     * there are other separators before.
+     *
+     * @param line the line to be investigated
+     * @param quoteIndex the index of the quote character
+     * @return the index of the separator before the quote or < 0 if there is
+     *         none
+     */
+    private static int findSeparatorBeforeQuote(String line, int quoteIndex)
+    {
+        int index = quoteIndex - 1;
+        while (index >= 0 && Character.isWhitespace(line.charAt(index)))
+        {
+            index--;
+        }
+
+        if (index >= 0 && SEPARATOR_CHARS.indexOf(line.charAt(index)) < 0)
+        {
+            index = -1;
+        }
+
+        return index;
+    }
+
+    /**
+     * Add quotes around the specified value if it contains a comment character.
+     */
+    private String formatValue(String value)
+    {
+        boolean quoted = false;
+
+        for (int i = 0; i < COMMENT_CHARS.length() && !quoted; i++)
+        {
+            char c = COMMENT_CHARS.charAt(i);
+            if (value.indexOf(c) != -1)
+            {
+                quoted = true;
+            }
+        }
+
+        if (quoted)
+        {
+            return '"' + value.replaceAll("\"", "\\\\\\\"") + '"';
+        }
+        else
+        {
+            return value;
+        }
+    }
+
+    /**
+     * Determine if the given line is a comment line.
+     *
+     * @param line The line to check.
+     * @return true if the line is empty or starts with one of the comment
+     *         characters
+     */
+    protected boolean isCommentLine(String line)
+    {
+        if (line == null)
+        {
+            return false;
+        }
+        // blank lines are also treated as comment lines
+        return line.length() < 1 || COMMENT_CHARS.indexOf(line.charAt(0)) >= 0;
+    }
+
+    /**
+     * Determine if the given line is a section.
+     *
+     * @param line The line to check.
+     * @return true if the line contains a section
+     */
+    protected boolean isSectionLine(String line)
+    {
+        if (line == null)
+        {
+            return false;
+        }
+        return line.startsWith("[") && line.endsWith("]");
+    }
+
+    /**
+     * Return a set containing the sections in this ini configuration. Note that
+     * changes to this set do not affect the configuration.
+     *
+     * @return a set containing the sections.
+     */
+    public Set<String> getSections()
+    {
+        Set<String> sections = new LinkedHashSet<String>();
+        boolean globalSection = false;
+        boolean inSection = false;
+
+        for (ConfigurationNode node : getRootNode().getChildren())
+        {
+            if (isSectionNode(node))
+            {
+                inSection = true;
+                sections.add(node.getName());
+            }
+            else
+            {
+                if (!inSection && !globalSection)
+                {
+                    globalSection = true;
+                    sections.add(null);
+                }
+            }
+        }
+
+        return sections;
+    }
+
+    /**
+     * Returns a configuration with the content of the specified section. This
+     * provides an easy way of working with a single section only. The way this
+     * configuration is structured internally, this method is very similar to
+     * calling {@link HierarchicalConfiguration#configurationAt(String)} with
+     * the name of the section in question. There are the following differences
+     * however:
+     * <ul>
+     * <li>This method never throws an exception. If the section does not exist,
+     * it is created now. The configuration returned in this case is empty.</li>
+     * <li>If section is contained multiple times in the configuration, the
+     * configuration returned by this method is initialized with the first
+     * occurrence of the section. (This can only happen if
+     * {@code addProperty()} has been used in a way that does not conform
+     * to the storage scheme used by {@code HierarchicalINIConfiguration}.
+     * If used correctly, there will not be duplicate sections.)</li>
+     * <li>There is special support for the global section: Passing in
+     * <b>null</b> as section name returns a configuration with the content of
+     * the global section (which may also be empty).</li>
+     * </ul>
+     *
+     * @param name the name of the section in question; <b>null</b> represents
+     *        the global section
+     * @return a configuration containing only the properties of the specified
+     *         section
+     */
+    public SubnodeConfiguration getSection(String name)
+    {
+        if (name == null)
+        {
+            return getGlobalSection();
+        }
+
+        else
+        {
+            try
+            {
+                return configurationAt(name);
+            }
+            catch (IllegalArgumentException iex)
+            {
+                // the passed in key does not map to exactly one node
+                // obtain the node for the section, create it on demand
+                return new SubnodeConfiguration(this, getSectionNode(name));
+            }
+        }
+    }
+
+    /**
+     * Obtains the node representing the specified section. This method is
+     * called while the configuration is loaded. If a node for this section
+     * already exists, it is returned. Otherwise a new node is created.
+     *
+     * @param sectionName the name of the section
+     * @return the node for this section
+     */
+    private ConfigurationNode getSectionNode(String sectionName)
+    {
+        List<ConfigurationNode> nodes = getRootNode().getChildren(sectionName);
+        if (!nodes.isEmpty())
+        {
+            return nodes.get(0);
+        }
+
+        ConfigurationNode node = createNode(sectionName);
+        markSectionNode(node);
+        getRootNode().addChild(node);
+        return node;
+    }
+
+    /**
+     * Creates a sub configuration for the global section of the represented INI
+     * configuration.
+     *
+     * @return the sub configuration for the global section
+     */
+    private SubnodeConfiguration getGlobalSection()
+    {
+        ViewNode parent = new ViewNode();
+
+        for (ConfigurationNode node : getRootNode().getChildren())
+        {
+            if (!isSectionNode(node))
+            {
+                synchronized (node)
+                {
+                    parent.addChild(node);
+                }
+            }
+        }
+
+        return createSubnodeConfiguration(parent);
+    }
+
+    /**
+     * Marks a configuration node as a section node. This means that this node
+     * represents a section header. This implementation uses the node's
+     * reference property to store a flag.
+     *
+     * @param node the node to be marked
+     */
+    private static void markSectionNode(ConfigurationNode node)
+    {
+        node.setReference(Boolean.TRUE);
+    }
+
+    /**
+     * Checks whether the specified configuration node represents a section.
+     *
+     * @param node the node in question
+     * @return a flag whether this node represents a section
+     */
+    private static boolean isSectionNode(ConfigurationNode node)
+    {
+        return node.getReference() != null || node.getChildrenCount() > 0;
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/HierarchicalReloadableConfiguration.java b/src/main/java/org/apache/commons/configuration/HierarchicalReloadableConfiguration.java
new file mode 100644
index 0000000..f56a680
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/HierarchicalReloadableConfiguration.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import org.apache.commons.configuration.reloading.Reloadable;
+
+/**
+ * <p>A base class for hierarchical configurations with specific reloading
+ * requirements.</p>
+ * <p>This class manages a lock object which can be used for synchronization.</p>
+ *
+ * @author <a
+ *         href="http://commons.apache.org/configuration/team-list.html">Commons
+ *         Configuration team</a>
+ * @since 1.7
+ * @version $Id: HierarchicalReloadableConfiguration.java 1210000 2011-12-03 20:43:38Z oheger $
+ */
+public class HierarchicalReloadableConfiguration extends HierarchicalConfiguration
+    implements Reloadable
+{
+    /** Constant for the name used for the lock object. */
+    private static final String LOCK_NAME = "HierarchicalReloadableConfigurationLock";
+
+    /** The lock object used by this instance. */
+    private final Object reloadLock;
+
+    /**
+     * Creates a new instance of {@code HierarchicalReloadableConfiguration}.
+     */
+    public HierarchicalReloadableConfiguration()
+    {
+        super();
+        reloadLock = new Lock(LOCK_NAME);
+    }
+
+    /**
+     * Creates a new instance of {@code HierarchicalReloadableConfiguration} and
+     * initializes it with the given lock object.
+     *
+     * @param lock the lock object
+     */
+    public HierarchicalReloadableConfiguration(Object lock)
+    {
+        super();
+        reloadLock = lock == null ? new Lock(LOCK_NAME) : lock;
+    }
+
+    /**
+     * Creates a new instance of {@code HierarchicalReloadableConfiguration} and
+     * copies all data contained in the specified configuration into the new
+     * one.
+     *
+     * @param c the configuration that is to be copied (if <b>null</b>, this
+     * constructor will behave like the standard constructor)
+     */
+    public HierarchicalReloadableConfiguration(HierarchicalConfiguration c)
+    {
+        super(c);
+        reloadLock = new Lock(LOCK_NAME);
+    }
+
+    @Override
+    public Object getReloadLock()
+    {
+        return reloadLock;
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/HierarchicalXMLConfiguration.java b/src/main/java/org/apache/commons/configuration/HierarchicalXMLConfiguration.java
new file mode 100644
index 0000000..a99962f
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/HierarchicalXMLConfiguration.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+/**
+ * A specialized hierarchical configuration class that is able to parse XML
+ * documents.
+ *
+ * <p>The parsed document will be stored keeping its structure. The contained
+ * properties can be accessed using all methods supported by the base class
+ * {@code HierarchicalConfiguration}.
+ *
+ * @since commons-configuration 1.0
+ *
+ * @author Jörg Schaible
+ * @version $Id: HierarchicalXMLConfiguration.java 1210002 2011-12-03 20:54:17Z oheger $
+ * @deprecated This class is deprecated. Use {@code XMLConfiguration}
+ * instead, which supports all features this class had offered before.
+ */
+ at Deprecated
+public class HierarchicalXMLConfiguration extends XMLConfiguration
+{
+    /**
+     * The serial version UID.
+     */
+    private static final long serialVersionUID = 5597530014798917521L;
+}
diff --git a/src/main/java/org/apache/commons/configuration/INIConfiguration.java b/src/main/java/org/apache/commons/configuration/INIConfiguration.java
new file mode 100644
index 0000000..26a42a2
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/INIConfiguration.java
@@ -0,0 +1,493 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * <p>
+ * An initialization or ini file is a configuration file typically found on
+ * Microsoft's Windows operating system and contains data for Windows based
+ * applications.
+ * </p>
+ *
+ * <p>
+ * Although popularized by Windows, ini files can be used on any system or
+ * platform due to the fact that they are merely text files that can easily be
+ * parsed and modified by both humans and computers.
+ * </p>
+ *
+ * <p>
+ * A typical ini file could look something like:
+ * </p>
+ * <pre>
+ * [section1]
+ * ; this is a comment!
+ * var1 = foo
+ * var2 = bar
+ *
+ * [section2]
+ * var1 = doo
+ * </pre>
+ *
+ * <p>
+ * The format of ini files is fairly straight forward and is composed of three
+ * components:<br>
+ * <ul>
+ * <li><b>Sections:</b> Ini files are split into sections, each section
+ * starting with a section declaration. A section declaration starts with a '['
+ * and ends with a ']'. Sections occur on one line only.</li>
+ * <li><b>Parameters:</b> Items in a section are known as parameters.
+ * Parameters have a typical {@code key = value} format.</li>
+ * <li><b>Comments:</b> Lines starting with a ';' are assumed to be comments.
+ * </li>
+ * </ul>
+ * </p>
+ *
+ * <p>
+ * There are various implementations of the ini file format by various vendors
+ * which has caused a number of differences to appear. As far as possible this
+ * configuration tries to be lenient and support most of the differences.
+ * </p>
+ *
+ * <p>
+ * Some of the differences supported are as follows:
+ * <ul>
+ * <li><b>Comments:</b> The '#' character is also accepted as a comment
+ * signifier.</li>
+ * <li><b>Key value separtor:</b> The ':' character is also accepted in place
+ * of '=' to separate keys and values in parameters, for example
+ * {@code var1 : foo}.</li>
+ * <li><b>Duplicate sections:</b> Typically duplicate sections are not allowed ,
+ * this configuration does however support it. In the event of a duplicate
+ * section, the two section's values are merged.</li>
+ * <li><b>Duplicate parameters:</b> Typically duplicate parameters are only
+ * allowed if they are in two different sections, thus they are local to
+ * sections; this configuration simply merges duplicates; if a section has a
+ * duplicate parameter the values are then added to the key as a list. </li>
+ * </ul>
+ * </p>
+ * <p>
+ * Global parameters are also allowed; any parameters declared before a section
+ * is declared are added to a global section. It is important to note that this
+ * global section does not have a name.
+ * </p>
+ * <p>
+ * In all instances, a parameter's key is prepended with its section name and a
+ * '.' (period). Thus a parameter named "var1" in "section1" will have the key
+ * {@code section1.var1} in this configuration. Thus, a section's
+ * parameters can easily be retrieved using the {@code subset} method
+ * using the section name as the prefix.
+ * </p>
+ * <p>
+ * <h3>Implementation Details:</h3>
+ * Consider the following ini file:<br>
+ * <pre>
+ *  default = ok
+ *
+ *  [section1]
+ *  var1 = foo
+ *  var2 = doodle
+ *
+ *  [section2]
+ *  ; a comment
+ *  var1 = baz
+ *  var2 = shoodle
+ *  bad =
+ *  = worse
+ *
+ *  [section3]
+ *  # another comment
+ *  var1 : foo
+ *  var2 : bar
+ *  var5 : test1
+ *
+ *  [section3]
+ *  var3 = foo
+ *  var4 = bar
+ *  var5 = test2
+ *  </pre>
+ * </p>
+ * <p>
+ * This ini file will be parsed without error. Note:
+ * <ul>
+ * <li>The parameter named "default" is added to the global section, it's value
+ * is accessed simply using {@code getProperty("default")}.</li>
+ * <li>Section 1's parameters can be accessed using
+ * {@code getProperty("section1.var1")}.</li>
+ * <li>The parameter named "bad" simply adds the parameter with an empty value.
+ * </li>
+ * <li>The empty key with value "= worse" is added using an empty key. This key
+ * is still added to section 2 and the value can be accessed using
+ * {@code getProperty("section2.")}, notice the period '.' following the
+ * section name.</li>
+ * <li>Section three uses both '=' and ':' to separate keys and values.</li>
+ * <li>Section 3 has a duplicate key named "var5". The value for this key is
+ * [test1, test2], and is represented as a List.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * The set of sections in this configuration can be retrieved using the
+ * {@code getSections} method.
+ * </p>
+ * <p>
+ * <em>Note:</em> Configuration objects of this type can be read concurrently
+ * by multiple threads. However if one of these threads modifies the object,
+ * synchronization has to be performed manually.
+ * </p>
+ *
+ * @author Trevor Miller
+ * @version $Id: INIConfiguration.java 1210003 2011-12-03 20:54:46Z oheger $
+ * @since 1.4
+ * @deprecated This class has been replaced by HierarchicalINIConfiguration,
+ * which provides a superset of the functionality offered by this class.
+ */
+ at Deprecated
+public class INIConfiguration extends AbstractFileConfiguration
+{
+    /**
+     * The characters that signal the start of a comment line.
+     */
+    protected static final String COMMENT_CHARS = "#;";
+
+    /**
+     * The characters used to separate keys from values.
+     */
+    protected static final String SEPARATOR_CHARS = "=:";
+
+    /**
+     * Create a new empty INI Configuration.
+     */
+    public INIConfiguration()
+    {
+        super();
+    }
+
+    /**
+     * Create and load the ini configuration from the given file.
+     *
+     * @param filename The name pr path of the ini file to load.
+     * @throws ConfigurationException If an error occurs while loading the file
+     */
+    public INIConfiguration(String filename) throws ConfigurationException
+    {
+        super(filename);
+    }
+
+    /**
+     * Create and load the ini configuration from the given file.
+     *
+     * @param file The ini file to load.
+     * @throws ConfigurationException If an error occurs while loading the file
+     */
+    public INIConfiguration(File file) throws ConfigurationException
+    {
+        super(file);
+    }
+
+    /**
+     * Create and load the ini configuration from the given url.
+     *
+     * @param url The url of the ini file to load.
+     * @throws ConfigurationException If an error occurs while loading the file
+     */
+    public INIConfiguration(URL url) throws ConfigurationException
+    {
+        super(url);
+    }
+
+    /**
+     * Save the configuration to the specified writer.
+     *
+     * @param writer - The writer to save the configuration to.
+     * @throws ConfigurationException If an error occurs while writing the
+     * configuration
+     */
+    public void save(Writer writer) throws ConfigurationException
+    {
+        PrintWriter out = new PrintWriter(writer);
+        Iterator<String> it = getSections().iterator();
+        while (it.hasNext())
+        {
+            String section = it.next();
+            out.print("[");
+            out.print(section);
+            out.print("]");
+            out.println();
+
+            Configuration subset = subset(section);
+            Iterator<String> keys = subset.getKeys();
+            while (keys.hasNext())
+            {
+                String key = keys.next();
+                Object value = subset.getProperty(key);
+                if (value instanceof Collection)
+                {
+                    Iterator<?> values = ((Collection<?>) value).iterator();
+                    while (values.hasNext())
+                    {
+                        value = values.next();
+                        out.print(key);
+                        out.print(" = ");
+                        out.print(formatValue(value.toString()));
+                        out.println();
+                    }
+                }
+                else
+                {
+                    out.print(key);
+                    out.print(" = ");
+                    out.print(formatValue(value.toString()));
+                    out.println();
+                }
+            }
+
+            out.println();
+        }
+
+        out.flush();
+    }
+
+    /**
+     * Load the configuration from the given reader. Note that the
+     * {@code clear()} method is not called so the configuration read in
+     * will be merged with the current configuration.
+     *
+     * @param reader The reader to read the configuration from.
+     * @throws ConfigurationException If an error occurs while reading the
+     * configuration
+     */
+    public void load(Reader reader) throws ConfigurationException
+    {
+        try
+        {
+            BufferedReader bufferedReader = new BufferedReader(reader);
+            String line = bufferedReader.readLine();
+            String section = "";
+            while (line != null)
+            {
+                line = line.trim();
+                if (!isCommentLine(line))
+                {
+                    if (isSectionLine(line))
+                    {
+                        section = line.substring(1, line.length() - 1) + ".";
+                    }
+                    else
+                    {
+                        String key = "";
+                        String value = "";
+                        int index = line.indexOf("=");
+                        if (index >= 0)
+                        {
+                            key = section + line.substring(0, index);
+                            value = parseValue(line.substring(index + 1));
+                        }
+                        else
+                        {
+                            index = line.indexOf(":");
+                            if (index >= 0)
+                            {
+                                key = section + line.substring(0, index);
+                                value = parseValue(line.substring(index + 1));
+                            }
+                            else
+                            {
+                                key = section + line;
+                            }
+                        }
+                        addProperty(key.trim(), value);
+                    }
+                }
+                line = bufferedReader.readLine();
+            }
+        }
+        catch (IOException e)
+        {
+            throw new ConfigurationException("Unable to load the configuration", e);
+        }
+    }
+
+    /**
+     * Parse the value to remove the quotes and ignoring the comment.
+     * Example:
+     *
+     * <pre>"value" ; comment -> value</pre>
+     *
+     * <pre>'value' ; comment -> value</pre>
+     *
+     * @param value
+     */
+    private String parseValue(String value)
+    {
+        value = value.trim();
+
+        boolean quoted = value.startsWith("\"") || value.startsWith("'");
+        boolean stop = false;
+        boolean escape = false;
+
+        char quote = quoted ? value.charAt(0) : 0;
+
+        int i = quoted ? 1 : 0;
+
+        StringBuilder result = new StringBuilder();
+        while (i < value.length() && !stop)
+        {
+            char c = value.charAt(i);
+
+            if (quoted)
+            {
+                if ('\\' == c && !escape)
+                {
+                    escape = true;
+                }
+                else if (!escape && quote == c)
+                {
+                    stop = true;
+                }
+                else if (escape && quote == c)
+                {
+                    escape = false;
+                    result.append(c);
+                }
+                else
+                {
+                    if (escape)
+                    {
+                        escape = false;
+                        result.append('\\');
+                    }
+
+                    result.append(c);
+                }
+            }
+            else
+            {
+                if (COMMENT_CHARS.indexOf(c) == -1)
+                {
+                    result.append(c);
+                }
+                else
+                {
+                    stop = true;
+                }
+            }
+
+            i++;
+        }
+
+        String v = result.toString();
+        if (!quoted)
+        {
+            v = v.trim();
+        }
+        return v;
+    }
+
+    /**
+     * Add quotes around the specified value if it contains a comment character.
+     */
+    private String formatValue(String value)
+    {
+        boolean quoted = false;
+
+        for (int i = 0; i < COMMENT_CHARS.length() && !quoted; i++)
+        {
+            char c = COMMENT_CHARS.charAt(i);
+            if (value.indexOf(c) != -1)
+            {
+                quoted = true;
+            }
+        }
+
+        if (quoted)
+        {
+            return '"' + value.replaceAll("\"", "\\\\\\\"") + '"';
+        }
+        else
+        {
+            return value;
+        }
+    }
+
+    /**
+     * Determine if the given line is a comment line.
+     *
+     * @param line The line to check.
+     * @return true if the line is empty or starts with one of the comment
+     * characters
+     */
+    protected boolean isCommentLine(String line)
+    {
+        if (line == null)
+        {
+            return false;
+        }
+        // blank lines are also treated as comment lines
+        return line.length() < 1 || COMMENT_CHARS.indexOf(line.charAt(0)) >= 0;
+    }
+
+    /**
+     * Determine if the given line is a section.
+     *
+     * @param line The line to check.
+     * @return true if the line contains a secion
+     */
+    protected boolean isSectionLine(String line)
+    {
+        if (line == null)
+        {
+            return false;
+        }
+        return line.startsWith("[") && line.endsWith("]");
+    }
+
+    /**
+     * Return a set containing the sections in this ini configuration. Note that
+     * changes to this set do not affect the configuration.
+     *
+     * @return a set containing the sections.
+     */
+    public Set<String> getSections()
+    {
+        Set<String> sections = new TreeSet<String>();
+
+        Iterator<String> keys = getKeys();
+        while (keys.hasNext())
+        {
+            String key = keys.next();
+            int index = key.indexOf(".");
+            if (index >= 0)
+            {
+                sections.add(key.substring(0, index));
+            }
+        }
+
+        return sections;
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/JNDIConfiguration.java b/src/main/java/org/apache/commons/configuration/JNDIConfiguration.java
new file mode 100644
index 0000000..3baf38a
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/JNDIConfiguration.java
@@ -0,0 +1,482 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NameClassPair;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.NotContextException;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * This Configuration class allows you to interface with a JNDI datasource.
+ * A JNDIConfiguration is read-only, write operations will throw an
+ * UnsupportedOperationException. The clear operations are supported but the
+ * underlying JNDI data source is not changed.
+ *
+ * @author <a href="mailto:epugh at upstate.com">Eric Pugh</a>
+ * @version $Id: JNDIConfiguration.java 1234985 2012-01-23 21:09:09Z oheger $
+ */
+public class JNDIConfiguration extends AbstractConfiguration
+{
+    /** The prefix of the context. */
+    private String prefix;
+
+    /** The initial JNDI context. */
+    private Context context;
+
+    /** The base JNDI context. */
+    private Context baseContext;
+
+    /** The Set of keys that have been virtually cleared. */
+    private Set<String> clearedProperties = new HashSet<String>();
+
+    /**
+     * Creates a JNDIConfiguration using the default initial context as the
+     * root of the properties.
+     *
+     * @throws NamingException thrown if an error occurs when initializing the default context
+     */
+    public JNDIConfiguration() throws NamingException
+    {
+        this((String) null);
+    }
+
+    /**
+     * Creates a JNDIConfiguration using the default initial context, shifted
+     * with the specified prefix, as the root of the properties.
+     *
+     * @param prefix the prefix
+     *
+     * @throws NamingException thrown if an error occurs when initializing the default context
+     */
+    public JNDIConfiguration(String prefix) throws NamingException
+    {
+        this(new InitialContext(), prefix);
+    }
+
+    /**
+     * Creates a JNDIConfiguration using the specified initial context as the
+     * root of the properties.
+     *
+     * @param context the initial context
+     */
+    public JNDIConfiguration(Context context)
+    {
+        this(context, null);
+    }
+
+    /**
+     * Creates a JNDIConfiguration using the specified initial context shifted
+     * by the specified prefix as the root of the properties.
+     *
+     * @param context the initial context
+     * @param prefix the prefix
+     */
+    public JNDIConfiguration(Context context, String prefix)
+    {
+        this.context = context;
+        this.prefix = prefix;
+        setLogger(LogFactory.getLog(getClass()));
+        addErrorLogListener();
+    }
+
+    /**
+     * This method recursive traverse the JNDI tree, looking for Context objects.
+     * When it finds them, it traverses them as well.  Otherwise it just adds the
+     * values to the list of keys found.
+     *
+     * @param keys All the keys that have been found.
+     * @param context The parent context
+     * @param prefix What prefix we are building on.
+     * @param processedCtx a set with the so far processed objects
+     * @throws NamingException If JNDI has an issue.
+     */
+    private void recursiveGetKeys(Set<String> keys, Context context, String prefix,
+            Set<Context> processedCtx) throws NamingException
+    {
+        processedCtx.add(context);
+        NamingEnumeration<NameClassPair> elements = null;
+
+        try
+        {
+            elements = context.list("");
+
+            // iterates through the context's elements
+            while (elements.hasMore())
+            {
+                NameClassPair nameClassPair = elements.next();
+                String name = nameClassPair.getName();
+                Object object = context.lookup(name);
+
+                // build the key
+                StringBuilder key = new StringBuilder();
+                key.append(prefix);
+                if (key.length() > 0)
+                {
+                    key.append(".");
+                }
+                key.append(name);
+
+                if (object instanceof Context)
+                {
+                    // add the keys of the sub context
+                    Context subcontext = (Context) object;
+                    if (!processedCtx.contains(subcontext))
+                    {
+                        recursiveGetKeys(keys, subcontext, key.toString(),
+                                processedCtx);
+                    }
+                }
+                else
+                {
+                    // add the key
+                    keys.add(key.toString());
+                }
+            }
+        }
+        finally
+        {
+            // close the enumeration
+            if (elements != null)
+            {
+                elements.close();
+            }
+        }
+    }
+
+    /**
+     * Returns an iterator with all property keys stored in this configuration.
+     *
+     * @return an iterator with all keys
+     */
+    public Iterator<String> getKeys()
+    {
+        return getKeys("");
+    }
+
+    /**
+     * Returns an iterator with all property keys starting with the given
+     * prefix.
+     *
+     * @param prefix the prefix
+     * @return an iterator with the selected keys
+     */
+    @Override
+    public Iterator<String> getKeys(String prefix)
+    {
+        // build the path
+        String[] splitPath = StringUtils.split(prefix, ".");
+
+        List<String> path = Arrays.asList(splitPath);
+
+        try
+        {
+            // find the context matching the specified path
+            Context context = getContext(path, getBaseContext());
+
+            // return all the keys under the context found
+            Set<String> keys = new HashSet<String>();
+            if (context != null)
+            {
+                recursiveGetKeys(keys, context, prefix, new HashSet<Context>());
+            }
+            else if (containsKey(prefix))
+            {
+                // add the prefix if it matches exactly a property key
+                keys.add(prefix);
+            }
+
+            return keys.iterator();
+        }
+        catch (NameNotFoundException e)
+        {
+            // expected exception, no need to log it
+            return new ArrayList<String>().iterator();
+        }
+        catch (NamingException e)
+        {
+            fireError(EVENT_READ_PROPERTY, null, null, e);
+            return new ArrayList<String>().iterator();
+        }
+    }
+
+    /**
+     * Because JNDI is based on a tree configuration, we need to filter down the
+     * tree, till we find the Context specified by the key to start from.
+     * Otherwise return null.
+     *
+     * @param path     the path of keys to traverse in order to find the context
+     * @param context  the context to start from
+     * @return The context at that key's location in the JNDI tree, or null if not found
+     * @throws NamingException if JNDI has an issue
+     */
+    private Context getContext(List<String> path, Context context) throws NamingException
+    {
+        // return the current context if the path is empty
+        if (path == null || path.isEmpty())
+        {
+            return context;
+        }
+
+        String key = path.get(0);
+
+        // search a context matching the key in the context's elements
+        NamingEnumeration<NameClassPair> elements = null;
+
+        try
+        {
+            elements = context.list("");
+            while (elements.hasMore())
+            {
+                NameClassPair nameClassPair = elements.next();
+                String name = nameClassPair.getName();
+                Object object = context.lookup(name);
+
+                if (object instanceof Context && name.equals(key))
+                {
+                    Context subcontext = (Context) object;
+
+                    // recursive search in the sub context
+                    return getContext(path.subList(1, path.size()), subcontext);
+                }
+            }
+        }
+        finally
+        {
+            if (elements != null)
+            {
+                elements.close();
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns a flag whether this configuration is empty.
+     *
+     * @return the empty flag
+     */
+    public boolean isEmpty()
+    {
+        try
+        {
+            NamingEnumeration<NameClassPair> enumeration = null;
+
+            try
+            {
+                enumeration = getBaseContext().list("");
+                return !enumeration.hasMore();
+            }
+            finally
+            {
+                // close the enumeration
+                if (enumeration != null)
+                {
+                    enumeration.close();
+                }
+            }
+        }
+        catch (NamingException e)
+        {
+            fireError(EVENT_READ_PROPERTY, null, null, e);
+            return true;
+        }
+    }
+
+    /**
+     * <p><strong>This operation is not supported and will throw an
+     * UnsupportedOperationException.</strong></p>
+     *
+     * @param key the key
+     * @param value the value
+     * @throws UnsupportedOperationException
+     */
+    @Override
+    public void setProperty(String key, Object value)
+    {
+        throw new UnsupportedOperationException("This operation is not supported");
+    }
+
+    /**
+     * Removes the specified property.
+     *
+     * @param key the key of the property to remove
+     */
+    @Override
+    public void clearProperty(String key)
+    {
+        clearedProperties.add(key);
+    }
+
+    /**
+     * Checks whether the specified key is contained in this configuration.
+     *
+     * @param key the key to check
+     * @return a flag whether this key is stored in this configuration
+     */
+    public boolean containsKey(String key)
+    {
+        if (clearedProperties.contains(key))
+        {
+            return false;
+        }
+        key = key.replaceAll("\\.", "/");
+        try
+        {
+            // throws a NamingException if JNDI doesn't contain the key.
+            getBaseContext().lookup(key);
+            return true;
+        }
+        catch (NameNotFoundException e)
+        {
+            // expected exception, no need to log it
+            return false;
+        }
+        catch (NamingException e)
+        {
+            fireError(EVENT_READ_PROPERTY, key, null, e);
+            return false;
+        }
+    }
+
+    /**
+     * Returns the prefix.
+     * @return the prefix
+     */
+    public String getPrefix()
+    {
+        return prefix;
+    }
+
+    /**
+     * Sets the prefix.
+     *
+     * @param prefix The prefix to set
+     */
+    public void setPrefix(String prefix)
+    {
+        this.prefix = prefix;
+
+        // clear the previous baseContext
+        baseContext = null;
+    }
+
+    /**
+     * Returns the value of the specified property.
+     *
+     * @param key the key of the property
+     * @return the value of this property
+     */
+    public Object getProperty(String key)
+    {
+        if (clearedProperties.contains(key))
+        {
+            return null;
+        }
+
+        try
+        {
+            key = key.replaceAll("\\.", "/");
+            return getBaseContext().lookup(key);
+        }
+        catch (NameNotFoundException e)
+        {
+            // expected exception, no need to log it
+            return null;
+        }
+        catch (NotContextException nctxex)
+        {
+            // expected exception, no need to log it
+            return null;
+        }
+        catch (NamingException e)
+        {
+            fireError(EVENT_READ_PROPERTY, key, null, e);
+            return null;
+        }
+    }
+
+    /**
+     * <p><strong>This operation is not supported and will throw an
+     * UnsupportedOperationException.</strong></p>
+     *
+     * @param key the key
+     * @param obj the value
+     * @throws UnsupportedOperationException
+     */
+    @Override
+    protected void addPropertyDirect(String key, Object obj)
+    {
+        throw new UnsupportedOperationException("This operation is not supported");
+    }
+
+    /**
+     * Return the base context with the prefix applied.
+     *
+     * @return the base context
+     * @throws NamingException if an error occurs
+     */
+    public Context getBaseContext() throws NamingException
+    {
+        if (baseContext == null)
+        {
+            baseContext = (Context) getContext().lookup(prefix == null ? "" : prefix);
+        }
+
+        return baseContext;
+    }
+
+    /**
+     * Return the initial context used by this configuration. This context is
+     * independent of the prefix specified.
+     *
+     * @return the initial context
+     */
+    public Context getContext()
+    {
+        return context;
+    }
+
+    /**
+     * Set the initial context of the configuration.
+     *
+     * @param context the context
+     */
+    public void setContext(Context context)
+    {
+        // forget the removed properties
+        clearedProperties.clear();
+
+        // change the context
+        this.context = context;
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/Lock.java b/src/main/java/org/apache/commons/configuration/Lock.java
new file mode 100644
index 0000000..8a12bf2
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/Lock.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+/**
+ * <p>
+ * A simple class acting as lock.
+ * </p>
+ * <p>
+ * Instances of this class are used by some configuration classes to synchronize
+ * themselves.
+ * </p>
+ *
+ * @author <a
+ *         href="http://commons.apache.org/configuration/team-list.html">Commons
+ *         Configuration team</a>
+ * @since 1.7
+ * @version $Id: Lock.java 1301995 2012-03-17 20:24:16Z sebb $
+ */
+public class Lock
+{
+    /** A string used internally to synchronize counter updates. */
+    private static String counterLock = "Lock";
+
+    /** A counter for generating unique instance IDs. */
+    private static int counter;
+
+    /** The name of this lock. */
+    private final String name;
+
+    /** The unique ID of this lock instance. */
+    private final int instanceId;
+
+    /**
+     * Creates a new instance of {@code Lock} with the specified name.
+     *
+     * @param name the name of this lock
+     */
+    public Lock(String name)
+    {
+        this.name = name;
+        synchronized (counterLock)
+        {
+            instanceId = ++counter;
+        }
+    }
+
+    /**
+     * Returns the name of this lock.
+     *
+     * @return the name of this lock
+     */
+    public String getName()
+    {
+        return name;
+    }
+
+    /**
+     * Returns a string representation of this object. This implementation
+     * returns a string which contains the lock name and the instance ID.
+     *
+     * @return a string for this object
+     */
+    @Override
+    public String toString()
+    {
+        return "Lock: " + name + " id = " + instanceId + ": "
+                + super.toString();
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/MapConfiguration.java b/src/main/java/org/apache/commons/configuration/MapConfiguration.java
new file mode 100644
index 0000000..f6d8b4d
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/MapConfiguration.java
@@ -0,0 +1,295 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+/**
+ * <p>
+ * A Map based Configuration.
+ * </p>
+ * <p>
+ * This implementation of the {@code Configuration} interface is
+ * initialized with a {@code java.util.Map}. The methods of the
+ * {@code Configuration} interface are implemented on top of the content of
+ * this map. The following storage scheme is used:
+ * </p>
+ * <p>
+ * Property keys are directly mapped to map keys, i.e. the
+ * {@code getProperty()} method directly performs a {@code get()} on
+ * the map. Analogously, {@code setProperty()} or
+ * {@code addProperty()} operations write new data into the map. If a value
+ * is added to an existing property, a {@code java.util.List} is created,
+ * which stores the values of this property.
+ * </p>
+ * <p>
+ * An important use case of this class is to treat a map as a
+ * {@code Configuration} allowing access to its data through the richer
+ * interface. This can be a bit problematic in some cases because the map may
+ * contain values that need not adhere to the default storage scheme used by
+ * typical configuration implementations, e.g. regarding lists. In such cases
+ * care must be taken when manipulating the data through the
+ * {@code Configuration} interface, e.g. by calling
+ * {@code addProperty()}; results may be different than expected.
+ * </p>
+ * <p>
+ * An important point is the handling of list delimiters: If delimiter parsing
+ * is enabled (which it is per default), {@code getProperty()} checks
+ * whether the value of a property is a string and whether it contains the list
+ * delimiter character. If this is the case, the value is split at the delimiter
+ * resulting in a list. This split operation typically also involves trimming
+ * the single values as the list delimiter character may be surrounded by
+ * whitespace. Trimming can be disabled with the
+ * {@link #setTrimmingDisabled(boolean)} method. The whole list splitting
+ * behavior can be disabled using the
+ * {@link #setDelimiterParsingDisabled(boolean)} method.
+ * </p>
+ * <p>
+ * Notice that list splitting is only performed for single string values. If a
+ * property has multiple values, the single values are not split even if they
+ * contain the list delimiter character.
+ * </p>
+ * <p>
+ * As the underlying {@code Map} is directly used as store of the property
+ * values, the thread-safety of this {@code Configuration} implementation
+ * depends on the map passed to the constructor.
+ * </p>
+ * <p>
+ * Notes about type safety: For properties with multiple values this implementation
+ * creates lists of type {@code Object} and stores them. If a property is assigned
+ * another value, the value is added to the list. This can cause problems if the
+ * map passed to the constructor already contains lists of other types. This
+ * should be avoided, otherwise it cannot be guaranteed that the application
+ * might throw {@code ClassCastException} exceptions later.
+ * </p>
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: MapConfiguration.java 1534429 2013-10-22 00:45:36Z henning $
+ * @since 1.1
+ */
+public class MapConfiguration extends AbstractConfiguration implements Cloneable
+{
+    /** The Map decorated by this configuration. */
+    protected Map<String, Object> map;
+
+    /** A flag whether trimming of property values should be disabled.*/
+    private boolean trimmingDisabled;
+
+    /**
+     * Create a Configuration decorator around the specified Map. The map is
+     * used to store the configuration properties, any change will also affect
+     * the Map.
+     *
+     * @param map the map
+     */
+    public MapConfiguration(Map<String, ?> map)
+    {
+        this.map = (Map<String, Object>) map;
+    }
+
+    /**
+     * Creates a new instance of {@code MapConfiguration} and initializes its
+     * content from the specified {@code Properties} object. The resulting
+     * configuration is not connected to the {@code Properties} object, but all
+     * keys which are strings are copied (keys of other types are ignored).
+     *
+     * @param props the {@code Properties} object defining the content of this
+     *        configuration
+     * @throws NullPointerException if the {@code Properties} object is
+     *         <b>null</b>
+     * @since 1.8
+     */
+    public MapConfiguration(Properties props)
+    {
+        map = convertPropertiesToMap(props);
+    }
+
+    /**
+     * Return the Map decorated by this configuration.
+     *
+     * @return the map this configuration is based onto
+     */
+    public Map<String, Object> getMap()
+    {
+        return map;
+    }
+
+    /**
+     * Returns the flag whether trimming of property values is disabled.
+     *
+     * @return <b>true</b> if trimming of property values is disabled;
+     *         <b>false</b> otherwise
+     * @since 1.7
+     */
+    public boolean isTrimmingDisabled()
+    {
+        return trimmingDisabled;
+    }
+
+    /**
+     * Sets a flag whether trimming of property values is disabled. This flag is
+     * only evaluated if list splitting is enabled. Refer to the header comment
+     * for more information about list splitting and trimming.
+     *
+     * @param trimmingDisabled a flag whether trimming of property values should
+     *        be disabled
+     * @since 1.7
+     */
+    public void setTrimmingDisabled(boolean trimmingDisabled)
+    {
+        this.trimmingDisabled = trimmingDisabled;
+    }
+
+    public Object getProperty(String key)
+    {
+        Object value = map.get(key);
+        if ((value instanceof String) && (!isDelimiterParsingDisabled()))
+        {
+            List<String> list = PropertyConverter.split((String) value, getListDelimiter(), !isTrimmingDisabled());
+            return list.size() > 1 ? list : list.get(0);
+        }
+        else
+        {
+            return value;
+        }
+    }
+
+    @Override
+    protected void addPropertyDirect(String key, Object value)
+    {
+        Object previousValue = getProperty(key);
+
+        if (previousValue == null)
+        {
+            map.put(key, value);
+        }
+        else if (previousValue instanceof List)
+        {
+            // the value is added to the existing list
+            // Note: This is problematic. See header comment!
+            ((List<Object>) previousValue).add(value);
+        }
+        else
+        {
+            // the previous value is replaced by a list containing the previous value and the new value
+            List<Object> list = new ArrayList<Object>();
+            list.add(previousValue);
+            list.add(value);
+
+            map.put(key, list);
+        }
+    }
+
+    public boolean isEmpty()
+    {
+        return map.isEmpty();
+    }
+
+    public boolean containsKey(String key)
+    {
+        return map.containsKey(key);
+    }
+
+    @Override
+    protected void clearPropertyDirect(String key)
+    {
+        map.remove(key);
+    }
+
+    public Iterator<String> getKeys()
+    {
+        return map.keySet().iterator();
+    }
+
+    /**
+     * Returns a copy of this object. The returned configuration will contain
+     * the same properties as the original. Event listeners are not cloned.
+     *
+     * @return the copy
+     * @since 1.3
+     */
+    @Override
+    public Object clone()
+    {
+        try
+        {
+            MapConfiguration copy = (MapConfiguration) super.clone();
+            copy.clearConfigurationListeners();
+            // Safe because ConfigurationUtils returns a map of the same types.
+            @SuppressWarnings("unchecked")
+            Map<String, Object> clonedMap = (Map<String, Object>) ConfigurationUtils.clone(map);
+            copy.map = clonedMap;
+            return copy;
+        }
+        catch (CloneNotSupportedException cex)
+        {
+            // cannot happen
+            throw new ConfigurationRuntimeException(cex);
+        }
+    }
+
+    /**
+     * Helper method for copying all string keys from the given
+     * {@code Properties} object to a newly created map.
+     *
+     * @param props the {@code Properties} to be copied
+     * @return a newly created map with all string keys of the properties
+     */
+    private static Map<String, Object> convertPropertiesToMap(final Properties props)
+    {
+        return new AbstractMap<String, Object>() {
+
+            @Override
+            public Set<Map.Entry<String, Object>> entrySet()
+            {
+                Set<Map.Entry<String, Object>> entries = new HashSet<Map.Entry<String, Object>>();
+                for (final Map.Entry<Object, Object> propertyEntry : props.entrySet())
+                {
+                    if (propertyEntry.getKey() instanceof String)
+                    {
+                        entries.add(new Map.Entry<String, Object>() {
+
+                            public String getKey()
+                            {
+                                return propertyEntry.getKey().toString();
+                            }
+
+                            public Object getValue()
+                            {
+                                return propertyEntry.getValue();
+                            }
+
+                            public Object setValue(Object value)
+                            {
+                                throw new UnsupportedOperationException();
+                            }
+                        });
+                    }
+                }
+                return entries;
+            }
+        };
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/MultiFileHierarchicalConfiguration.java b/src/main/java/org/apache/commons/configuration/MultiFileHierarchicalConfiguration.java
new file mode 100644
index 0000000..8a90255
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/MultiFileHierarchicalConfiguration.java
@@ -0,0 +1,871 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.commons.beanutils.BeanUtils;
+import org.apache.commons.configuration.event.ConfigurationErrorEvent;
+import org.apache.commons.configuration.event.ConfigurationErrorListener;
+import org.apache.commons.configuration.event.ConfigurationEvent;
+import org.apache.commons.configuration.event.ConfigurationListener;
+import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
+import org.apache.commons.configuration.reloading.ReloadingStrategy;
+import org.apache.commons.configuration.resolver.EntityResolverSupport;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.ExpressionEngine;
+import org.apache.commons.lang.text.StrSubstitutor;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.SAXParseException;
+
+/**
+ * This class provides access to multiple configuration files that reside in a location that
+ * can be specified by a pattern allowing applications to be multi-tenant.  For example,
+ * providing a pattern of "file:///opt/config/${product}/${client}/config.xml" will result in
+ * "product" and "client" being resolved on every call. The configuration resulting from the
+ * resolved pattern will be saved for future access.
+ * @since 1.6
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: MultiFileHierarchicalConfiguration.java 1534064 2013-10-21 08:44:33Z henning $
+ */
+public class MultiFileHierarchicalConfiguration extends AbstractHierarchicalFileConfiguration
+    implements ConfigurationListener, ConfigurationErrorListener, EntityResolverSupport
+{
+    /**
+     * Prevent recursion while resolving unprefixed properties.
+     */
+    private static ThreadLocal<Boolean> recursive = new ThreadLocal<Boolean>()
+    {
+        @Override
+        protected synchronized Boolean initialValue()
+        {
+            return Boolean.FALSE;
+        }
+    };
+
+    /** Map of configurations */
+    private final ConcurrentMap<String, XMLConfiguration> configurationsMap =
+            new ConcurrentHashMap<String, XMLConfiguration>();
+
+    /** key pattern for configurationsMap */
+    private String pattern;
+
+    /** True if the constructor has finished */
+    private boolean init;
+
+    /** Return an empty configuration if loading fails */
+    private boolean ignoreException = true;
+
+    /** Capture the schema validation setting */
+    private boolean schemaValidation;
+
+    /** Stores a flag whether DTD or Schema validation should be performed.*/
+    private boolean validating;
+
+    /** A flag whether attribute splitting is disabled.*/
+    private boolean attributeSplittingDisabled;
+
+    /** The Logger name to use */
+    private String loggerName = MultiFileHierarchicalConfiguration.class.getName();
+
+    /** The Reloading strategy to use on created configurations */
+    private ReloadingStrategy fileStrategy;
+
+    /** The EntityResolver */
+    private EntityResolver entityResolver;
+
+    /** The internally used helper object for variable substitution. */
+    private StrSubstitutor localSubst = new StrSubstitutor(new ConfigurationInterpolator());
+
+    /**
+     * Default Constructor.
+     */
+    public MultiFileHierarchicalConfiguration()
+    {
+        super();
+        this.init = true;
+        setLogger(LogFactory.getLog(loggerName));
+    }
+
+    /**
+     * Construct the configuration with the specified pattern.
+     * @param pathPattern The pattern to use to locate configuration files.
+     */
+    public MultiFileHierarchicalConfiguration(String pathPattern)
+    {
+        super();
+        this.pattern = pathPattern;
+        this.init = true;
+        setLogger(LogFactory.getLog(loggerName));
+    }
+
+    public void setLoggerName(String name)
+    {
+        this.loggerName = name;
+    }
+
+    /**
+     * Set the File pattern
+     * @param pathPattern The pattern for the path to the configuration.
+     */
+    public void setFilePattern(String pathPattern)
+    {
+        this.pattern = pathPattern;
+    }
+
+    public boolean isSchemaValidation()
+    {
+        return schemaValidation;
+    }
+
+    public void setSchemaValidation(boolean schemaValidation)
+    {
+        this.schemaValidation = schemaValidation;
+    }
+
+    public boolean isValidating()
+    {
+        return validating;
+    }
+
+    public void setValidating(boolean validating)
+    {
+        this.validating = validating;
+    }
+
+    public boolean isAttributeSplittingDisabled()
+    {
+        return attributeSplittingDisabled;
+    }
+
+    public void setAttributeSplittingDisabled(boolean attributeSplittingDisabled)
+    {
+        this.attributeSplittingDisabled = attributeSplittingDisabled;
+    }
+
+    @Override
+    public ReloadingStrategy getReloadingStrategy()
+    {
+        return fileStrategy;
+    }
+
+    @Override
+    public void setReloadingStrategy(ReloadingStrategy strategy)
+    {
+        this.fileStrategy = strategy;
+    }
+
+    public void setEntityResolver(EntityResolver entityResolver)
+    {
+        this.entityResolver = entityResolver;
+    }
+
+    public EntityResolver getEntityResolver()
+    {
+        return this.entityResolver;
+    }
+
+    /**
+     * Set to true if an empty Configuration should be returned when loading fails. If
+     * false an exception will be thrown.
+     * @param ignoreException The ignore value.
+     */
+    public void setIgnoreException(boolean ignoreException)
+    {
+        this.ignoreException = ignoreException;
+    }
+
+    @Override
+    public void addProperty(String key, Object value)
+    {
+        this.getConfiguration().addProperty(key, value);
+    }
+
+    @Override
+    public void clear()
+    {
+        this.getConfiguration().clear();
+    }
+
+    @Override
+    public void clearProperty(String key)
+    {
+        this.getConfiguration().clearProperty(key);
+    }
+
+    @Override
+    public boolean containsKey(String key)
+    {
+        return this.getConfiguration().containsKey(key);
+    }
+
+    @Override
+    public BigDecimal getBigDecimal(String key, BigDecimal defaultValue)
+    {
+        return this.getConfiguration().getBigDecimal(key, defaultValue);
+    }
+
+    @Override
+    public BigDecimal getBigDecimal(String key)
+    {
+        return this.getConfiguration().getBigDecimal(key);
+    }
+
+    @Override
+    public BigInteger getBigInteger(String key, BigInteger defaultValue)
+    {
+        return this.getConfiguration().getBigInteger(key, defaultValue);
+    }
+
+    @Override
+    public BigInteger getBigInteger(String key)
+    {
+        return this.getConfiguration().getBigInteger(key);
+    }
+
+    @Override
+    public boolean getBoolean(String key, boolean defaultValue)
+    {
+        return this.getConfiguration().getBoolean(key, defaultValue);
+    }
+
+    @Override
+    public Boolean getBoolean(String key, Boolean defaultValue)
+    {
+        return this.getConfiguration().getBoolean(key, defaultValue);
+    }
+
+    @Override
+    public boolean getBoolean(String key)
+    {
+        return this.getConfiguration().getBoolean(key);
+    }
+
+    @Override
+    public byte getByte(String key, byte defaultValue)
+    {
+        return this.getConfiguration().getByte(key, defaultValue);
+    }
+
+    @Override
+    public Byte getByte(String key, Byte defaultValue)
+    {
+        return this.getConfiguration().getByte(key, defaultValue);
+    }
+
+    @Override
+    public byte getByte(String key)
+    {
+        return this.getConfiguration().getByte(key);
+    }
+
+    @Override
+    public double getDouble(String key, double defaultValue)
+    {
+        return this.getConfiguration().getDouble(key, defaultValue);
+    }
+
+    @Override
+    public Double getDouble(String key, Double defaultValue)
+    {
+        return this.getConfiguration().getDouble(key, defaultValue);
+    }
+
+    @Override
+    public double getDouble(String key)
+    {
+        return this.getConfiguration().getDouble(key);
+    }
+
+    @Override
+    public float getFloat(String key, float defaultValue)
+    {
+        return this.getConfiguration().getFloat(key, defaultValue);
+    }
+
+    @Override
+    public Float getFloat(String key, Float defaultValue)
+    {
+        return this.getConfiguration().getFloat(key, defaultValue);
+    }
+
+    @Override
+    public float getFloat(String key)
+    {
+        return this.getConfiguration().getFloat(key);
+    }
+
+    @Override
+    public int getInt(String key, int defaultValue)
+    {
+        return this.getConfiguration().getInt(key, defaultValue);
+    }
+
+    @Override
+    public int getInt(String key)
+    {
+        return this.getConfiguration().getInt(key);
+    }
+
+    @Override
+    public Integer getInteger(String key, Integer defaultValue)
+    {
+        return this.getConfiguration().getInteger(key, defaultValue);
+    }
+
+    @Override
+    public Iterator<String> getKeys()
+    {
+        return this.getConfiguration().getKeys();
+    }
+
+    @Override
+    public Iterator<String> getKeys(String prefix)
+    {
+        return this.getConfiguration().getKeys(prefix);
+    }
+
+    @Override
+    public List<Object> getList(String key, List<?> defaultValue)
+    {
+        return this.getConfiguration().getList(key, defaultValue);
+    }
+
+    @Override
+    public List<Object> getList(String key)
+    {
+        return this.getConfiguration().getList(key);
+    }
+
+    @Override
+    public long getLong(String key, long defaultValue)
+    {
+        return this.getConfiguration().getLong(key, defaultValue);
+    }
+
+    @Override
+    public Long getLong(String key, Long defaultValue)
+    {
+        return this.getConfiguration().getLong(key, defaultValue);
+    }
+
+    @Override
+    public long getLong(String key)
+    {
+        return this.getConfiguration().getLong(key);
+    }
+
+    @Override
+    public Properties getProperties(String key)
+    {
+        return this.getConfiguration().getProperties(key);
+    }
+
+    @Override
+    public Object getProperty(String key)
+    {
+        return this.getConfiguration().getProperty(key);
+    }
+
+    @Override
+    public short getShort(String key, short defaultValue)
+    {
+        return this.getConfiguration().getShort(key, defaultValue);
+    }
+
+    @Override
+    public Short getShort(String key, Short defaultValue)
+    {
+        return this.getConfiguration().getShort(key, defaultValue);
+    }
+
+    @Override
+    public short getShort(String key)
+    {
+        return this.getConfiguration().getShort(key);
+    }
+
+    @Override
+    public String getString(String key, String defaultValue)
+    {
+        return this.getConfiguration().getString(key, defaultValue);
+    }
+
+    @Override
+    public String getString(String key)
+    {
+        return this.getConfiguration().getString(key);
+    }
+
+    @Override
+    public String[] getStringArray(String key)
+    {
+        return this.getConfiguration().getStringArray(key);
+    }
+
+    @Override
+    public boolean isEmpty()
+    {
+        return this.getConfiguration().isEmpty();
+    }
+
+    @Override
+    public void setProperty(String key, Object value)
+    {
+        if (init)
+        {
+            this.getConfiguration().setProperty(key, value);
+        }
+    }
+
+    @Override
+    public Configuration subset(String prefix)
+    {
+        return this.getConfiguration().subset(prefix);
+    }
+
+    @Override
+    public Object getReloadLock()
+    {
+        return this.getConfiguration().getReloadLock();
+    }
+
+    @Override
+    public Node getRoot()
+    {
+        return this.getConfiguration().getRoot();
+    }
+
+    @Override
+    public void setRoot(Node node)
+    {
+        if (init)
+        {
+            this.getConfiguration().setRoot(node);
+        }
+        else
+        {
+            super.setRoot(node);
+        }
+    }
+
+    @Override
+    public ConfigurationNode getRootNode()
+    {
+        return this.getConfiguration().getRootNode();
+    }
+
+    @Override
+    public void setRootNode(ConfigurationNode rootNode)
+    {
+        if (init)
+        {
+            this.getConfiguration().setRootNode(rootNode);
+        }
+        else
+        {
+            super.setRootNode(rootNode);
+        }
+    }
+
+    @Override
+    public ExpressionEngine getExpressionEngine()
+    {
+        return super.getExpressionEngine();
+    }
+
+    @Override
+    public void setExpressionEngine(ExpressionEngine expressionEngine)
+    {
+        super.setExpressionEngine(expressionEngine);
+    }
+
+    @Override
+    public void addNodes(String key, Collection<? extends ConfigurationNode> nodes)
+    {
+        this.getConfiguration().addNodes(key, nodes);
+    }
+
+    @Override
+    public SubnodeConfiguration configurationAt(String key, boolean supportUpdates)
+    {
+        return this.getConfiguration().configurationAt(key, supportUpdates);
+    }
+
+    @Override
+    public SubnodeConfiguration configurationAt(String key)
+    {
+        return this.getConfiguration().configurationAt(key);
+    }
+
+    @Override
+    public List<HierarchicalConfiguration> configurationsAt(String key)
+    {
+        return this.getConfiguration().configurationsAt(key);
+    }
+
+    @Override
+    public void clearTree(String key)
+    {
+        this.getConfiguration().clearTree(key);
+    }
+
+    @Override
+    public int getMaxIndex(String key)
+    {
+        return this.getConfiguration().getMaxIndex(key);
+    }
+
+    @Override
+    public Configuration interpolatedConfiguration()
+    {
+        return this.getConfiguration().interpolatedConfiguration();
+    }
+
+    @Override
+    public void addConfigurationListener(ConfigurationListener l)
+    {
+        super.addConfigurationListener(l);
+    }
+
+    @Override
+    public boolean removeConfigurationListener(ConfigurationListener l)
+    {
+        return super.removeConfigurationListener(l);
+    }
+
+    @Override
+    public Collection<ConfigurationListener> getConfigurationListeners()
+    {
+        return super.getConfigurationListeners();
+    }
+
+    @Override
+    public void clearConfigurationListeners()
+    {
+        super.clearConfigurationListeners();
+    }
+
+    @Override
+    public void addErrorListener(ConfigurationErrorListener l)
+    {
+        super.addErrorListener(l);
+    }
+
+    @Override
+    public boolean removeErrorListener(ConfigurationErrorListener l)
+    {
+        return super.removeErrorListener(l);
+    }
+
+    @Override
+    public void clearErrorListeners()
+    {
+        super.clearErrorListeners();
+    }
+
+    @Override
+    public Collection<ConfigurationErrorListener> getErrorListeners()
+    {
+        return super.getErrorListeners();
+    }
+
+    public void save(Writer writer) throws ConfigurationException
+    {
+        if (init)
+        {
+            this.getConfiguration().save(writer);
+        }
+    }
+
+    public void load(Reader reader) throws ConfigurationException
+    {
+        if (init)
+        {
+            this.getConfiguration().load(reader);
+        }
+    }
+
+    @Override
+    public void load() throws ConfigurationException
+    {
+        this.getConfiguration();
+    }
+
+    @Override
+    public void load(String fileName) throws ConfigurationException
+    {
+        this.getConfiguration().load(fileName);
+    }
+
+    @Override
+    public void load(File file) throws ConfigurationException
+    {
+        this.getConfiguration().load(file);
+    }
+
+    @Override
+    public void load(URL url) throws ConfigurationException
+    {
+        this.getConfiguration().load(url);
+    }
+
+    @Override
+    public void load(InputStream in) throws ConfigurationException
+    {
+        this.getConfiguration().load(in);
+    }
+
+    @Override
+    public void load(InputStream in, String encoding) throws ConfigurationException
+    {
+        this.getConfiguration().load(in, encoding);
+    }
+
+    @Override
+    public void save() throws ConfigurationException
+    {
+        this.getConfiguration().save();
+    }
+
+    @Override
+    public void save(String fileName) throws ConfigurationException
+    {
+        this.getConfiguration().save(fileName);
+    }
+
+    @Override
+    public void save(File file) throws ConfigurationException
+    {
+        this.getConfiguration().save(file);
+    }
+
+    @Override
+    public void save(URL url) throws ConfigurationException
+    {
+        this.getConfiguration().save(url);
+    }
+
+    @Override
+    public void save(OutputStream out) throws ConfigurationException
+    {
+        this.getConfiguration().save(out);
+    }
+
+    @Override
+    public void save(OutputStream out, String encoding) throws ConfigurationException
+    {
+        this.getConfiguration().save(out, encoding);
+    }
+
+    @Override
+    public void configurationChanged(ConfigurationEvent event)
+    {
+        if (event.getSource() instanceof XMLConfiguration)
+        {
+            for (ConfigurationListener listener : getConfigurationListeners())
+            {
+                listener.configurationChanged(event);
+            }
+        }
+    }
+
+    @Override
+    public void configurationError(ConfigurationErrorEvent event)
+    {
+        if (event.getSource() instanceof XMLConfiguration)
+        {
+            for (ConfigurationErrorListener listener : getErrorListeners())
+            {
+                listener.configurationError(event);
+            }
+        }
+
+        if (event.getType() == AbstractFileConfiguration.EVENT_RELOAD)
+        {
+            if (isThrowable(event.getCause()))
+            {
+                throw new ConfigurationRuntimeException(event.getCause());
+            }
+        }
+    }
+
+    /*
+     * Don't allow resolveContainerStore to be called recursively.
+     * @param key The key to resolve.
+     * @return The value of the key.
+     */
+    @Override
+    protected Object resolveContainerStore(String key)
+    {
+        if (recursive.get().booleanValue())
+        {
+            return null;
+        }
+        recursive.set(Boolean.TRUE);
+        try
+        {
+            return super.resolveContainerStore(key);
+        }
+        finally
+        {
+            recursive.set(Boolean.FALSE);
+        }
+    }
+
+    /**
+     * Remove the current Configuration.
+     */
+    public void removeConfiguration()
+    {
+        String path = getSubstitutor().replace(pattern);
+        configurationsMap.remove(path);
+    }
+
+    /**
+     * First checks to see if the cache exists, if it does, get the associated Configuration.
+     * If not it will load a new Configuration and save it in the cache.
+     *
+     * @return the Configuration associated with the current value of the path pattern.
+     */
+    private AbstractHierarchicalFileConfiguration getConfiguration()
+    {
+        if (pattern == null)
+        {
+            throw new ConfigurationRuntimeException("File pattern must be defined");
+        }
+        String path = localSubst.replace(pattern);
+
+        if (configurationsMap.containsKey(path))
+        {
+            return configurationsMap.get(path);
+        }
+
+        if (path.equals(pattern))
+        {
+            XMLConfiguration configuration = new XMLConfiguration()
+            {
+                @Override
+                public void load() throws ConfigurationException
+                {
+                }
+                @Override
+                public void save() throws ConfigurationException
+                {
+                }
+            };
+
+            configurationsMap.putIfAbsent(pattern, configuration);
+
+            return configuration;
+        }
+
+        XMLConfiguration configuration = new XMLConfiguration();
+        if (loggerName != null)
+        {
+            Log log = LogFactory.getLog(loggerName);
+            if (log != null)
+            {
+                configuration.setLogger(log);
+            }
+        }
+        configuration.setBasePath(getBasePath());
+        configuration.setFileName(path);
+        configuration.setFileSystem(getFileSystem());
+        configuration.setExpressionEngine(getExpressionEngine());
+        ReloadingStrategy strategy = createReloadingStrategy();
+        if (strategy != null)
+        {
+            configuration.setReloadingStrategy(strategy);
+        }
+        configuration.setDelimiterParsingDisabled(isDelimiterParsingDisabled());
+        configuration.setAttributeSplittingDisabled(isAttributeSplittingDisabled());
+        configuration.setValidating(validating);
+        configuration.setSchemaValidation(schemaValidation);
+        configuration.setEntityResolver(entityResolver);
+        configuration.setListDelimiter(getListDelimiter());
+        configuration.addConfigurationListener(this);
+        configuration.addErrorListener(this);
+        try
+        {
+            configuration.load();
+        }
+        catch (ConfigurationException ce)
+        {
+            if (isThrowable(ce))
+            {
+                throw new ConfigurationRuntimeException(ce);
+            }
+        }
+        configurationsMap.putIfAbsent(path, configuration);
+        return configurationsMap.get(path);
+    }
+
+    private boolean isThrowable(Throwable throwable)
+    {
+        if (!ignoreException)
+        {
+            return true;
+        }
+        Throwable cause = throwable.getCause();
+        while (cause != null && !(cause instanceof SAXParseException))
+        {
+            cause = cause.getCause();
+        }
+        return cause != null;
+    }
+
+    /**
+     * Clone the FileReloadingStrategy since each file needs its own.
+     * @return A new FileReloadingStrategy.
+     */
+    private ReloadingStrategy createReloadingStrategy()
+    {
+        if (fileStrategy == null)
+        {
+            return null;
+        }
+        try
+        {
+            ReloadingStrategy strategy = (ReloadingStrategy) BeanUtils.cloneBean(fileStrategy);
+            strategy.setConfiguration(null);
+            return strategy;
+        }
+        catch (Exception ex)
+        {
+            return null;
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/configuration/PatternSubtreeConfigurationWrapper.java b/src/main/java/org/apache/commons/configuration/PatternSubtreeConfigurationWrapper.java
new file mode 100644
index 0000000..5f77f8e
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/PatternSubtreeConfigurationWrapper.java
@@ -0,0 +1,541 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import java.io.Reader;
+import java.io.Writer;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.commons.configuration.event.ConfigurationErrorListener;
+import org.apache.commons.configuration.event.ConfigurationListener;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.ExpressionEngine;
+
+/**
+ * Wraps a HierarchicalConfiguration and allows subtrees to be access via a configured path with
+ * replaceable tokens derived from the ConfigurationInterpolator. When used with injection frameworks
+ * such as Spring it allows components to be injected with subtrees of the configuration.
+ * @since 1.6
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: PatternSubtreeConfigurationWrapper.java 1534064 2013-10-21 08:44:33Z henning $
+ */
+public class PatternSubtreeConfigurationWrapper extends AbstractHierarchicalFileConfiguration
+{
+    /**
+     * Prevent recursion while resolving unprefixed properties.
+     */
+    private static ThreadLocal<Boolean> recursive = new ThreadLocal<Boolean>()
+    {
+        @Override
+        protected synchronized Boolean initialValue()
+        {
+            return Boolean.FALSE;
+        }
+    };
+
+    /** The wrapped configuration */
+    private final AbstractHierarchicalFileConfiguration config;
+
+    /** The path to the subtree */
+    private final String path;
+
+    /** True if the path ends with '/', false otherwise */
+    private final boolean trailing;
+
+    /** True if the constructor has finished */
+    private boolean init;
+
+    /**
+     * Constructor
+     * @param config The Configuration to be wrapped.
+     * @param path The base path pattern.
+     */
+    public PatternSubtreeConfigurationWrapper(AbstractHierarchicalFileConfiguration config, String path)
+    {
+        this.config = config;
+        this.path = path;
+        this.trailing = path.endsWith("/");
+        this.init = true;
+    }
+
+    @Override
+    public Object getReloadLock()
+    {
+        return config.getReloadLock();
+    }
+
+    @Override
+    public void addProperty(String key, Object value)
+    {
+        config.addProperty(makePath(key), value);
+    }
+
+    @Override
+    public void clear()
+    {
+        getConfig().clear();
+    }
+
+    @Override
+    public void clearProperty(String key)
+    {
+        config.clearProperty(makePath(key));
+    }
+
+    @Override
+    public boolean containsKey(String key)
+    {
+        return config.containsKey(makePath(key));
+    }
+
+    @Override
+    public BigDecimal getBigDecimal(String key, BigDecimal defaultValue)
+    {
+        return config.getBigDecimal(makePath(key), defaultValue);
+    }
+
+    @Override
+    public BigDecimal getBigDecimal(String key)
+    {
+        return config.getBigDecimal(makePath(key));
+    }
+
+    @Override
+    public BigInteger getBigInteger(String key, BigInteger defaultValue)
+    {
+        return config.getBigInteger(makePath(key), defaultValue);
+    }
+
+    @Override
+    public BigInteger getBigInteger(String key)
+    {
+        return config.getBigInteger(makePath(key));
+    }
+
+    @Override
+    public boolean getBoolean(String key, boolean defaultValue)
+    {
+        return config.getBoolean(makePath(key), defaultValue);
+    }
+
+    @Override
+    public Boolean getBoolean(String key, Boolean defaultValue)
+    {
+        return config.getBoolean(makePath(key), defaultValue);
+    }
+
+    @Override
+    public boolean getBoolean(String key)
+    {
+        return config.getBoolean(makePath(key));
+    }
+
+    @Override
+    public byte getByte(String key, byte defaultValue)
+    {
+        return config.getByte(makePath(key), defaultValue);
+    }
+
+    @Override
+    public Byte getByte(String key, Byte defaultValue)
+    {
+        return config.getByte(makePath(key), defaultValue);
+    }
+
+    @Override
+    public byte getByte(String key)
+    {
+        return config.getByte(makePath(key));
+    }
+
+    @Override
+    public double getDouble(String key, double defaultValue)
+    {
+        return config.getDouble(makePath(key), defaultValue);
+    }
+
+    @Override
+    public Double getDouble(String key, Double defaultValue)
+    {
+        return config.getDouble(makePath(key), defaultValue);
+    }
+
+    @Override
+    public double getDouble(String key)
+    {
+        return config.getDouble(makePath(key));
+    }
+
+    @Override
+    public float getFloat(String key, float defaultValue)
+    {
+        return config.getFloat(makePath(key), defaultValue);
+    }
+
+    @Override
+    public Float getFloat(String key, Float defaultValue)
+    {
+        return config.getFloat(makePath(key), defaultValue);
+    }
+
+    @Override
+    public float getFloat(String key)
+    {
+        return config.getFloat(makePath(key));
+    }
+
+    @Override
+    public int getInt(String key, int defaultValue)
+    {
+        return config.getInt(makePath(key), defaultValue);
+    }
+
+    @Override
+    public int getInt(String key)
+    {
+        return config.getInt(makePath(key));
+    }
+
+    @Override
+    public Integer getInteger(String key, Integer defaultValue)
+    {
+        return config.getInteger(makePath(key), defaultValue);
+    }
+
+    @Override
+    public Iterator<String> getKeys()
+    {
+        return config.getKeys(makePath());
+    }
+
+    @Override
+    public Iterator<String> getKeys(String prefix)
+    {
+        return config.getKeys(makePath(prefix));
+    }
+
+    @Override
+    public List<Object> getList(String key, List<?> defaultValue)
+    {
+        return config.getList(makePath(key), defaultValue);
+    }
+
+    @Override
+    public List<Object> getList(String key)
+    {
+        return config.getList(makePath(key));
+    }
+
+    @Override
+    public long getLong(String key, long defaultValue)
+    {
+        return config.getLong(makePath(key), defaultValue);
+    }
+
+    @Override
+    public Long getLong(String key, Long defaultValue)
+    {
+        return config.getLong(makePath(key), defaultValue);
+    }
+
+    @Override
+    public long getLong(String key)
+    {
+        return config.getLong(makePath(key));
+    }
+
+    @Override
+    public Properties getProperties(String key)
+    {
+        return config.getProperties(makePath(key));
+    }
+
+    @Override
+    public Object getProperty(String key)
+    {
+        return config.getProperty(makePath(key));
+    }
+
+    @Override
+    public short getShort(String key, short defaultValue)
+    {
+        return config.getShort(makePath(key), defaultValue);
+    }
+
+    @Override
+    public Short getShort(String key, Short defaultValue)
+    {
+        return config.getShort(makePath(key), defaultValue);
+    }
+
+    @Override
+    public short getShort(String key)
+    {
+        return config.getShort(makePath(key));
+    }
+
+    @Override
+    public String getString(String key, String defaultValue)
+    {
+        return config.getString(makePath(key), defaultValue);
+    }
+
+    @Override
+    public String getString(String key)
+    {
+        return config.getString(makePath(key));
+    }
+
+    @Override
+    public String[] getStringArray(String key)
+    {
+        return config.getStringArray(makePath(key));
+    }
+
+    @Override
+    public boolean isEmpty()
+    {
+        return getConfig().isEmpty();
+    }
+
+    @Override
+    public void setProperty(String key, Object value)
+    {
+        getConfig().setProperty(key, value);
+    }
+
+    @Override
+    public Configuration subset(String prefix)
+    {
+        return getConfig().subset(prefix);
+    }
+
+    @Override
+    public Node getRoot()
+    {
+        return getConfig().getRoot();
+    }
+
+    @Override
+    public void setRoot(Node node)
+    {
+        if (init)
+        {
+            getConfig().setRoot(node);
+        }
+        else
+        {
+            super.setRoot(node);
+        }
+    }
+
+    @Override
+    public ConfigurationNode getRootNode()
+    {
+        return getConfig().getRootNode();
+    }
+
+    @Override
+    public void setRootNode(ConfigurationNode rootNode)
+    {
+        if (init)
+        {
+            getConfig().setRootNode(rootNode);
+        }
+        else
+        {
+            super.setRootNode(rootNode);
+        }
+    }
+
+    @Override
+    public ExpressionEngine getExpressionEngine()
+    {
+        return config.getExpressionEngine();
+    }
+
+    @Override
+    public void setExpressionEngine(ExpressionEngine expressionEngine)
+    {
+        if (init)
+        {
+            config.setExpressionEngine(expressionEngine);
+        }
+        else
+        {
+            super.setExpressionEngine(expressionEngine);
+        }
+    }
+
+    @Override
+    public void addNodes(String key, Collection<? extends ConfigurationNode> nodes)
+    {
+        getConfig().addNodes(key, nodes);
+    }
+
+    @Override
+    public SubnodeConfiguration configurationAt(String key, boolean supportUpdates)
+    {
+        return config.configurationAt(makePath(key), supportUpdates);
+    }
+
+    @Override
+    public SubnodeConfiguration configurationAt(String key)
+    {
+        return config.configurationAt(makePath(key));
+    }
+
+    @Override
+    public List<HierarchicalConfiguration> configurationsAt(String key)
+    {
+        return config.configurationsAt(makePath(key));
+    }
+
+    @Override
+    public void clearTree(String key)
+    {
+        config.clearTree(makePath(key));
+    }
+
+    @Override
+    public int getMaxIndex(String key)
+    {
+        return config.getMaxIndex(makePath(key));
+    }
+
+    @Override
+    public Configuration interpolatedConfiguration()
+    {
+        return getConfig().interpolatedConfiguration();
+    }
+
+    @Override
+    public void addConfigurationListener(ConfigurationListener l)
+    {
+        getConfig().addConfigurationListener(l);
+    }
+
+    @Override
+    public boolean removeConfigurationListener(ConfigurationListener l)
+    {
+        return getConfig().removeConfigurationListener(l);
+    }
+
+    @Override
+    public Collection<ConfigurationListener> getConfigurationListeners()
+    {
+        return getConfig().getConfigurationListeners();
+    }
+
+    @Override
+    public void clearConfigurationListeners()
+    {
+        getConfig().clearConfigurationListeners();
+    }
+
+    @Override
+    public void addErrorListener(ConfigurationErrorListener l)
+    {
+        getConfig().addErrorListener(l);
+    }
+
+    @Override
+    public boolean removeErrorListener(ConfigurationErrorListener l)
+    {
+        return getConfig().removeErrorListener(l);
+    }
+
+    @Override
+    public void clearErrorListeners()
+    {
+        getConfig().clearErrorListeners();
+    }
+
+    public void save(Writer writer) throws ConfigurationException
+    {
+        config.save(writer);
+    }
+
+    public void load(Reader reader) throws ConfigurationException
+    {
+        config.load(reader);
+    }
+
+    @Override
+    public Collection<ConfigurationErrorListener> getErrorListeners()
+    {
+        return getConfig().getErrorListeners();
+    }
+
+    @Override
+    protected Object resolveContainerStore(String key)
+    {
+        if (recursive.get().booleanValue())
+        {
+            return null;
+        }
+        recursive.set(Boolean.TRUE);
+        try
+        {
+            return super.resolveContainerStore(key);
+        }
+        finally
+        {
+            recursive.set(Boolean.FALSE);
+        }
+    }
+
+    private HierarchicalConfiguration getConfig()
+    {
+        return config.configurationAt(makePath());
+    }
+
+    private String makePath()
+    {
+        String pathPattern = trailing ? path.substring(0, path.length() - 1) : path;
+        return getSubstitutor().replace(pathPattern);
+    }
+
+    /*
+     * Resolve the root expression and then add the item being retrieved. Insert a
+     * separator character as required.
+     */
+    private String makePath(String item)
+    {
+        String pathPattern;
+        if ((item.length() == 0 || item.startsWith("/")) && trailing)
+        {
+            pathPattern = path.substring(0, path.length() - 1);
+        }
+        else  if (!item.startsWith("/") || !trailing)
+        {
+            pathPattern = path + "/";
+        }
+        else
+        {
+            pathPattern = path;
+        }
+        return getSubstitutor().replace(pathPattern) + item;
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/PrefixedKeysIterator.java b/src/main/java/org/apache/commons/configuration/PrefixedKeysIterator.java
new file mode 100644
index 0000000..5d63949
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/PrefixedKeysIterator.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * * A specialized iterator implementation used by {@link AbstractConfiguration}
+ * to return an iteration over all keys starting with a specified prefix.
+ *
+ * <p>This class is basically a stripped-down version of the
+ * {@code FilterIterator} class of Commons Collections</p>
+ *
+ * @author <a href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a>
+ * @version $Id: PrefixedKeysIterator.java 1205603 2011-11-23 21:17:38Z oheger $
+ */
+class PrefixedKeysIterator implements Iterator<String>
+{
+    /** Stores the wrapped iterator. */
+    private final Iterator<String> iterator;
+
+    /** Stores the prefix. */
+    private final String prefix;
+
+    /** Stores the next element in the iteration. */
+    private String nextElement;
+
+    /** A flag whether the next element has been calculated. */
+    private boolean nextElementSet;
+
+    /**
+     * Creates a new instance of {@code PrefixedKeysIterator} and sets
+     * the wrapped iterator and the prefix for the accepted keys.
+     *
+     * @param wrappedIterator the wrapped iterator
+     * @param keyPrefix the prefix of the allowed keys
+     */
+    public PrefixedKeysIterator(Iterator<String> wrappedIterator, String keyPrefix)
+    {
+        iterator = wrappedIterator;
+        prefix = keyPrefix;
+    }
+
+    /**
+     * Returns a flag whether there are more elements in the iteration.
+     *
+     * @return a flag if there is a next element
+     */
+    public boolean hasNext()
+    {
+        return nextElementSet || setNextElement();
+    }
+
+    /**
+     * Returns the next element in the iteration. This is the next key that
+     * matches the specified prefix.
+     *
+     * @return the next element in the iteration
+     * @throws NoSuchElementException if there is no next element
+     */
+    public String next()
+    {
+        if (!nextElementSet && !setNextElement())
+        {
+            throw new NoSuchElementException();
+        }
+        nextElementSet = false;
+        return nextElement;
+    }
+
+    /**
+     * Removes from the underlying collection of the base iterator the last
+     * element returned by this iterator. This method can only be called if
+     * {@code next()} was called, but not after {@code hasNext()},
+     * because the {@code hasNext()} call changes the base iterator.
+     *
+     * @throws IllegalStateException if {@code hasNext()} has already
+     *         been called.
+     */
+    public void remove()
+    {
+        if (nextElementSet)
+        {
+            throw new IllegalStateException("remove() cannot be called");
+        }
+        iterator.remove();
+    }
+
+    /**
+     * Determines the next element in the iteration. The return value indicates
+     * whether such an element can be found.
+     *
+     * @return a flag whether a next element exists
+     */
+    private boolean setNextElement()
+    {
+        while (iterator.hasNext())
+        {
+            String key = iterator.next();
+            if (key.startsWith(prefix + ".") || key.equals(prefix))
+            {
+                nextElement = key;
+                nextElementSet = true;
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/PropertiesConfiguration.java b/src/main/java/org/apache/commons/configuration/PropertiesConfiguration.java
new file mode 100644
index 0000000..13cacfb
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/PropertiesConfiguration.java
@@ -0,0 +1,1516 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.io.File;
+import java.io.FilterWriter;
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.commons.lang.StringEscapeUtils;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * This is the "classic" Properties loader which loads the values from
+ * a single or multiple files (which can be chained with "include =".
+ * All given path references are either absolute or relative to the
+ * file name supplied in the constructor.
+ * <p>
+ * In this class, empty PropertyConfigurations can be built, properties
+ * added and later saved. include statements are (obviously) not supported
+ * if you don't construct a PropertyConfiguration from a file.
+ *
+ * <p>The properties file syntax is explained here, basically it follows
+ * the syntax of the stream parsed by {@link java.util.Properties#load} and
+ * adds several useful extensions:
+ *
+ * <ul>
+ *  <li>
+ *   Each property has the syntax <code>key <separator> value</code>. The
+ *   separators accepted are {@code '='}, {@code ':'} and any white
+ *   space character. Examples:
+ * <pre>
+ *  key1 = value1
+ *  key2 : value2
+ *  key3   value3</pre>
+ *  </li>
+ *  <li>
+ *   The <i>key</i> may use any character, separators must be escaped:
+ * <pre>
+ *  key\:foo = bar</pre>
+ *  </li>
+ *  <li>
+ *   <i>value</i> may be separated on different lines if a backslash
+ *   is placed at the end of the line that continues below.
+ *  </li>
+ *  <li>
+ *   <i>value</i> can contain <em>value delimiters</em> and will then be interpreted
+ *   as a list of tokens. Default value delimiter is the comma ','. So the
+ *   following property definition
+ * <pre>
+ *  key = This property, has multiple, values
+ * </pre>
+ *   will result in a property with three values. You can change the value
+ *   delimiter using the {@link AbstractConfiguration#setListDelimiter(char)}
+ *   method. Setting the delimiter to 0 will disable value splitting completely.
+ *  </li>
+ *  <li>
+ *   Commas in each token are escaped placing a backslash right before
+ *   the comma.
+ *  </li>
+ *  <li>
+ *   If a <i>key</i> is used more than once, the values are appended
+ *   like if they were on the same line separated with commas. <em>Note</em>:
+ *   When the configuration file is written back to disk the associated
+ *   {@link PropertiesConfigurationLayout} object (see below) will
+ *   try to preserve as much of the original format as possible, i.e. properties
+ *   with multiple values defined on a single line will also be written back on
+ *   a single line, and multiple occurrences of a single key will be written on
+ *   multiple lines. If the {@code addProperty()} method was called
+ *   multiple times for adding multiple values to a property, these properties
+ *   will per default be written on multiple lines in the output file, too.
+ *   Some options of the {@code PropertiesConfigurationLayout} class have
+ *   influence on that behavior.
+ *  </li>
+ *  <li>
+ *   Blank lines and lines starting with character '#' or '!' are skipped.
+ *  </li>
+ *  <li>
+ *   If a property is named "include" (or whatever is defined by
+ *   setInclude() and getInclude() and the value of that property is
+ *   the full path to a file on disk, that file will be included into
+ *   the configuration. You can also pull in files relative to the parent
+ *   configuration file. So if you have something like the following:
+ *
+ *   include = additional.properties
+ *
+ *   Then "additional.properties" is expected to be in the same
+ *   directory as the parent configuration file.
+ *
+ *   The properties in the included file are added to the parent configuration,
+ *   they do not replace existing properties with the same key.
+ *
+ *  </li>
+ * </ul>
+ *
+ * <p>Here is an example of a valid extended properties file:
+ *
+ * <p><pre>
+ *      # lines starting with # are comments
+ *
+ *      # This is the simplest property
+ *      key = value
+ *
+ *      # A long property may be separated on multiple lines
+ *      longvalue = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
+ *                  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ *
+ *      # This is a property with many tokens
+ *      tokens_on_a_line = first token, second token
+ *
+ *      # This sequence generates exactly the same result
+ *      tokens_on_multiple_lines = first token
+ *      tokens_on_multiple_lines = second token
+ *
+ *      # commas may be escaped in tokens
+ *      commas.escaped = Hi\, what'up?
+ *
+ *      # properties can reference other properties
+ *      base.prop = /base
+ *      first.prop = ${base.prop}/first
+ *      second.prop = ${first.prop}/second
+ * </pre>
+ *
+ * <p>A {@code PropertiesConfiguration} object is associated with an
+ * instance of the {@link PropertiesConfigurationLayout} class,
+ * which is responsible for storing the layout of the parsed properties file
+ * (i.e. empty lines, comments, and such things). The {@code getLayout()}
+ * method can be used to obtain this layout object. With {@code setLayout()}
+ * a new layout object can be set. This should be done before a properties file
+ * was loaded.
+ * <p><em>Note:</em>Configuration objects of this type can be read concurrently
+ * by multiple threads. However if one of these threads modifies the object,
+ * synchronization has to be performed manually.
+ *
+ * @see java.util.Properties#load
+ *
+ * @author <a href="mailto:stefano at apache.org">Stefano Mazzocchi</a>
+ * @author <a href="mailto:jon at latchkey.com">Jon S. Stevens</a>
+ * @author <a href="mailto:daveb at miceda-data">Dave Bryson</a>
+ * @author <a href="mailto:geirm at optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:leon at opticode.co.za">Leon Messerschmidt</a>
+ * @author <a href="mailto:kjohnson at transparent.com">Kent Johnson</a>
+ * @author <a href="mailto:dlr at finemaltcoding.com">Daniel Rall</a>
+ * @author <a href="mailto:ipriha at surfeu.fi">Ilkka Priha</a>
+ * @author <a href="mailto:jvanzyl at apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:mpoeschl at marmot.at">Martin Poeschl</a>
+ * @author <a href="mailto:hps at intermeta.de">Henning P. Schmiedehausen</a>
+ * @author <a href="mailto:epugh at upstate.com">Eric Pugh</a>
+ * @author <a href="mailto:ebourg at apache.org">Emmanuel Bourg</a>
+ * @version $Id: PropertiesConfiguration.java 1534445 2013-10-22 01:19:43Z henning $
+ */
+public class PropertiesConfiguration extends AbstractFileConfiguration
+{
+    /** Constant for the supported comment characters.*/
+    static final String COMMENT_CHARS = "#!";
+
+    /** Constant for the default properties separator.*/
+    static final String DEFAULT_SEPARATOR = " = ";
+
+    /**
+     * Constant for the default {@code IOFactory}. This instance is used
+     * when no specific factory was set.
+     */
+    private static final IOFactory DEFAULT_IO_FACTORY = new DefaultIOFactory();
+
+    /**
+     * This is the name of the property that can point to other
+     * properties file for including other properties files.
+     */
+    private static String include = "include";
+
+    /** The list of possible key/value separators */
+    private static final char[] SEPARATORS = new char[] {'=', ':'};
+
+    /** The white space characters used as key/value separators. */
+    private static final char[] WHITE_SPACE = new char[]{' ', '\t', '\f'};
+
+    /**
+     * The default encoding (ISO-8859-1 as specified by
+     * http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html)
+     */
+    private static final String DEFAULT_ENCODING = "ISO-8859-1";
+
+    /** Constant for the platform specific line separator.*/
+    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
+    /** Constant for the escaping character.*/
+    private static final String ESCAPE = "\\";
+
+    /** Constant for the escaped escaping character.*/
+    private static final String DOUBLE_ESC = ESCAPE + ESCAPE;
+
+    /** Constant for the radix of hex numbers.*/
+    private static final int HEX_RADIX = 16;
+
+    /** Constant for the length of a unicode literal.*/
+    private static final int UNICODE_LEN = 4;
+
+    /** Stores the layout object.*/
+    private PropertiesConfigurationLayout layout;
+
+    /** The IOFactory for creating readers and writers.*/
+    private volatile IOFactory ioFactory;
+
+    /** Allow file inclusion or not */
+    private boolean includesAllowed = true;
+
+    /**
+     * Creates an empty PropertyConfiguration object which can be
+     * used to synthesize a new Properties file by adding values and
+     * then saving().
+     */
+    public PropertiesConfiguration()
+    {
+        layout = createLayout();
+    }
+
+    /**
+     * Creates and loads the extended properties from the specified file.
+     * The specified file can contain "include = " properties which then
+     * are loaded and merged into the properties.
+     *
+     * @param fileName The name of the properties file to load.
+     * @throws ConfigurationException Error while loading the properties file
+     */
+    public PropertiesConfiguration(String fileName) throws ConfigurationException
+    {
+        super(fileName);
+    }
+
+    /**
+     * Creates and loads the extended properties from the specified file.
+     * The specified file can contain "include = " properties which then
+     * are loaded and merged into the properties. If the file does not exist,
+     * an empty configuration will be created. Later the {@code save()}
+     * method can be called to save the properties to the specified file.
+     *
+     * @param file The properties file to load.
+     * @throws ConfigurationException Error while loading the properties file
+     */
+    public PropertiesConfiguration(File file) throws ConfigurationException
+    {
+        super(file);
+
+        // If the file does not exist, no layout object was created. We have to
+        // do this manually in this case.
+        getLayout();
+    }
+
+    /**
+     * Creates and loads the extended properties from the specified URL.
+     * The specified file can contain "include = " properties which then
+     * are loaded and merged into the properties.
+     *
+     * @param url The location of the properties file to load.
+     * @throws ConfigurationException Error while loading the properties file
+     */
+    public PropertiesConfiguration(URL url) throws ConfigurationException
+    {
+        super(url);
+    }
+
+    /**
+     * Gets the property value for including other properties files.
+     * By default it is "include".
+     *
+     * @return A String.
+     */
+    public static String getInclude()
+    {
+        return PropertiesConfiguration.include;
+    }
+
+    /**
+     * Sets the property value for including other properties files.
+     * By default it is "include".
+     *
+     * @param inc A String.
+     */
+    public static void setInclude(String inc)
+    {
+        PropertiesConfiguration.include = inc;
+    }
+
+    /**
+     * Controls whether additional files can be loaded by the include = <xxx>
+     * statement or not. This is <b>true</b> per default.
+     *
+     * @param includesAllowed True if Includes are allowed.
+     */
+    public void setIncludesAllowed(boolean includesAllowed)
+    {
+        this.includesAllowed = includesAllowed;
+    }
+
+    /**
+     * Reports the status of file inclusion.
+     *
+     * @return True if include files are loaded.
+     *
+     * @see PropertiesConfiguration#isIncludesAllowed()
+     *
+     * @deprecated Only exists to not break backwards compatibility.
+     * Use {@link PropertiesConfiguration#isIncludesAllowed()} instead.
+     */
+    @Deprecated
+    public boolean getIncludesAllowed()
+    {
+        return isIncludesAllowed();
+    }
+
+    /**
+     * Reports the status of file inclusion.
+     *
+     * @return True if include files are loaded.
+     */
+    public boolean isIncludesAllowed()
+    {
+        return this.includesAllowed;
+    }
+
+    /**
+     * Return the comment header.
+     *
+     * @return the comment header
+     * @since 1.1
+     */
+    public String getHeader()
+    {
+        return getLayout().getHeaderComment();
+    }
+
+    /**
+     * Set the comment header.
+     *
+     * @param header the header to use
+     * @since 1.1
+     */
+    public void setHeader(String header)
+    {
+        getLayout().setHeaderComment(header);
+    }
+
+    /**
+     * Returns the footer comment. This is a comment at the very end of the
+     * file.
+     *
+     * @return the footer comment
+     * @since 1.10
+     */
+    public String getFooter()
+    {
+        return getLayout().getFooterComment();
+    }
+
+    /**
+     * Sets the footer comment. If set, this comment is written after all
+     * properties at the end of the file.
+     *
+     * @param footer the footer comment
+     * @since 1.10
+     */
+    public void setFooter(String footer)
+    {
+        getLayout().setFooterComment(footer);
+    }
+
+    /**
+     * Returns the encoding to be used when loading or storing configuration
+     * data. This implementation ensures that the default encoding will be used
+     * if none has been set explicitly.
+     *
+     * @return the encoding
+     */
+    @Override
+    public String getEncoding()
+    {
+        String enc = super.getEncoding();
+        return (enc != null) ? enc : DEFAULT_ENCODING;
+    }
+
+    /**
+     * Returns the associated layout object.
+     *
+     * @return the associated layout object
+     * @since 1.3
+     */
+    public synchronized PropertiesConfigurationLayout getLayout()
+    {
+        if (layout == null)
+        {
+            layout = createLayout();
+        }
+        return layout;
+    }
+
+    /**
+     * Sets the associated layout object.
+     *
+     * @param layout the new layout object; can be <b>null</b>, then a new
+     * layout object will be created
+     * @since 1.3
+     */
+    public synchronized void setLayout(PropertiesConfigurationLayout layout)
+    {
+        // only one layout must exist
+        if (this.layout != null)
+        {
+            removeConfigurationListener(this.layout);
+        }
+
+        if (layout == null)
+        {
+            this.layout = createLayout();
+        }
+        else
+        {
+            this.layout = layout;
+        }
+    }
+
+    /**
+     * Creates the associated layout object. This method is invoked when the
+     * layout object is accessed and has not been created yet. Derived classes
+     * can override this method to hook in a different layout implementation.
+     *
+     * @return the layout object to use
+     * @since 1.3
+     */
+    protected PropertiesConfigurationLayout createLayout()
+    {
+        return new PropertiesConfigurationLayout(this);
+    }
+
+    /**
+     * Returns the {@code IOFactory} to be used for creating readers and
+     * writers when loading or saving this configuration.
+     *
+     * @return the {@code IOFactory}
+     * @since 1.7
+     */
+    public IOFactory getIOFactory()
+    {
+        return (ioFactory != null) ? ioFactory : DEFAULT_IO_FACTORY;
+    }
+
+    /**
+     * Sets the {@code IOFactory} to be used for creating readers and
+     * writers when loading or saving this configuration. Using this method a
+     * client can customize the reader and writer classes used by the load and
+     * save operations. Note that this method must be called before invoking
+     * one of the {@code load()} and {@code save()} methods.
+     * Especially, if you want to use a custom {@code IOFactory} for
+     * changing the {@code PropertiesReader}, you cannot load the
+     * configuration data in the constructor.
+     *
+     * @param ioFactory the new {@code IOFactory} (must not be <b>null</b>)
+     * @throws IllegalArgumentException if the {@code IOFactory} is
+     *         <b>null</b>
+     * @since 1.7
+     */
+    public void setIOFactory(IOFactory ioFactory)
+    {
+        if (ioFactory == null)
+        {
+            throw new IllegalArgumentException("IOFactory must not be null!");
+        }
+
+        this.ioFactory = ioFactory;
+    }
+
+    /**
+     * Load the properties from the given reader.
+     * Note that the {@code clear()} method is not called, so
+     * the properties contained in the loaded file will be added to the
+     * actual set of properties.
+     *
+     * @param in An InputStream.
+     *
+     * @throws ConfigurationException if an error occurs
+     */
+    public synchronized void load(Reader in) throws ConfigurationException
+    {
+        boolean oldAutoSave = isAutoSave();
+        setAutoSave(false);
+
+        try
+        {
+            getLayout().load(in);
+        }
+        finally
+        {
+            setAutoSave(oldAutoSave);
+        }
+    }
+
+    /**
+     * Save the configuration to the specified stream.
+     *
+     * @param writer the output stream used to save the configuration
+     * @throws ConfigurationException if an error occurs
+     */
+    public void save(Writer writer) throws ConfigurationException
+    {
+        enterNoReload();
+        try
+        {
+            getLayout().save(writer);
+        }
+        finally
+        {
+            exitNoReload();
+        }
+    }
+
+    /**
+     * Extend the setBasePath method to turn includes
+     * on and off based on the existence of a base path.
+     *
+     * @param basePath The new basePath to set.
+     */
+    @Override
+    public void setBasePath(String basePath)
+    {
+        super.setBasePath(basePath);
+        setIncludesAllowed(StringUtils.isNotEmpty(basePath));
+    }
+
+    /**
+     * Creates a copy of this object.
+     *
+     * @return the copy
+     */
+    @Override
+    public Object clone()
+    {
+        PropertiesConfiguration copy = (PropertiesConfiguration) super.clone();
+        if (layout != null)
+        {
+            copy.setLayout(new PropertiesConfigurationLayout(copy, layout));
+        }
+        return copy;
+    }
+
+    /**
+     * This method is invoked by the associated
+     * {@link PropertiesConfigurationLayout} object for each
+     * property definition detected in the parsed properties file. Its task is
+     * to check whether this is a special property definition (e.g. the
+     * {@code include} property). If not, the property must be added to
+     * this configuration. The return value indicates whether the property
+     * should be treated as a normal property. If it is <b>false</b>, the
+     * layout object will ignore this property.
+     *
+     * @param key the property key
+     * @param value the property value
+     * @return a flag whether this is a normal property
+     * @throws ConfigurationException if an error occurs
+     * @since 1.3
+     */
+    boolean propertyLoaded(String key, String value)
+            throws ConfigurationException
+    {
+        boolean result;
+
+        if (StringUtils.isNotEmpty(getInclude())
+                && key.equalsIgnoreCase(getInclude()))
+        {
+            if (isIncludesAllowed())
+            {
+                String[] files;
+                if (!isDelimiterParsingDisabled())
+                {
+                    files = StringUtils.split(value, getListDelimiter());
+                }
+                else
+                {
+                    files = new String[]{value};
+                }
+                for (String f : files)
+                {
+                    loadIncludeFile(interpolate(f.trim()));
+                }
+            }
+            result = false;
+        }
+
+        else
+        {
+            addProperty(key, value);
+            result = true;
+        }
+
+        return result;
+    }
+
+    /**
+     * Tests whether a line is a comment, i.e. whether it starts with a comment
+     * character.
+     *
+     * @param line the line
+     * @return a flag if this is a comment line
+     * @since 1.3
+     */
+    static boolean isCommentLine(String line)
+    {
+        String s = line.trim();
+        // blanc lines are also treated as comment lines
+        return s.length() < 1 || COMMENT_CHARS.indexOf(s.charAt(0)) >= 0;
+    }
+
+    /**
+     * Returns the number of trailing backslashes. This is sometimes needed for
+     * the correct handling of escape characters.
+     *
+     * @param line the string to investigate
+     * @return the number of trailing backslashes
+     */
+    private static int countTrailingBS(String line)
+    {
+        int bsCount = 0;
+        for (int idx = line.length() - 1; idx >= 0 && line.charAt(idx) == '\\'; idx--)
+        {
+            bsCount++;
+        }
+
+        return bsCount;
+    }
+
+    /**
+     * This class is used to read properties lines. These lines do
+     * not terminate with new-line chars but rather when there is no
+     * backslash sign a the end of the line.  This is used to
+     * concatenate multiple lines for readability.
+     */
+    public static class PropertiesReader extends LineNumberReader
+    {
+        /** The regular expression to parse the key and the value of a property. */
+        private static final Pattern PROPERTY_PATTERN = Pattern
+                .compile("(([\\S&&[^\\\\" + new String(SEPARATORS)
+                        + "]]|\\\\.)*)(\\s*(\\s+|[" + new String(SEPARATORS)
+                        + "])\\s*)(.*)");
+
+        /** Constant for the index of the group for the key. */
+        private static final int IDX_KEY = 1;
+
+        /** Constant for the index of the group for the value. */
+        private static final int IDX_VALUE = 5;
+
+        /** Constant for the index of the group for the separator. */
+        private static final int IDX_SEPARATOR = 3;
+
+        /** Stores the comment lines for the currently processed property.*/
+        private List<String> commentLines;
+
+        /** Stores the name of the last read property.*/
+        private String propertyName;
+
+        /** Stores the value of the last read property.*/
+        private String propertyValue;
+
+        /** Stores the property separator of the last read property.*/
+        private String propertySeparator = DEFAULT_SEPARATOR;
+
+        /** Stores the list delimiter character.*/
+        private char delimiter;
+
+        /**
+         * Constructor.
+         *
+         * @param reader A Reader.
+         */
+        public PropertiesReader(Reader reader)
+        {
+            this(reader, AbstractConfiguration.getDefaultListDelimiter());
+        }
+
+        /**
+         * Creates a new instance of {@code PropertiesReader} and sets
+         * the underlying reader and the list delimiter.
+         *
+         * @param reader the reader
+         * @param listDelimiter the list delimiter character
+         * @since 1.3
+         */
+        public PropertiesReader(Reader reader, char listDelimiter)
+        {
+            super(reader);
+            commentLines = new ArrayList<String>();
+            delimiter = listDelimiter;
+        }
+
+        /**
+         * Reads a property line. Returns null if Stream is
+         * at EOF. Concatenates lines ending with "\".
+         * Skips lines beginning with "#" or "!" and empty lines.
+         * The return value is a property definition (<code><name></code>
+         * = <code><value></code>)
+         *
+         * @return A string containing a property value or null
+         *
+         * @throws IOException in case of an I/O error
+         */
+        public String readProperty() throws IOException
+        {
+            commentLines.clear();
+            StringBuilder buffer = new StringBuilder();
+
+            while (true)
+            {
+                String line = readLine();
+                if (line == null)
+                {
+                    // EOF
+                    return null;
+                }
+
+                if (isCommentLine(line))
+                {
+                    commentLines.add(line);
+                    continue;
+                }
+
+                line = line.trim();
+
+                if (checkCombineLines(line))
+                {
+                    line = line.substring(0, line.length() - 1);
+                    buffer.append(line);
+                }
+                else
+                {
+                    buffer.append(line);
+                    break;
+                }
+            }
+            return buffer.toString();
+        }
+
+        /**
+         * Parses the next property from the input stream and stores the found
+         * name and value in internal fields. These fields can be obtained using
+         * the provided getter methods. The return value indicates whether EOF
+         * was reached (<b>false</b>) or whether further properties are
+         * available (<b>true</b>).
+         *
+         * @return a flag if further properties are available
+         * @throws IOException if an error occurs
+         * @since 1.3
+         */
+        public boolean nextProperty() throws IOException
+        {
+            String line = readProperty();
+
+            if (line == null)
+            {
+                return false; // EOF
+            }
+
+            // parse the line
+            parseProperty(line);
+            return true;
+        }
+
+        /**
+         * Returns the comment lines that have been read for the last property.
+         *
+         * @return the comment lines for the last property returned by
+         * {@code readProperty()}
+         * @since 1.3
+         */
+        public List<String> getCommentLines()
+        {
+            return commentLines;
+        }
+
+        /**
+         * Returns the name of the last read property. This method can be called
+         * after {@link #nextProperty()} was invoked and its
+         * return value was <b>true</b>.
+         *
+         * @return the name of the last read property
+         * @since 1.3
+         */
+        public String getPropertyName()
+        {
+            return propertyName;
+        }
+
+        /**
+         * Returns the value of the last read property. This method can be
+         * called after {@link #nextProperty()} was invoked and
+         * its return value was <b>true</b>.
+         *
+         * @return the value of the last read property
+         * @since 1.3
+         */
+        public String getPropertyValue()
+        {
+            return propertyValue;
+        }
+
+        /**
+         * Returns the separator that was used for the last read property. The
+         * separator can be stored so that it can later be restored when saving
+         * the configuration.
+         *
+         * @return the separator for the last read property
+         * @since 1.7
+         */
+        public String getPropertySeparator()
+        {
+            return propertySeparator;
+        }
+
+        /**
+         * Parses a line read from the properties file. This method is called
+         * for each non-comment line read from the source file. Its task is to
+         * split the passed in line into the property key and its value. The
+         * results of the parse operation can be stored by calling the
+         * {@code initPropertyXXX()} methods.
+         *
+         * @param line the line read from the properties file
+         * @since 1.7
+         */
+        protected void parseProperty(String line)
+        {
+            String[] property = doParseProperty(line);
+            initPropertyName(property[0]);
+            initPropertyValue(property[1]);
+            initPropertySeparator(property[2]);
+        }
+
+        /**
+         * Sets the name of the current property. This method can be called by
+         * {@code parseProperty()} for storing the results of the parse
+         * operation. It also ensures that the property key is correctly
+         * escaped.
+         *
+         * @param name the name of the current property
+         * @since 1.7
+         */
+        protected void initPropertyName(String name)
+        {
+            propertyName = StringEscapeUtils.unescapeJava(name);
+        }
+
+        /**
+         * Sets the value of the current property. This method can be called by
+         * {@code parseProperty()} for storing the results of the parse
+         * operation. It also ensures that the property value is correctly
+         * escaped.
+         *
+         * @param value the value of the current property
+         * @since 1.7
+         */
+        protected void initPropertyValue(String value)
+        {
+            propertyValue = unescapeJava(value, delimiter);
+        }
+
+        /**
+         * Sets the separator of the current property. This method can be called
+         * by {@code parseProperty()}. It allows the associated layout
+         * object to keep track of the property separators. When saving the
+         * configuration the separators can be restored.
+         *
+         * @param value the separator used for the current property
+         * @since 1.7
+         */
+        protected void initPropertySeparator(String value)
+        {
+            propertySeparator = value;
+        }
+
+        /**
+         * Checks if the passed in line should be combined with the following.
+         * This is true, if the line ends with an odd number of backslashes.
+         *
+         * @param line the line
+         * @return a flag if the lines should be combined
+         */
+        private static boolean checkCombineLines(String line)
+        {
+            return countTrailingBS(line) % 2 != 0;
+        }
+
+        /**
+         * Parse a property line and return the key, the value, and the separator in an array.
+         *
+         * @param line the line to parse
+         * @return an array with the property's key, value, and separator
+         */
+        private static String[] doParseProperty(String line)
+        {
+            Matcher matcher = PROPERTY_PATTERN.matcher(line);
+
+            String[] result = {"", "", ""};
+
+            if (matcher.matches())
+            {
+                result[0] = matcher.group(IDX_KEY).trim();
+                result[1] = matcher.group(IDX_VALUE).trim();
+                result[2] = matcher.group(IDX_SEPARATOR);
+            }
+
+            return result;
+        }
+    } // class PropertiesReader
+
+    /**
+     * This class is used to write properties lines. The most important method
+     * is {@code writeProperty(String, Object, boolean)}, which is called
+     * during a save operation for each property found in the configuration.
+     */
+    public static class PropertiesWriter extends FilterWriter
+    {
+        /** Constant for the initial size when creating a string buffer. */
+        private static final int BUF_SIZE = 8;
+
+        /** The delimiter for multi-valued properties.*/
+        private char delimiter;
+
+        /** The separator to be used for the current property. */
+        private String currentSeparator;
+
+        /** The global separator. If set, it overrides the current separator.*/
+        private String globalSeparator;
+
+        /** The line separator.*/
+        private String lineSeparator;
+
+        /**
+         * Constructor.
+         *
+         * @param writer a Writer object providing the underlying stream
+         * @param delimiter the delimiter character for multi-valued properties
+         */
+        public PropertiesWriter(Writer writer, char delimiter)
+        {
+            super(writer);
+            this.delimiter = delimiter;
+        }
+
+        /**
+         * Returns the current property separator.
+         *
+         * @return the current property separator
+         * @since 1.7
+         */
+        public String getCurrentSeparator()
+        {
+            return currentSeparator;
+        }
+
+        /**
+         * Sets the current property separator. This separator is used when
+         * writing the next property.
+         *
+         * @param currentSeparator the current property separator
+         * @since 1.7
+         */
+        public void setCurrentSeparator(String currentSeparator)
+        {
+            this.currentSeparator = currentSeparator;
+        }
+
+        /**
+         * Returns the global property separator.
+         *
+         * @return the global property separator
+         * @since 1.7
+         */
+        public String getGlobalSeparator()
+        {
+            return globalSeparator;
+        }
+
+        /**
+         * Sets the global property separator. This separator corresponds to the
+         * {@code globalSeparator} property of
+         * {@link PropertiesConfigurationLayout}. It defines the separator to be
+         * used for all properties. If it is undefined, the current separator is
+         * used.
+         *
+         * @param globalSeparator the global property separator
+         * @since 1.7
+         */
+        public void setGlobalSeparator(String globalSeparator)
+        {
+            this.globalSeparator = globalSeparator;
+        }
+
+        /**
+         * Returns the line separator.
+         *
+         * @return the line separator
+         * @since 1.7
+         */
+        public String getLineSeparator()
+        {
+            return (lineSeparator != null) ? lineSeparator : LINE_SEPARATOR;
+        }
+
+        /**
+         * Sets the line separator. Each line written by this writer is
+         * terminated with this separator. If not set, the platform-specific
+         * line separator is used.
+         *
+         * @param lineSeparator the line separator to be used
+         * @since 1.7
+         */
+        public void setLineSeparator(String lineSeparator)
+        {
+            this.lineSeparator = lineSeparator;
+        }
+
+        /**
+         * Write a property.
+         *
+         * @param key the key of the property
+         * @param value the value of the property
+         *
+         * @throws IOException if an I/O error occurs
+         */
+        public void writeProperty(String key, Object value) throws IOException
+        {
+            writeProperty(key, value, false);
+        }
+
+        /**
+         * Write a property.
+         *
+         * @param key The key of the property
+         * @param values The array of values of the property
+         *
+         * @throws IOException if an I/O error occurs
+         */
+        public void writeProperty(String key, List<?> values) throws IOException
+        {
+            for (int i = 0; i < values.size(); i++)
+            {
+                writeProperty(key, values.get(i));
+            }
+        }
+
+        /**
+         * Writes the given property and its value. If the value happens to be a
+         * list, the {@code forceSingleLine} flag is evaluated. If it is
+         * set, all values are written on a single line using the list delimiter
+         * as separator.
+         *
+         * @param key the property key
+         * @param value the property value
+         * @param forceSingleLine the "force single line" flag
+         * @throws IOException if an error occurs
+         * @since 1.3
+         */
+        public void writeProperty(String key, Object value,
+                boolean forceSingleLine) throws IOException
+        {
+            String v;
+
+            if (value instanceof List)
+            {
+                List<?> values = (List<?>) value;
+                if (forceSingleLine)
+                {
+                    v = makeSingleLineValue(values);
+                }
+                else
+                {
+                    writeProperty(key, values);
+                    return;
+                }
+            }
+            else
+            {
+                v = escapeValue(value, false);
+            }
+
+            write(escapeKey(key));
+            write(fetchSeparator(key, value));
+            write(v);
+
+            writeln(null);
+        }
+
+        /**
+         * Write a comment.
+         *
+         * @param comment the comment to write
+         * @throws IOException if an I/O error occurs
+         */
+        public void writeComment(String comment) throws IOException
+        {
+            writeln("# " + comment);
+        }
+
+        /**
+         * Escape the separators in the key.
+         *
+         * @param key the key
+         * @return the escaped key
+         * @since 1.2
+         */
+        private String escapeKey(String key)
+        {
+            StringBuilder newkey = new StringBuilder();
+
+            for (int i = 0; i < key.length(); i++)
+            {
+                char c = key.charAt(i);
+
+                if (ArrayUtils.contains(SEPARATORS, c) || ArrayUtils.contains(WHITE_SPACE, c))
+                {
+                    // escape the separator
+                    newkey.append('\\');
+                    newkey.append(c);
+                }
+                else
+                {
+                    newkey.append(c);
+                }
+            }
+
+            return newkey.toString();
+        }
+
+        /**
+         * Escapes the given property value. Delimiter characters in the value
+         * will be escaped.
+         *
+         * @param value the property value
+         * @param inList a flag whether the value is part of a list
+         * @return the escaped property value
+         * @since 1.3
+         */
+        private String escapeValue(Object value, boolean inList)
+        {
+            String escapedValue = handleBackslashs(value, inList);
+            if (delimiter != 0)
+            {
+                escapedValue = StringUtils.replace(escapedValue, String.valueOf(delimiter), ESCAPE + delimiter);
+            }
+            return escapedValue;
+        }
+
+        /**
+         * Performs the escaping of backslashes in the specified properties
+         * value. Because a double backslash is used to escape the escape
+         * character of a list delimiter, double backslashes also have to be
+         * escaped if the property is part of a (single line) list. Then, in all
+         * cases each backslash has to be doubled in order to produce a valid
+         * properties file.
+         *
+         * @param value the value to be escaped
+         * @param inList a flag whether the value is part of a list
+         * @return the value with escaped backslashes as string
+         */
+        private String handleBackslashs(Object value, boolean inList)
+        {
+            String strValue = String.valueOf(value);
+
+            if (inList && strValue.indexOf(DOUBLE_ESC) >= 0)
+            {
+                char esc = ESCAPE.charAt(0);
+                StringBuilder buf = new StringBuilder(strValue.length() + BUF_SIZE);
+                for (int i = 0; i < strValue.length(); i++)
+                {
+                    if (strValue.charAt(i) == esc && i < strValue.length() - 1
+                            && strValue.charAt(i + 1) == esc)
+                    {
+                        buf.append(DOUBLE_ESC).append(DOUBLE_ESC);
+                        i++;
+                    }
+                    else
+                    {
+                        buf.append(strValue.charAt(i));
+                    }
+                }
+
+                strValue = buf.toString();
+            }
+
+            return StringEscapeUtils.escapeJava(strValue);
+        }
+
+        /**
+         * Transforms a list of values into a single line value.
+         *
+         * @param values the list with the values
+         * @return a string with the single line value (can be <b>null</b>)
+         * @since 1.3
+         */
+        private String makeSingleLineValue(List<?> values)
+        {
+            if (!values.isEmpty())
+            {
+                Iterator<?> it = values.iterator();
+                String lastValue = escapeValue(it.next(), true);
+                StringBuilder buf = new StringBuilder(lastValue);
+                while (it.hasNext())
+                {
+                    // if the last value ended with an escape character, it has
+                    // to be escaped itself; otherwise the list delimiter will
+                    // be escaped
+                    if (lastValue.endsWith(ESCAPE) && (countTrailingBS(lastValue) / 2) % 2 != 0)
+                    {
+                        buf.append(ESCAPE).append(ESCAPE);
+                    }
+                    buf.append(delimiter);
+                    lastValue = escapeValue(it.next(), true);
+                    buf.append(lastValue);
+                }
+                return buf.toString();
+            }
+            else
+            {
+                return null;
+            }
+        }
+
+        /**
+         * Helper method for writing a line with the platform specific line
+         * ending.
+         *
+         * @param s the content of the line (may be <b>null</b>)
+         * @throws IOException if an error occurs
+         * @since 1.3
+         */
+        public void writeln(String s) throws IOException
+        {
+            if (s != null)
+            {
+                write(s);
+            }
+            write(getLineSeparator());
+        }
+
+        /**
+         * Returns the separator to be used for the given property. This method
+         * is called by {@code writeProperty()}. The string returned here
+         * is used as separator between the property key and its value. Per
+         * default the method checks whether a global separator is set. If this
+         * is the case, it is returned. Otherwise the separator returned by
+         * {@code getCurrentSeparator()} is used, which was set by the
+         * associated layout object. Derived classes may implement a different
+         * strategy for defining the separator.
+         *
+         * @param key the property key
+         * @param value the value
+         * @return the separator to be used
+         * @since 1.7
+         */
+        protected String fetchSeparator(String key, Object value)
+        {
+            return (getGlobalSeparator() != null) ? getGlobalSeparator()
+                    : getCurrentSeparator();
+        }
+    } // class PropertiesWriter
+
+    /**
+     * <p>
+     * Definition of an interface that allows customization of read and write
+     * operations.
+     * </p>
+     * <p>
+     * For reading and writing properties files the inner classes
+     * {@code PropertiesReader} and {@code PropertiesWriter} are used.
+     * This interface defines factory methods for creating both a
+     * {@code PropertiesReader} and a {@code PropertiesWriter}. An
+     * object implementing this interface can be passed to the
+     * {@code setIOFactory()} method of
+     * {@code PropertiesConfiguration}. Every time the configuration is
+     * read or written the {@code IOFactory} is asked to create the
+     * appropriate reader or writer object. This provides an opportunity to
+     * inject custom reader or writer implementations.
+     * </p>
+     *
+     * @since 1.7
+     */
+    public interface IOFactory
+    {
+        /**
+         * Creates a {@code PropertiesReader} for reading a properties
+         * file. This method is called whenever the
+         * {@code PropertiesConfiguration} is loaded. The reader returned
+         * by this method is then used for parsing the properties file.
+         *
+         * @param in the underlying reader (of the properties file)
+         * @param delimiter the delimiter character for list parsing
+         * @return the {@code PropertiesReader} for loading the
+         *         configuration
+         */
+        PropertiesReader createPropertiesReader(Reader in, char delimiter);
+
+        /**
+         * Creates a {@code PropertiesWriter} for writing a properties
+         * file. This method is called before the
+         * {@code PropertiesConfiguration} is saved. The writer returned by
+         * this method is then used for writing the properties file.
+         *
+         * @param out the underlying writer (to the properties file)
+         * @param delimiter the delimiter character for list parsing
+         * @return the {@code PropertiesWriter} for saving the
+         *         configuration
+         */
+        PropertiesWriter createPropertiesWriter(Writer out, char delimiter);
+    }
+
+    /**
+     * <p>
+     * A default implementation of the {@code IOFactory} interface.
+     * </p>
+     * <p>
+     * This class implements the {@code createXXXX()} methods defined by
+     * the {@code IOFactory} interface in a way that the default objects
+     * (i.e. {@code PropertiesReader} and {@code PropertiesWriter} are
+     * returned. Customizing either the reader or the writer (or both) can be
+     * done by extending this class and overriding the corresponding
+     * {@code createXXXX()} method.
+     * </p>
+     *
+     * @since 1.7
+     */
+    public static class DefaultIOFactory implements IOFactory
+    {
+        public PropertiesReader createPropertiesReader(Reader in, char delimiter)
+        {
+            return new PropertiesReader(in, delimiter);
+        }
+
+        public PropertiesWriter createPropertiesWriter(Writer out,
+                char delimiter)
+        {
+            return new PropertiesWriter(out, delimiter);
+        }
+    }
+
+    /**
+     * <p>Unescapes any Java literals found in the {@code String} to a
+     * {@code Writer}.</p> This is a slightly modified version of the
+     * StringEscapeUtils.unescapeJava() function in commons-lang that doesn't
+     * drop escaped separators (i.e '\,').
+     *
+     * @param str  the {@code String} to unescape, may be null
+     * @param delimiter the delimiter for multi-valued properties
+     * @return the processed string
+     * @throws IllegalArgumentException if the Writer is {@code null}
+     */
+    protected static String unescapeJava(String str, char delimiter)
+    {
+        if (str == null)
+        {
+            return null;
+        }
+        int sz = str.length();
+        StringBuilder out = new StringBuilder(sz);
+        StringBuilder unicode = new StringBuilder(UNICODE_LEN);
+        boolean hadSlash = false;
+        boolean inUnicode = false;
+        for (int i = 0; i < sz; i++)
+        {
+            char ch = str.charAt(i);
+            if (inUnicode)
+            {
+                // if in unicode, then we're reading unicode
+                // values in somehow
+                unicode.append(ch);
+                if (unicode.length() == UNICODE_LEN)
+                {
+                    // unicode now contains the four hex digits
+                    // which represents our unicode character
+                    try
+                    {
+                        int value = Integer.parseInt(unicode.toString(), HEX_RADIX);
+                        out.append((char) value);
+                        unicode.setLength(0);
+                        inUnicode = false;
+                        hadSlash = false;
+                    }
+                    catch (NumberFormatException nfe)
+                    {
+                        throw new ConfigurationRuntimeException("Unable to parse unicode value: " + unicode, nfe);
+                    }
+                }
+                continue;
+            }
+
+            if (hadSlash)
+            {
+                // handle an escaped value
+                hadSlash = false;
+
+                if (ch == '\\')
+                {
+                    out.append('\\');
+                }
+                else if (ch == '\'')
+                {
+                    out.append('\'');
+                }
+                else if (ch == '\"')
+                {
+                    out.append('"');
+                }
+                else if (ch == 'r')
+                {
+                    out.append('\r');
+                }
+                else if (ch == 'f')
+                {
+                    out.append('\f');
+                }
+                else if (ch == 't')
+                {
+                    out.append('\t');
+                }
+                else if (ch == 'n')
+                {
+                    out.append('\n');
+                }
+                else if (ch == 'b')
+                {
+                    out.append('\b');
+                }
+                else if (ch == delimiter)
+                {
+                    out.append('\\');
+                    out.append(delimiter);
+                }
+                else if (ch == 'u')
+                {
+                    // uh-oh, we're in unicode country....
+                    inUnicode = true;
+                }
+                else
+                {
+                    out.append(ch);
+                }
+
+                continue;
+            }
+            else if (ch == '\\')
+            {
+                hadSlash = true;
+                continue;
+            }
+            out.append(ch);
+        }
+
+        if (hadSlash)
+        {
+            // then we're in the weird case of a \ at the end of the
+            // string, let's output it anyway.
+            out.append('\\');
+        }
+
+        return out.toString();
+    }
+
+    /**
+     * Helper method for loading an included properties file. This method is
+     * called by {@code load()} when an {@code include} property
+     * is encountered. It tries to resolve relative file names based on the
+     * current base path. If this fails, a resolution based on the location of
+     * this properties file is tried.
+     *
+     * @param fileName the name of the file to load
+     * @throws ConfigurationException if loading fails
+     */
+    private void loadIncludeFile(String fileName) throws ConfigurationException
+    {
+        URL url = ConfigurationUtils.locate(getFileSystem(), getBasePath(), fileName);
+        if (url == null)
+        {
+            URL baseURL = getURL();
+            if (baseURL != null)
+            {
+                url = ConfigurationUtils.locate(getFileSystem(), baseURL.toString(), fileName);
+            }
+        }
+
+        if (url == null)
+        {
+            throw new ConfigurationException("Cannot resolve include file "
+                    + fileName);
+        }
+        load(url);
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/PropertiesConfigurationLayout.java b/src/main/java/org/apache/commons/configuration/PropertiesConfigurationLayout.java
new file mode 100644
index 0000000..e4f0891
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/PropertiesConfigurationLayout.java
@@ -0,0 +1,1064 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.configuration.event.ConfigurationEvent;
+import org.apache.commons.configuration.event.ConfigurationListener;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * <p>
+ * A helper class used by {@link PropertiesConfiguration} to keep
+ * the layout of a properties file.
+ * </p>
+ * <p>
+ * Instances of this class are associated with a
+ * {@code PropertiesConfiguration} object. They are responsible for
+ * analyzing properties files and for extracting as much information about the
+ * file layout (e.g. empty lines, comments) as possible. When the properties
+ * file is written back again it should be close to the original.
+ * </p>
+ * <p>
+ * The {@code PropertiesConfigurationLayout} object associated with a
+ * {@code PropertiesConfiguration} object can be obtained using the
+ * {@code getLayout()} method of the configuration. Then the methods
+ * provided by this class can be used to alter the properties file's layout.
+ * </p>
+ * <p>
+ * Implementation note: This is a very simple implementation, which is far away
+ * from being perfect, i.e. the original layout of a properties file won't be
+ * reproduced in all cases. One limitation is that comments for multi-valued
+ * property keys are concatenated. Maybe this implementation can later be
+ * improved.
+ * </p>
+ * <p>
+ * To get an impression how this class works consider the following properties
+ * file:
+ * </p>
+ * <p>
+ *
+ * <pre>
+ * # A demo configuration file
+ * # for Demo App 1.42
+ *
+ * # Application name
+ * AppName=Demo App
+ *
+ * # Application vendor
+ * AppVendor=DemoSoft
+ *
+ *
+ * # GUI properties
+ * # Window Color
+ * windowColors=0xFFFFFF,0x000000
+ *
+ * # Include some setting
+ * include=settings.properties
+ * # Another vendor
+ * AppVendor=TestSoft
+ * </pre>
+ *
+ * </p>
+ * <p>
+ * For this example the following points are relevant:
+ * </p>
+ * <p>
+ * <ul>
+ * <li>The first two lines are set as header comment. The header comment is
+ * determined by the last blanc line before the first property definition.</li>
+ * <li>For the property {@code AppName} one comment line and one
+ * leading blanc line is stored.</li>
+ * <li>For the property {@code windowColors} two comment lines and two
+ * leading blanc lines are stored.</li>
+ * <li>Include files is something this class cannot deal with well. When saving
+ * the properties configuration back, the included properties are simply
+ * contained in the original file. The comment before the include property is
+ * skipped.</li>
+ * <li>For all properties except for {@code AppVendor} the "single
+ * line" flag is set. This is relevant only for {@code windowColors},
+ * which has multiple values defined in one line using the separator character.</li>
+ * <li>The {@code AppVendor} property appears twice. The comment lines
+ * are concatenated, so that {@code layout.getComment("AppVendor");} will
+ * result in <code>Application vendor<CR>Another vendor</code>, with
+ * <code><CR></code> meaning the line separator. In addition the
+ * "single line" flag is set to <b>false</b> for this property. When
+ * the file is saved, two property definitions will be written (in series).</li>
+ * </ul>
+ * </p>
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: PropertiesConfigurationLayout.java 1534402 2013-10-21 22:35:52Z henning $
+ * @since 1.3
+ */
+public class PropertiesConfigurationLayout implements ConfigurationListener
+{
+    /** Constant for the line break character. */
+    private static final String CR = "\n";
+
+    /** Constant for the default comment prefix. */
+    private static final String COMMENT_PREFIX = "# ";
+
+    /** Stores the associated configuration object. */
+    private PropertiesConfiguration configuration;
+
+    /** Stores a map with the contained layout information. */
+    private Map<String, PropertyLayoutData> layoutData;
+
+    /** Stores the header comment. */
+    private String headerComment;
+
+    /** Stores the footer comment. */
+    private String footerComment;
+
+    /** The global separator that will be used for all properties. */
+    private String globalSeparator;
+
+    /** The line separator.*/
+    private String lineSeparator;
+
+    /** A counter for determining nested load calls. */
+    private int loadCounter;
+
+    /** Stores the force single line flag. */
+    private boolean forceSingleLine;
+
+    /**
+     * Creates a new instance of {@code PropertiesConfigurationLayout}
+     * and initializes it with the associated configuration object.
+     *
+     * @param config the configuration (must not be <b>null</b>)
+     */
+    public PropertiesConfigurationLayout(PropertiesConfiguration config)
+    {
+        this(config, null);
+    }
+
+    /**
+     * Creates a new instance of {@code PropertiesConfigurationLayout}
+     * and initializes it with the given configuration object. The data of the
+     * specified layout object is copied.
+     *
+     * @param config the configuration (must not be <b>null</b>)
+     * @param c the layout object to be copied
+     */
+    public PropertiesConfigurationLayout(PropertiesConfiguration config,
+            PropertiesConfigurationLayout c)
+    {
+        if (config == null)
+        {
+            throw new IllegalArgumentException(
+                    "Configuration must not be null!");
+        }
+        configuration = config;
+        layoutData = new LinkedHashMap<String, PropertyLayoutData>();
+        config.addConfigurationListener(this);
+
+        if (c != null)
+        {
+            copyFrom(c);
+        }
+    }
+
+    /**
+     * Returns the associated configuration object.
+     *
+     * @return the associated configuration
+     */
+    public PropertiesConfiguration getConfiguration()
+    {
+        return configuration;
+    }
+
+    /**
+     * Returns the comment for the specified property key in a canonical form.
+     * "Canonical" means that either all lines start with a comment
+     * character or none. If the {@code commentChar} parameter is <b>false</b>,
+     * all comment characters are removed, so that the result is only the plain
+     * text of the comment. Otherwise it is ensured that each line of the
+     * comment starts with a comment character. Also, line breaks in the comment
+     * are normalized to the line separator "\n".
+     *
+     * @param key the key of the property
+     * @param commentChar determines whether all lines should start with comment
+     * characters or not
+     * @return the canonical comment for this key (can be <b>null</b>)
+     */
+    public String getCanonicalComment(String key, boolean commentChar)
+    {
+        return constructCanonicalComment(getComment(key), commentChar);
+    }
+
+    /**
+     * Returns the comment for the specified property key. The comment is
+     * returned as it was set (either manually by calling
+     * {@code setComment()} or when it was loaded from a properties
+     * file). No modifications are performed.
+     *
+     * @param key the key of the property
+     * @return the comment for this key (can be <b>null</b>)
+     */
+    public String getComment(String key)
+    {
+        return fetchLayoutData(key).getComment();
+    }
+
+    /**
+     * Sets the comment for the specified property key. The comment (or its
+     * single lines if it is a multi-line comment) can start with a comment
+     * character. If this is the case, it will be written without changes.
+     * Otherwise a default comment character is added automatically.
+     *
+     * @param key the key of the property
+     * @param comment the comment for this key (can be <b>null</b>, then the
+     * comment will be removed)
+     */
+    public void setComment(String key, String comment)
+    {
+        fetchLayoutData(key).setComment(comment);
+    }
+
+    /**
+     * Returns the number of blanc lines before this property key. If this key
+     * does not exist, 0 will be returned.
+     *
+     * @param key the property key
+     * @return the number of blanc lines before the property definition for this
+     * key
+     */
+    public int getBlancLinesBefore(String key)
+    {
+        return fetchLayoutData(key).getBlancLines();
+    }
+
+    /**
+     * Sets the number of blanc lines before the given property key. This can be
+     * used for a logical grouping of properties.
+     *
+     * @param key the property key
+     * @param number the number of blanc lines to add before this property
+     * definition
+     */
+    public void setBlancLinesBefore(String key, int number)
+    {
+        fetchLayoutData(key).setBlancLines(number);
+    }
+
+    /**
+     * Returns the header comment of the represented properties file in a
+     * canonical form. With the {@code commentChar} parameter it can be
+     * specified whether comment characters should be stripped or be always
+     * present.
+     *
+     * @param commentChar determines the presence of comment characters
+     * @return the header comment (can be <b>null</b>)
+     */
+    public String getCanonicalHeaderComment(boolean commentChar)
+    {
+        return constructCanonicalComment(getHeaderComment(), commentChar);
+    }
+
+    /**
+     * Returns the header comment of the represented properties file. This
+     * method returns the header comment exactly as it was set using
+     * {@code setHeaderComment()} or extracted from the loaded properties
+     * file.
+     *
+     * @return the header comment (can be <b>null</b>)
+     */
+    public String getHeaderComment()
+    {
+        return headerComment;
+    }
+
+    /**
+     * Sets the header comment for the represented properties file. This comment
+     * will be output on top of the file.
+     *
+     * @param comment the comment
+     */
+    public void setHeaderComment(String comment)
+    {
+        headerComment = comment;
+    }
+
+    /**
+     * Returns the footer comment of the represented properties file in a
+     * canonical form. This method works like
+     * {@code getCanonicalHeaderComment()}, but reads the footer comment.
+     *
+     * @param commentChar determines the presence of comment characters
+     * @return the footer comment (can be <b>null</b>)
+     * @see #getCanonicalHeaderComment(boolean)
+     * @since 1.10
+     */
+    public String getCanonicalFooterCooment(boolean commentChar)
+    {
+        return constructCanonicalComment(getFooterComment(), commentChar);
+    }
+
+    /**
+     * Returns the footer comment of the represented properties file. This
+     * method returns the footer comment exactly as it was set using
+     * {@code setFooterComment()} or extracted from the loaded properties
+     * file.
+     *
+     * @return the footer comment (can be <b>null</b>)
+     * @since 1.10
+     */
+    public String getFooterComment()
+    {
+        return footerComment;
+    }
+
+    /**
+     * Sets the footer comment for the represented properties file. This comment
+     * will be output at the bottom of the file.
+     *
+     * @param footerComment the footer comment
+     * @since 1.10
+     */
+    public void setFooterComment(String footerComment)
+    {
+        this.footerComment = footerComment;
+    }
+
+    /**
+     * Returns a flag whether the specified property is defined on a single
+     * line. This is meaningful only if this property has multiple values.
+     *
+     * @param key the property key
+     * @return a flag if this property is defined on a single line
+     */
+    public boolean isSingleLine(String key)
+    {
+        return fetchLayoutData(key).isSingleLine();
+    }
+
+    /**
+     * Sets the "single line flag" for the specified property key.
+     * This flag is evaluated if the property has multiple values (i.e. if it is
+     * a list property). In this case, if the flag is set, all values will be
+     * written in a single property definition using the list delimiter as
+     * separator. Otherwise multiple lines will be written for this property,
+     * each line containing one property value.
+     *
+     * @param key the property key
+     * @param f the single line flag
+     */
+    public void setSingleLine(String key, boolean f)
+    {
+        fetchLayoutData(key).setSingleLine(f);
+    }
+
+    /**
+     * Returns the "force single line" flag.
+     *
+     * @return the force single line flag
+     * @see #setForceSingleLine(boolean)
+     */
+    public boolean isForceSingleLine()
+    {
+        return forceSingleLine;
+    }
+
+    /**
+     * Sets the "force single line" flag. If this flag is set, all
+     * properties with multiple values are written on single lines. This mode
+     * provides more compatibility with {@code java.lang.Properties},
+     * which cannot deal with multiple definitions of a single property. This
+     * mode has no effect if the list delimiter parsing is disabled.
+     *
+     * @param f the force single line flag
+     */
+    public void setForceSingleLine(boolean f)
+    {
+        forceSingleLine = f;
+    }
+
+    /**
+     * Returns the separator for the property with the given key.
+     *
+     * @param key the property key
+     * @return the property separator for this property
+     * @since 1.7
+     */
+    public String getSeparator(String key)
+    {
+        return fetchLayoutData(key).getSeparator();
+    }
+
+    /**
+     * Sets the separator to be used for the property with the given key. The
+     * separator is the string between the property key and its value. For new
+     * properties " = " is used. When a properties file is read, the
+     * layout tries to determine the separator for each property. With this
+     * method the separator can be changed. To be compatible with the properties
+     * format only the characters {@code =} and {@code :} (with or
+     * without whitespace) should be used, but this method does not enforce this
+     * - it accepts arbitrary strings. If the key refers to a property with
+     * multiple values that are written on multiple lines, this separator will
+     * be used on all lines.
+     *
+     * @param key the key for the property
+     * @param sep the separator to be used for this property
+     * @since 1.7
+     */
+    public void setSeparator(String key, String sep)
+    {
+        fetchLayoutData(key).setSeparator(sep);
+    }
+
+    /**
+     * Returns the global separator.
+     *
+     * @return the global properties separator
+     * @since 1.7
+     */
+    public String getGlobalSeparator()
+    {
+        return globalSeparator;
+    }
+
+    /**
+     * Sets the global separator for properties. With this method a separator
+     * can be set that will be used for all properties when writing the
+     * configuration. This is an easy way of determining the properties
+     * separator globally. To be compatible with the properties format only the
+     * characters {@code =} and {@code :} (with or without whitespace)
+     * should be used, but this method does not enforce this - it accepts
+     * arbitrary strings. If the global separator is set to <b>null</b>,
+     * property separators are not changed. This is the default behavior as it
+     * produces results that are closer to the original properties file.
+     *
+     * @param globalSeparator the separator to be used for all properties
+     * @since 1.7
+     */
+    public void setGlobalSeparator(String globalSeparator)
+    {
+        this.globalSeparator = globalSeparator;
+    }
+
+    /**
+     * Returns the line separator.
+     *
+     * @return the line separator
+     * @since 1.7
+     */
+    public String getLineSeparator()
+    {
+        return lineSeparator;
+    }
+
+    /**
+     * Sets the line separator. When writing the properties configuration, all
+     * lines are terminated with this separator. If no separator was set, the
+     * platform-specific default line separator is used.
+     *
+     * @param lineSeparator the line separator
+     * @since 1.7
+     */
+    public void setLineSeparator(String lineSeparator)
+    {
+        this.lineSeparator = lineSeparator;
+    }
+
+    /**
+     * Returns a set with all property keys managed by this object.
+     *
+     * @return a set with all contained property keys
+     */
+    public Set<String> getKeys()
+    {
+        return layoutData.keySet();
+    }
+
+    /**
+     * Reads a properties file and stores its internal structure. The found
+     * properties will be added to the associated configuration object.
+     *
+     * @param in the reader to the properties file
+     * @throws ConfigurationException if an error occurs
+     */
+    public void load(Reader in) throws ConfigurationException
+    {
+        if (++loadCounter == 1)
+        {
+            getConfiguration().removeConfigurationListener(this);
+        }
+        PropertiesConfiguration.PropertiesReader reader = getConfiguration()
+                .getIOFactory().createPropertiesReader(in,
+                        getConfiguration().getListDelimiter());
+
+        try
+        {
+            while (reader.nextProperty())
+            {
+                if (getConfiguration().propertyLoaded(reader.getPropertyName(),
+                        reader.getPropertyValue()))
+                {
+                    boolean contained = layoutData.containsKey(reader
+                            .getPropertyName());
+                    int blancLines = 0;
+                    int idx = checkHeaderComment(reader.getCommentLines());
+                    while (idx < reader.getCommentLines().size()
+                            && reader.getCommentLines().get(idx).length() < 1)
+                    {
+                        idx++;
+                        blancLines++;
+                    }
+                    String comment = extractComment(reader.getCommentLines(),
+                            idx, reader.getCommentLines().size() - 1);
+                    PropertyLayoutData data = fetchLayoutData(reader
+                            .getPropertyName());
+                    if (contained)
+                    {
+                        data.addComment(comment);
+                        data.setSingleLine(false);
+                    }
+                    else
+                    {
+                        data.setComment(comment);
+                        data.setBlancLines(blancLines);
+                        data.setSeparator(reader.getPropertySeparator());
+                    }
+                }
+            }
+
+            setFooterComment(extractComment(reader.getCommentLines(), 0, reader
+                    .getCommentLines().size() - 1));
+        }
+        catch (IOException ioex)
+        {
+            throw new ConfigurationException(ioex);
+        }
+        finally
+        {
+            if (--loadCounter == 0)
+            {
+                getConfiguration().addConfigurationListener(this);
+            }
+        }
+    }
+
+    /**
+     * Writes the properties file to the given writer, preserving as much of its
+     * structure as possible.
+     *
+     * @param out the writer
+     * @throws ConfigurationException if an error occurs
+     */
+    public void save(Writer out) throws ConfigurationException
+    {
+        try
+        {
+            char delimiter = getConfiguration().isDelimiterParsingDisabled() ? 0
+                    : getConfiguration().getListDelimiter();
+            PropertiesConfiguration.PropertiesWriter writer = getConfiguration()
+                    .getIOFactory().createPropertiesWriter(out, delimiter);
+            writer.setGlobalSeparator(getGlobalSeparator());
+            if (getLineSeparator() != null)
+            {
+                writer.setLineSeparator(getLineSeparator());
+            }
+
+            if (headerComment != null)
+            {
+                writeComment(writer, getCanonicalHeaderComment(true));
+                writer.writeln(null);
+            }
+
+            for (String key : layoutData.keySet())
+            {
+                if (getConfiguration().containsKey(key))
+                {
+
+                    // Output blank lines before property
+                    for (int i = 0; i < getBlancLinesBefore(key); i++)
+                    {
+                        writer.writeln(null);
+                    }
+
+                    // Output the comment
+                    writeComment(writer, getCanonicalComment(key, true));
+
+                    // Output the property and its value
+                    boolean singleLine = (isForceSingleLine() || isSingleLine(key))
+                            && !getConfiguration().isDelimiterParsingDisabled();
+                    writer.setCurrentSeparator(getSeparator(key));
+                    writer.writeProperty(key, getConfiguration().getProperty(
+                            key), singleLine);
+                }
+            }
+
+            writeComment(writer, getCanonicalFooterCooment(true));
+            writer.flush();
+        }
+        catch (IOException ioex)
+        {
+            throw new ConfigurationException(ioex);
+        }
+    }
+
+    /**
+     * The event listener callback. Here event notifications of the
+     * configuration object are processed to update the layout object properly.
+     *
+     * @param event the event object
+     */
+    public void configurationChanged(ConfigurationEvent event)
+    {
+        if (event.isBeforeUpdate())
+        {
+            if (AbstractFileConfiguration.EVENT_RELOAD == event.getType())
+            {
+                clear();
+            }
+        }
+
+        else
+        {
+            switch (event.getType())
+            {
+            case AbstractConfiguration.EVENT_ADD_PROPERTY:
+                boolean contained = layoutData.containsKey(event
+                        .getPropertyName());
+                PropertyLayoutData data = fetchLayoutData(event
+                        .getPropertyName());
+                data.setSingleLine(!contained);
+                break;
+            case AbstractConfiguration.EVENT_CLEAR_PROPERTY:
+                layoutData.remove(event.getPropertyName());
+                break;
+            case AbstractConfiguration.EVENT_CLEAR:
+                clear();
+                break;
+            case AbstractConfiguration.EVENT_SET_PROPERTY:
+                fetchLayoutData(event.getPropertyName());
+                break;
+            }
+        }
+    }
+
+    /**
+     * Returns a layout data object for the specified key. If this is a new key,
+     * a new object is created and initialized with default values.
+     *
+     * @param key the key
+     * @return the corresponding layout data object
+     */
+    private PropertyLayoutData fetchLayoutData(String key)
+    {
+        if (key == null)
+        {
+            throw new IllegalArgumentException("Property key must not be null!");
+        }
+
+        PropertyLayoutData data = layoutData.get(key);
+        if (data == null)
+        {
+            data = new PropertyLayoutData();
+            data.setSingleLine(true);
+            layoutData.put(key, data);
+        }
+
+        return data;
+    }
+
+    /**
+     * Removes all content from this layout object.
+     */
+    private void clear()
+    {
+        layoutData.clear();
+        setHeaderComment(null);
+    }
+
+    /**
+     * Tests whether a line is a comment, i.e. whether it starts with a comment
+     * character.
+     *
+     * @param line the line
+     * @return a flag if this is a comment line
+     */
+    static boolean isCommentLine(String line)
+    {
+        return PropertiesConfiguration.isCommentLine(line);
+    }
+
+    /**
+     * Trims a comment. This method either removes all comment characters from
+     * the given string, leaving only the plain comment text or ensures that
+     * every line starts with a valid comment character.
+     *
+     * @param s the string to be processed
+     * @param comment if <b>true</b>, a comment character will always be
+     * enforced; if <b>false</b>, it will be removed
+     * @return the trimmed comment
+     */
+    static String trimComment(String s, boolean comment)
+    {
+        StringBuilder buf = new StringBuilder(s.length());
+        int lastPos = 0;
+        int pos;
+
+        do
+        {
+            pos = s.indexOf(CR, lastPos);
+            if (pos >= 0)
+            {
+                String line = s.substring(lastPos, pos);
+                buf.append(stripCommentChar(line, comment)).append(CR);
+                lastPos = pos + CR.length();
+            }
+        } while (pos >= 0);
+
+        if (lastPos < s.length())
+        {
+            buf.append(stripCommentChar(s.substring(lastPos), comment));
+        }
+        return buf.toString();
+    }
+
+    /**
+     * Either removes the comment character from the given comment line or
+     * ensures that the line starts with a comment character.
+     *
+     * @param s the comment line
+     * @param comment if <b>true</b>, a comment character will always be
+     * enforced; if <b>false</b>, it will be removed
+     * @return the line without comment character
+     */
+    static String stripCommentChar(String s, boolean comment)
+    {
+        if (s.length() < 1 || (isCommentLine(s) == comment))
+        {
+            return s;
+        }
+
+        else
+        {
+            if (!comment)
+            {
+                int pos = 0;
+                // find first comment character
+                while (PropertiesConfiguration.COMMENT_CHARS.indexOf(s
+                        .charAt(pos)) < 0)
+                {
+                    pos++;
+                }
+
+                // Remove leading spaces
+                pos++;
+                while (pos < s.length()
+                        && Character.isWhitespace(s.charAt(pos)))
+                {
+                    pos++;
+                }
+
+                return (pos < s.length()) ? s.substring(pos)
+                        : StringUtils.EMPTY;
+            }
+            else
+            {
+                return COMMENT_PREFIX + s;
+            }
+        }
+    }
+
+    /**
+     * Extracts a comment string from the given range of the specified comment
+     * lines. The single lines are added using a line feed as separator.
+     *
+     * @param commentLines a list with comment lines
+     * @param from the start index
+     * @param to the end index (inclusive)
+     * @return the comment string (<b>null</b> if it is undefined)
+     */
+    private String extractComment(List<String> commentLines, int from, int to)
+    {
+        if (to < from)
+        {
+            return null;
+        }
+
+        else
+        {
+            StringBuilder buf = new StringBuilder(commentLines.get(from));
+            for (int i = from + 1; i <= to; i++)
+            {
+                buf.append(CR);
+                buf.append(commentLines.get(i));
+            }
+            return buf.toString();
+        }
+    }
+
+    /**
+     * Checks if parts of the passed in comment can be used as header comment.
+     * This method checks whether a header comment can be defined (i.e. whether
+     * this is the first comment in the loaded file). If this is the case, it is
+     * searched for the latest blanc line. This line will mark the end of the
+     * header comment. The return value is the index of the first line in the
+     * passed in list, which does not belong to the header comment.
+     *
+     * @param commentLines the comment lines
+     * @return the index of the next line after the header comment
+     */
+    private int checkHeaderComment(List<String> commentLines)
+    {
+        if (loadCounter == 1 && getHeaderComment() == null
+                && layoutData.isEmpty())
+        {
+            // This is the first comment. Search for blanc lines.
+            int index = commentLines.size() - 1;
+            while (index >= 0
+                    && commentLines.get(index).length() > 0)
+            {
+                index--;
+            }
+            setHeaderComment(extractComment(commentLines, 0, index - 1));
+            return index + 1;
+        }
+        else
+        {
+            return 0;
+        }
+    }
+
+    /**
+     * Copies the data from the given layout object.
+     *
+     * @param c the layout object to copy
+     */
+    private void copyFrom(PropertiesConfigurationLayout c)
+    {
+        for (String key : c.getKeys())
+        {
+            PropertyLayoutData data = c.layoutData.get(key);
+            layoutData.put(key, data.clone());
+        }
+
+        setHeaderComment(c.getHeaderComment());
+        setFooterComment(c.getFooterComment());
+    }
+
+    /**
+     * Helper method for writing a comment line. This method ensures that the
+     * correct line separator is used if the comment spans multiple lines.
+     *
+     * @param writer the writer
+     * @param comment the comment to write
+     * @throws IOException if an IO error occurs
+     */
+    private static void writeComment(
+            PropertiesConfiguration.PropertiesWriter writer, String comment)
+            throws IOException
+    {
+        if (comment != null)
+        {
+            writer.writeln(StringUtils.replace(comment, CR, writer
+                    .getLineSeparator()));
+        }
+    }
+
+    /**
+     * Helper method for generating a comment string. Depending on the boolean
+     * argument the resulting string either has no comment characters or a
+     * leading comment character at each line.
+     *
+     * @param comment the comment string to be processed
+     * @param commentChar determines the presence of comment characters
+     * @return the canonical comment string (can be <b>null</b>)
+     */
+    private static String constructCanonicalComment(String comment,
+            boolean commentChar)
+    {
+        return (comment == null) ? null : trimComment(comment, commentChar);
+    }
+
+    /**
+     * A helper class for storing all layout related information for a
+     * configuration property.
+     */
+    static class PropertyLayoutData implements Cloneable
+    {
+        /** Stores the comment for the property. */
+        private StringBuffer comment;
+
+        /** The separator to be used for this property. */
+        private String separator;
+
+        /** Stores the number of blanc lines before this property. */
+        private int blancLines;
+
+        /** Stores the single line property. */
+        private boolean singleLine;
+
+        /**
+         * Creates a new instance of {@code PropertyLayoutData}.
+         */
+        public PropertyLayoutData()
+        {
+            singleLine = true;
+            separator = PropertiesConfiguration.DEFAULT_SEPARATOR;
+        }
+
+        /**
+         * Returns the number of blanc lines before this property.
+         *
+         * @return the number of blanc lines before this property
+         */
+        public int getBlancLines()
+        {
+            return blancLines;
+        }
+
+        /**
+         * Sets the number of properties before this property.
+         *
+         * @param blancLines the number of properties before this property
+         */
+        public void setBlancLines(int blancLines)
+        {
+            this.blancLines = blancLines;
+        }
+
+        /**
+         * Returns the single line flag.
+         *
+         * @return the single line flag
+         */
+        public boolean isSingleLine()
+        {
+            return singleLine;
+        }
+
+        /**
+         * Sets the single line flag.
+         *
+         * @param singleLine the single line flag
+         */
+        public void setSingleLine(boolean singleLine)
+        {
+            this.singleLine = singleLine;
+        }
+
+        /**
+         * Adds a comment for this property. If already a comment exists, the
+         * new comment is added (separated by a newline).
+         *
+         * @param s the comment to add
+         */
+        public void addComment(String s)
+        {
+            if (s != null)
+            {
+                if (comment == null)
+                {
+                    comment = new StringBuffer(s);
+                }
+                else
+                {
+                    comment.append(CR).append(s);
+                }
+            }
+        }
+
+        /**
+         * Sets the comment for this property.
+         *
+         * @param s the new comment (can be <b>null</b>)
+         */
+        public void setComment(String s)
+        {
+            if (s == null)
+            {
+                comment = null;
+            }
+            else
+            {
+                comment = new StringBuffer(s);
+            }
+        }
+
+        /**
+         * Returns the comment for this property. The comment is returned as it
+         * is, without processing of comment characters.
+         *
+         * @return the comment (can be <b>null</b>)
+         */
+        public String getComment()
+        {
+            return (comment == null) ? null : comment.toString();
+        }
+
+        /**
+         * Returns the separator that was used for this property.
+         *
+         * @return the property separator
+         */
+        public String getSeparator()
+        {
+            return separator;
+        }
+
+        /**
+         * Sets the separator to be used for the represented property.
+         *
+         * @param separator the property separator
+         */
+        public void setSeparator(String separator)
+        {
+            this.separator = separator;
+        }
+
+        /**
+         * Creates a copy of this object.
+         *
+         * @return the copy
+         */
+        @Override
+        public PropertyLayoutData clone()
+        {
+            try
+            {
+                PropertyLayoutData copy = (PropertyLayoutData) super.clone();
+                if (comment != null)
+                {
+                    // must copy string buffer, too
+                    copy.comment = new StringBuffer(getComment());
+                }
+                return copy;
+            }
+            catch (CloneNotSupportedException cnex)
+            {
+                // This cannot happen!
+                throw new ConfigurationRuntimeException(cnex);
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/PropertyConverter.java b/src/main/java/org/apache/commons/configuration/PropertyConverter.java
new file mode 100644
index 0000000..d3a4030
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/PropertyConverter.java
@@ -0,0 +1,1062 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.awt.Color;
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.commons.lang.BooleanUtils;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * A utility class to convert the configuration properties into any type.
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: PropertyConverter.java 1534376 2013-10-21 21:14:18Z henning $
+ * @since 1.1
+ */
+public final class PropertyConverter
+{
+    /** Constant for the list delimiter as char.*/
+    static final char LIST_ESC_CHAR = '\\';
+
+    /** Constant for the list delimiter escaping character as string.*/
+    static final String LIST_ESCAPE = String.valueOf(LIST_ESC_CHAR);
+
+    /** Constant for the prefix of hex numbers.*/
+    private static final String HEX_PREFIX = "0x";
+
+    /** Constant for the radix of hex numbers.*/
+    private static final int HEX_RADIX = 16;
+
+    /** Constant for the prefix of binary numbers.*/
+    private static final String BIN_PREFIX = "0b";
+
+    /** Constant for the radix of binary numbers.*/
+    private static final int BIN_RADIX = 2;
+
+    /** Constant for the argument classes of the Number constructor that takes a String. */
+    private static final Class<?>[] CONSTR_ARGS = {String.class};
+
+    /** The fully qualified name of {@link javax.mail.internet.InternetAddress} */
+    private static final String INTERNET_ADDRESS_CLASSNAME = "javax.mail.internet.InternetAddress";
+
+    /**
+     * Private constructor prevents instances from being created.
+     */
+    private PropertyConverter()
+    {
+        // to prevent instantiation...
+    }
+
+    /**
+     * Converts the specified value to the target class. If the class is a
+     * primitive type (Integer.TYPE, Boolean.TYPE, etc) the value returned
+     * will use the wrapper type (Integer.class, Boolean.class, etc).
+     *
+     * @param cls   the target class of the converted value
+     * @param value the value to convert
+     * @param params optional parameters used for the conversion
+     * @return the converted value
+     * @throws ConversionException if the value is not compatible with the requested type
+     *
+     * @since 1.5
+     */
+    static Object to(Class<?> cls, Object value, Object[] params) throws ConversionException
+    {
+        if (cls.isInstance(value))
+        {
+            return value; // no conversion needed
+        }
+
+        if (Boolean.class.equals(cls) || Boolean.TYPE.equals(cls))
+        {
+            return toBoolean(value);
+        }
+        else if (Character.class.equals(cls) || Character.TYPE.equals(cls))
+        {
+            return toCharacter(value);
+        }
+        else if (Number.class.isAssignableFrom(cls) || cls.isPrimitive())
+        {
+            if (Integer.class.equals(cls) || Integer.TYPE.equals(cls))
+            {
+                return toInteger(value);
+            }
+            else if (Long.class.equals(cls) || Long.TYPE.equals(cls))
+            {
+                return toLong(value);
+            }
+            else if (Byte.class.equals(cls) || Byte.TYPE.equals(cls))
+            {
+                return toByte(value);
+            }
+            else if (Short.class.equals(cls) || Short.TYPE.equals(cls))
+            {
+                return toShort(value);
+            }
+            else if (Float.class.equals(cls) || Float.TYPE.equals(cls))
+            {
+                return toFloat(value);
+            }
+            else if (Double.class.equals(cls) || Double.TYPE.equals(cls))
+            {
+                return toDouble(value);
+            }
+            else if (BigInteger.class.equals(cls))
+            {
+                return toBigInteger(value);
+            }
+            else if (BigDecimal.class.equals(cls))
+            {
+                return toBigDecimal(value);
+            }
+        }
+        else if (Date.class.equals(cls))
+        {
+            return toDate(value, (String) params[0]);
+        }
+        else if (Calendar.class.equals(cls))
+        {
+            return toCalendar(value, (String) params[0]);
+        }
+        else if (URL.class.equals(cls))
+        {
+            return toURL(value);
+        }
+        else if (Locale.class.equals(cls))
+        {
+            return toLocale(value);
+        }
+        else if (isEnum(cls))
+        {
+            return convertToEnum(cls, value);
+        }
+        else if (Color.class.equals(cls))
+        {
+            return toColor(value);
+        }
+        else if (cls.getName().equals(INTERNET_ADDRESS_CLASSNAME))
+        {
+            return toInternetAddress(value);
+        }
+        else if (InetAddress.class.isAssignableFrom(cls))
+        {
+            return toInetAddress(value);
+        }
+
+        throw new ConversionException("The value '" + value + "' (" + value.getClass() + ")"
+                + " can't be converted to a " + cls.getName() + " object");
+    }
+
+    /**
+     * Convert the specified object into a Boolean. Internally the
+     * {@code org.apache.commons.lang.BooleanUtils} class from the
+     * <a href="http://commons.apache.org/lang/">Commons Lang</a>
+     * project is used to perform this conversion. This class accepts some more
+     * tokens for the boolean value of <b>true</b>, e.g. {@code yes} and
+     * {@code on}. Please refer to the documentation of this class for more
+     * details.
+     *
+     * @param value the value to convert
+     * @return the converted value
+     * @throws ConversionException thrown if the value cannot be converted to a boolean
+     */
+    public static Boolean toBoolean(Object value) throws ConversionException
+    {
+        if (value instanceof Boolean)
+        {
+            return (Boolean) value;
+        }
+        else if (value instanceof String)
+        {
+            Boolean b = BooleanUtils.toBooleanObject((String) value);
+            if (b == null)
+            {
+                throw new ConversionException("The value " + value + " can't be converted to a Boolean object");
+            }
+            return b;
+        }
+        else
+        {
+            throw new ConversionException("The value " + value + " can't be converted to a Boolean object");
+        }
+    }
+
+    /**
+     * Converts the specified value object to a {@code Character}. This method
+     * converts the passed in object to a string. If the string has exactly one
+     * character, this character is returned as result. Otherwise, conversion
+     * fails.
+     *
+     * @param value the value to be converted
+     * @return the resulting {@code Character} object
+     * @throws ConversionException if the conversion is not possible
+     */
+    public static Character toCharacter(Object value) throws ConversionException
+    {
+        String strValue = String.valueOf(value);
+        if (strValue.length() == 1)
+        {
+            return Character.valueOf(strValue.charAt(0));
+        }
+        else
+        {
+            throw new ConversionException(
+                    String.format(
+                            "The value '%s' cannot be converted to a Character object!",
+                            strValue));
+        }
+    }
+
+    /**
+     * Convert the specified object into a Byte.
+     *
+     * @param value the value to convert
+     * @return the converted value
+     * @throws ConversionException thrown if the value cannot be converted to a byte
+     */
+    public static Byte toByte(Object value) throws ConversionException
+    {
+        Number n = toNumber(value, Byte.class);
+        if (n instanceof Byte)
+        {
+            return (Byte) n;
+        }
+        else
+        {
+            return new Byte(n.byteValue());
+        }
+    }
+
+    /**
+     * Convert the specified object into a Short.
+     *
+     * @param value the value to convert
+     * @return the converted value
+     * @throws ConversionException thrown if the value cannot be converted to a short
+     */
+    public static Short toShort(Object value) throws ConversionException
+    {
+        Number n = toNumber(value, Short.class);
+        if (n instanceof Short)
+        {
+            return (Short) n;
+        }
+        else
+        {
+            return new Short(n.shortValue());
+        }
+    }
+
+    /**
+     * Convert the specified object into an Integer.
+     *
+     * @param value the value to convert
+     * @return the converted value
+     * @throws ConversionException thrown if the value cannot be converted to an integer
+     */
+    public static Integer toInteger(Object value) throws ConversionException
+    {
+        Number n = toNumber(value, Integer.class);
+        if (n instanceof Integer)
+        {
+            return (Integer) n;
+        }
+        else
+        {
+            return new Integer(n.intValue());
+        }
+    }
+
+    /**
+     * Convert the specified object into a Long.
+     *
+     * @param value the value to convert
+     * @return the converted value
+     * @throws ConversionException thrown if the value cannot be converted to a Long
+     */
+    public static Long toLong(Object value) throws ConversionException
+    {
+        Number n = toNumber(value, Long.class);
+        if (n instanceof Long)
+        {
+            return (Long) n;
+        }
+        else
+        {
+            return new Long(n.longValue());
+        }
+    }
+
+    /**
+     * Convert the specified object into a Float.
+     *
+     * @param value the value to convert
+     * @return the converted value
+     * @throws ConversionException thrown if the value cannot be converted to a Float
+     */
+    public static Float toFloat(Object value) throws ConversionException
+    {
+        Number n = toNumber(value, Float.class);
+        if (n instanceof Float)
+        {
+            return (Float) n;
+        }
+        else
+        {
+            return new Float(n.floatValue());
+        }
+    }
+
+    /**
+     * Convert the specified object into a Double.
+     *
+     * @param value the value to convert
+     * @return the converted value
+     * @throws ConversionException thrown if the value cannot be converted to a Double
+     */
+    public static Double toDouble(Object value) throws ConversionException
+    {
+        Number n = toNumber(value, Double.class);
+        if (n instanceof Double)
+        {
+            return (Double) n;
+        }
+        else
+        {
+            return new Double(n.doubleValue());
+        }
+    }
+
+    /**
+     * Convert the specified object into a BigInteger.
+     *
+     * @param value the value to convert
+     * @return the converted value
+     * @throws ConversionException thrown if the value cannot be converted to a BigInteger
+     */
+    public static BigInteger toBigInteger(Object value) throws ConversionException
+    {
+        Number n = toNumber(value, BigInteger.class);
+        if (n instanceof BigInteger)
+        {
+            return (BigInteger) n;
+        }
+        else
+        {
+            return BigInteger.valueOf(n.longValue());
+        }
+    }
+
+    /**
+     * Convert the specified object into a BigDecimal.
+     *
+     * @param value the value to convert
+     * @return the converted value
+     * @throws ConversionException thrown if the value cannot be converted to a BigDecimal
+     */
+    public static BigDecimal toBigDecimal(Object value) throws ConversionException
+    {
+        Number n = toNumber(value, BigDecimal.class);
+        if (n instanceof BigDecimal)
+        {
+            return (BigDecimal) n;
+        }
+        else
+        {
+            return new BigDecimal(n.doubleValue());
+        }
+    }
+
+    /**
+     * Tries to convert the specified object into a number object. This method
+     * is used by the conversion methods for number types. Note that the return
+     * value is not in always of the specified target class, but only if a new
+     * object has to be created.
+     *
+     * @param value the value to be converted (must not be <b>null</b>)
+     * @param targetClass the target class of the conversion (must be derived
+     * from {@code java.lang.Number})
+     * @return the converted number
+     * @throws ConversionException if the object cannot be converted
+     */
+    static Number toNumber(Object value, Class<?> targetClass) throws ConversionException
+    {
+        if (value instanceof Number)
+        {
+            return (Number) value;
+        }
+        else
+        {
+            String str = value.toString();
+            if (str.startsWith(HEX_PREFIX))
+            {
+                try
+                {
+                    return new BigInteger(str.substring(HEX_PREFIX.length()), HEX_RADIX);
+                }
+                catch (NumberFormatException nex)
+                {
+                    throw new ConversionException("Could not convert " + str
+                            + " to " + targetClass.getName()
+                            + "! Invalid hex number.", nex);
+                }
+            }
+
+            if (str.startsWith(BIN_PREFIX))
+            {
+                try
+                {
+                    return new BigInteger(str.substring(BIN_PREFIX.length()), BIN_RADIX);
+                }
+                catch (NumberFormatException nex)
+                {
+                    throw new ConversionException("Could not convert " + str
+                            + " to " + targetClass.getName()
+                            + "! Invalid binary number.", nex);
+                }
+            }
+
+            try
+            {
+                Constructor<?> constr = targetClass.getConstructor(CONSTR_ARGS);
+                return (Number) constr.newInstance(new Object[]{str});
+            }
+            catch (InvocationTargetException itex)
+            {
+                throw new ConversionException("Could not convert " + str
+                        + " to " + targetClass.getName(), itex
+                        .getTargetException());
+            }
+            catch (Exception ex)
+            {
+                // Treat all possible exceptions the same way
+                throw new ConversionException(
+                        "Conversion error when trying to convert " + str
+                                + " to " + targetClass.getName(), ex);
+            }
+        }
+    }
+
+    /**
+     * Convert the specified object into an URL.
+     *
+     * @param value the value to convert
+     * @return the converted value
+     * @throws ConversionException thrown if the value cannot be converted to an URL
+     */
+    public static URL toURL(Object value) throws ConversionException
+    {
+        if (value instanceof URL)
+        {
+            return (URL) value;
+        }
+        else if (value instanceof String)
+        {
+            try
+            {
+                return new URL((String) value);
+            }
+            catch (MalformedURLException e)
+            {
+                throw new ConversionException("The value " + value + " can't be converted to an URL", e);
+            }
+        }
+        else
+        {
+            throw new ConversionException("The value " + value + " can't be converted to an URL");
+        }
+    }
+
+    /**
+     * Convert the specified object into a Locale.
+     *
+     * @param value the value to convert
+     * @return the converted value
+     * @throws ConversionException thrown if the value cannot be converted to a Locale
+     */
+    public static Locale toLocale(Object value) throws ConversionException
+    {
+        if (value instanceof Locale)
+        {
+            return (Locale) value;
+        }
+        else if (value instanceof String)
+        {
+            List<String> elements = split((String) value, '_');
+            int size = elements.size();
+
+            if (size >= 1 && ((elements.get(0)).length() == 2 || (elements.get(0)).length() == 0))
+            {
+                String language = elements.get(0);
+                String country = (size >= 2) ? elements.get(1) : "";
+                String variant = (size >= 3) ? elements.get(2) : "";
+
+                return new Locale(language, country, variant);
+            }
+            else
+            {
+                throw new ConversionException("The value " + value + " can't be converted to a Locale");
+            }
+        }
+        else
+        {
+            throw new ConversionException("The value " + value + " can't be converted to a Locale");
+        }
+    }
+
+    /**
+     * Split a string on the specified delimiter. To be removed when
+     * commons-lang has a better replacement available (Tokenizer?).
+     *
+     * todo: replace with a commons-lang equivalent
+     *
+     * @param s          the string to split
+     * @param delimiter  the delimiter
+     * @param trim       a flag whether the single elements should be trimmed
+     * @return a list with the single tokens
+     */
+    public static List<String> split(String s, char delimiter, boolean trim)
+    {
+        if (s == null)
+        {
+            return new ArrayList<String>();
+        }
+
+        List<String> list = new ArrayList<String>();
+
+        StringBuilder token = new StringBuilder();
+        int begin = 0;
+        boolean inEscape = false;
+
+        while (begin < s.length())
+        {
+            char c = s.charAt(begin);
+            if (inEscape)
+            {
+                // last character was the escape marker
+                // can current character be escaped?
+                if (c != delimiter && c != LIST_ESC_CHAR)
+                {
+                    // no, also add escape character
+                    token.append(LIST_ESC_CHAR);
+                }
+                token.append(c);
+                inEscape = false;
+            }
+
+            else
+            {
+                if (c == delimiter)
+                {
+                    // found a list delimiter -> add token and resetDefaultFileSystem buffer
+                    String t = token.toString();
+                    if (trim)
+                    {
+                        t = t.trim();
+                    }
+                    list.add(t);
+                    token = new StringBuilder();
+                }
+                else if (c == LIST_ESC_CHAR)
+                {
+                    // eventually escape next character
+                    inEscape = true;
+                }
+                else
+                {
+                    token.append(c);
+                }
+            }
+
+            begin++;
+        }
+
+        // Trailing delimiter?
+        if (inEscape)
+        {
+            token.append(LIST_ESC_CHAR);
+        }
+        // Add last token
+        String t = token.toString();
+        if (trim)
+        {
+            t = t.trim();
+        }
+        list.add(t);
+
+        return list;
+    }
+
+    /**
+     * Split a string on the specified delimiter always trimming the elements.
+     * This is a shortcut for {@code split(s, delimiter, true)}.
+     *
+     * @param s          the string to split
+     * @param delimiter  the delimiter
+     * @return a list with the single tokens
+     */
+    public static List<String> split(String s, char delimiter)
+    {
+        return split(s, delimiter, true);
+    }
+
+    /**
+     * Escapes the delimiters that might be contained in the given string. This
+     * method works like {@link #escapeListDelimiter(String, char)}. In addition,
+     * a single backslash will also be escaped.
+     *
+     * @param s the string with the value
+     * @param delimiter the list delimiter to use
+     * @return the correctly escaped string
+     */
+    public static String escapeDelimiters(String s, char delimiter)
+    {
+        String s1 = StringUtils.replace(s, LIST_ESCAPE, LIST_ESCAPE + LIST_ESCAPE);
+        return escapeListDelimiter(s1, delimiter);
+    }
+
+    /**
+     * Escapes the list delimiter if it is contained in the given string. This
+     * method ensures that list delimiter characters that are part of a
+     * property's value are correctly escaped when a configuration is saved to a
+     * file. Otherwise when loaded again the property will be treated as a list
+     * property.
+     *
+     * @param s the string with the value
+     * @param delimiter the list delimiter to use
+     * @return the escaped string
+     * @since 1.7
+     */
+    public static String escapeListDelimiter(String s, char delimiter)
+    {
+        return StringUtils.replace(s, String.valueOf(delimiter), LIST_ESCAPE
+                + delimiter);
+    }
+
+    /**
+     * Convert the specified object into a Color. If the value is a String,
+     * the format allowed is (#)?[0-9A-F]{6}([0-9A-F]{2})?. Examples:
+     * <ul>
+     *   <li>FF0000 (red)</li>
+     *   <li>0000FFA0 (semi transparent blue)</li>
+     *   <li>#CCCCCC (gray)</li>
+     *   <li>#00FF00A0 (semi transparent green)</li>
+     * </ul>
+     *
+     * @param value the value to convert
+     * @return the converted value
+     * @throws ConversionException thrown if the value cannot be converted to a Color
+     */
+    public static Color toColor(Object value) throws ConversionException
+    {
+        if (value instanceof Color)
+        {
+            return (Color) value;
+        }
+        else if (value instanceof String && !StringUtils.isBlank((String) value))
+        {
+            String color = ((String) value).trim();
+
+            int[] components = new int[3];
+
+            // check the size of the string
+            int minlength = components.length * 2;
+            if (color.length() < minlength)
+            {
+                throw new ConversionException("The value " + value + " can't be converted to a Color");
+            }
+
+            // remove the leading #
+            if (color.startsWith("#"))
+            {
+                color = color.substring(1);
+            }
+
+            try
+            {
+                // parse the components
+                for (int i = 0; i < components.length; i++)
+                {
+                    components[i] = Integer.parseInt(color.substring(2 * i, 2 * i + 2), HEX_RADIX);
+                }
+
+                // parse the transparency
+                int alpha;
+                if (color.length() >= minlength + 2)
+                {
+                    alpha = Integer.parseInt(color.substring(minlength, minlength + 2), HEX_RADIX);
+                }
+                else
+                {
+                    alpha = Color.black.getAlpha();
+                }
+
+                return new Color(components[0], components[1], components[2], alpha);
+            }
+            catch (Exception e)
+            {
+                throw new ConversionException("The value " + value + " can't be converted to a Color", e);
+            }
+        }
+        else
+        {
+            throw new ConversionException("The value " + value + " can't be converted to a Color");
+        }
+    }
+
+    /**
+     * Convert the specified value into an internet address.
+     *
+     * @param value the value to convert
+     * @return the converted value
+     * @throws ConversionException thrown if the value cannot be converted to a InetAddress
+     *
+     * @since 1.5
+     */
+    static InetAddress toInetAddress(Object value) throws ConversionException
+    {
+        if (value instanceof InetAddress)
+        {
+            return (InetAddress) value;
+        }
+        else if (value instanceof String)
+        {
+            try
+            {
+                return InetAddress.getByName((String) value);
+            }
+            catch (UnknownHostException e)
+            {
+                throw new ConversionException("The value " + value + " can't be converted to a InetAddress", e);
+            }
+        }
+        else
+        {
+            throw new ConversionException("The value " + value + " can't be converted to a InetAddress");
+        }
+    }
+
+    /**
+     * Convert the specified value into an email address.
+     *
+     * @param value the value to convert
+     * @return the converted value
+     * @throws ConversionException thrown if the value cannot be converted to an email address
+     *
+     * @since 1.5
+     */
+    static Object toInternetAddress(Object value) throws ConversionException
+    {
+        if (value.getClass().getName().equals(INTERNET_ADDRESS_CLASSNAME))
+        {
+            return value;
+        }
+        else if (value instanceof String)
+        {
+            try
+            {
+                Constructor<?> ctor = Class.forName(INTERNET_ADDRESS_CLASSNAME)
+                        .getConstructor(new Class[] {String.class});
+                return ctor.newInstance(new Object[] {value});
+            }
+            catch (Exception e)
+            {
+                throw new ConversionException("The value " + value + " can't be converted to a InternetAddress", e);
+            }
+        }
+        else
+        {
+            throw new ConversionException("The value " + value + " can't be converted to a InternetAddress");
+        }
+    }
+
+    /**
+     * Calls Class.isEnum() on Java 5, returns false on older JRE.
+     */
+    static boolean isEnum(Class<?> cls)
+    {
+        return cls.isEnum();
+    }
+
+    /**
+     * Convert the specified value into a Java 5 enum.
+     *
+     * @param value the value to convert
+     * @param cls   the type of the enumeration
+     * @return the converted value
+     * @throws ConversionException thrown if the value cannot be converted to an enumeration
+     *
+     * @since 1.5
+     */
+    static <E extends Enum<E>> E toEnum(Object value, Class<E> cls) throws ConversionException
+    {
+        if (value.getClass().equals(cls))
+        {
+            return cls.cast(value);
+        }
+        else if (value instanceof String)
+        {
+            try
+            {
+                return Enum.valueOf(cls, (String) value);
+            }
+            catch (Exception e)
+            {
+                throw new ConversionException("The value " + value + " can't be converted to a " + cls.getName());
+            }
+        }
+        else if (value instanceof Number)
+        {
+            try
+            {
+                E[] enumConstants = cls.getEnumConstants();
+                return enumConstants[((Number) value).intValue()];
+            }
+            catch (Exception e)
+            {
+                throw new ConversionException("The value " + value + " can't be converted to a " + cls.getName());
+            }
+        }
+        else
+        {
+            throw new ConversionException("The value " + value + " can't be converted to a " + cls.getName());
+        }
+    }
+
+    /**
+     * Convert the specified object into a Date.
+     *
+     * @param value  the value to convert
+     * @param format the DateFormat pattern to parse String values
+     * @return the converted value
+     * @throws ConversionException thrown if the value cannot be converted to a Calendar
+     */
+    public static Date toDate(Object value, String format) throws ConversionException
+    {
+        if (value instanceof Date)
+        {
+            return (Date) value;
+        }
+        else if (value instanceof Calendar)
+        {
+            return ((Calendar) value).getTime();
+        }
+        else if (value instanceof String)
+        {
+            try
+            {
+                return new SimpleDateFormat(format).parse((String) value);
+            }
+            catch (ParseException e)
+            {
+                throw new ConversionException("The value " + value + " can't be converted to a Date", e);
+            }
+        }
+        else
+        {
+            throw new ConversionException("The value " + value + " can't be converted to a Date");
+        }
+    }
+
+    /**
+     * Convert the specified object into a Calendar.
+     *
+     * @param value  the value to convert
+     * @param format the DateFormat pattern to parse String values
+     * @return the converted value
+     * @throws ConversionException thrown if the value cannot be converted to a Calendar
+     */
+    public static Calendar toCalendar(Object value, String format) throws ConversionException
+    {
+        if (value instanceof Calendar)
+        {
+            return (Calendar) value;
+        }
+        else if (value instanceof Date)
+        {
+            Calendar calendar = Calendar.getInstance();
+            calendar.setTime((Date) value);
+            return calendar;
+        }
+        else if (value instanceof String)
+        {
+            try
+            {
+                Calendar calendar = Calendar.getInstance();
+                calendar.setTime(new SimpleDateFormat(format).parse((String) value));
+                return calendar;
+            }
+            catch (ParseException e)
+            {
+                throw new ConversionException("The value " + value + " can't be converted to a Calendar", e);
+            }
+        }
+        else
+        {
+            throw new ConversionException("The value " + value + " can't be converted to a Calendar");
+        }
+    }
+
+    /**
+     * Returns an iterator over the simple values of a composite value. This
+     * implementation calls {@link #flatten(Object, char)} and
+     * returns an iterator over the returned collection.
+     *
+     * @param value the value to "split"
+     * @param delimiter the delimiter for String values
+     * @return an iterator for accessing the single values
+     */
+    public static Iterator<?> toIterator(Object value, char delimiter)
+    {
+        return flatten(value, delimiter).iterator();
+    }
+
+    /**
+     * Returns a collection with all values contained in the specified object.
+     * This method is used for instance by the {@code addProperty()}
+     * implementation of the default configurations to gather all values of the
+     * property to add. Depending on the type of the passed in object the
+     * following things happen:
+     * <ul>
+     * <li>Strings are checked for delimiter characters and split if necessary.</li>
+     * <li>For objects implementing the {@code Iterable} interface, the
+     * corresponding {@code Iterator} is obtained, and contained elements
+     * are added to the resulting collection.</li>
+     * <li>Arrays are treated as {@code Iterable} objects.</li>
+     * <li>All other types are directly inserted.</li>
+     * <li>Recursive combinations are supported, e.g. a collection containing
+     * an array that contains strings: The resulting collection will only
+     * contain primitive objects (hence the name "flatten").</li>
+     * </ul>
+     *
+     * @param value the value to be processed
+     * @param delimiter the delimiter for String values
+     * @return a "flat" collection containing all primitive values of
+     *         the passed in object
+     */
+    private static Collection<?> flatten(Object value, char delimiter)
+    {
+        if (value instanceof String)
+        {
+            String s = (String) value;
+            if (s.indexOf(delimiter) > 0)
+            {
+                return split(s, delimiter);
+            }
+        }
+
+        Collection<Object> result = new LinkedList<Object>();
+        if (value instanceof Iterable)
+        {
+            flattenIterator(result, ((Iterable<?>) value).iterator(), delimiter);
+        }
+        else if (value instanceof Iterator)
+        {
+            flattenIterator(result, (Iterator<?>) value, delimiter);
+        }
+        else if (value != null)
+        {
+            if (value.getClass().isArray())
+            {
+                for (int len = Array.getLength(value), idx = 0; idx < len; idx++)
+                {
+                    result.addAll(flatten(Array.get(value, idx), delimiter));
+                }
+            }
+            else
+            {
+                result.add(value);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Flattens the given iterator. For each element in the iteration
+     * {@code flatten()} will be called recursively.
+     *
+     * @param target the target collection
+     * @param it the iterator to process
+     * @param delimiter the delimiter for String values
+     */
+    private static void flattenIterator(Collection<Object> target, Iterator<?> it, char delimiter)
+    {
+        while (it.hasNext())
+        {
+            target.addAll(flatten(it.next(), delimiter));
+        }
+    }
+
+    /**
+     * Performs interpolation of the specified value. This method checks if the
+     * given value contains variables of the form <code>${...}</code>. If
+     * this is the case, all occurrences will be substituted by their current
+     * values.
+     *
+     * @param value the value to be interpolated
+     * @param config the current configuration object
+     * @return the interpolated value
+     */
+    public static Object interpolate(Object value, AbstractConfiguration config)
+    {
+        if (value instanceof String)
+        {
+            return config.getSubstitutor().replace((String) value);
+        }
+        else
+        {
+            return value;
+        }
+    }
+
+    /**
+     * Helper method for converting a value to a constant of an enumeration
+     * class.
+     *
+     * @param enumClass the enumeration class
+     * @param value the value to be converted
+     * @return the converted value
+     */
+    @SuppressWarnings("unchecked")
+    // conversion is safe because we know that the class is an Enum class
+    private static Object convertToEnum(Class<?> enumClass, Object value)
+    {
+        return toEnum(value, enumClass.asSubclass(Enum.class));
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/StrictConfigurationComparator.java b/src/main/java/org/apache/commons/configuration/StrictConfigurationComparator.java
new file mode 100644
index 0000000..472c754
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/StrictConfigurationComparator.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.util.Iterator;
+
+/**
+ * Strict comparator for configurations.
+ *
+ * @since 1.0
+ *
+ * @author <a href="mailto:herve.quiroz at esil.univ-mrs.fr">Herve Quiroz</a>
+ * @author <a href="mailto:shapira at mpi.com">Yoav Shapira</a>
+ * @version $Id: StrictConfigurationComparator.java 1210177 2011-12-04 18:58:19Z oheger $
+ */
+public class StrictConfigurationComparator implements ConfigurationComparator
+{
+    /**
+     * Create a new strict comparator.
+     */
+    public StrictConfigurationComparator()
+    {
+    }
+
+    /**
+     * Compare two configuration objects.
+     *
+     * @param a the first configuration
+     * @param b the second configuration
+     * @return true if keys from a are found in b and keys from b are
+     *         found in a and for each key in a, the corresponding value
+     *         is the sale in for the same key in b
+     */
+    public boolean compare(Configuration a, Configuration b)
+    {
+        if (a == null && b == null)
+        {
+            return true;
+        }
+        else if (a == null || b == null)
+        {
+            return false;
+        }
+
+        for (Iterator<String> keys = a.getKeys(); keys.hasNext();)
+        {
+            String key = keys.next();
+            Object value = a.getProperty(key);
+            if (!value.equals(b.getProperty(key)))
+            {
+                return false;
+            }
+        }
+
+        for (Iterator<String> keys = b.getKeys(); keys.hasNext();)
+        {
+            String key = keys.next();
+            Object value = b.getProperty(key);
+            if (!value.equals(a.getProperty(key)))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/SubnodeConfiguration.java b/src/main/java/org/apache/commons/configuration/SubnodeConfiguration.java
new file mode 100644
index 0000000..de2cff8
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/SubnodeConfiguration.java
@@ -0,0 +1,347 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
+import org.apache.commons.configuration.reloading.Reloadable;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+
+/**
+ * <p>
+ * A specialized hierarchical configuration class that wraps a single node of
+ * its parent configuration.
+ * </p>
+ * <p>
+ * Configurations of this type are initialized with a parent configuration and a
+ * configuration node of this configuration. This node becomes the root node of
+ * the subnode configuration. All property accessor methods are evaluated
+ * relative to this root node. A good use case for a
+ * {@code SubnodeConfiguration} is when multiple properties from a
+ * specific sub tree of the whole configuration need to be accessed. Then a
+ * {@code SubnodeConfiguration} can be created with the parent node of
+ * the affected sub tree as root node. This allows for simpler property keys and
+ * is also more efficient.
+ * </p>
+ * <p>
+ * A subnode configuration and its parent configuration operate on the same
+ * hierarchy of configuration nodes. So if modifications are performed at the
+ * subnode configuration, these changes are immediately visible in the parent
+ * configuration. Analogously will updates of the parent configuration affect
+ * the subnode configuration if the sub tree spanned by the subnode
+ * configuration's root node is involved.
+ * </p>
+ * <p>
+ * There are however changes at the parent configuration, which cause the
+ * subnode configuration to become detached. An example for such a change is a
+ * reload operation of a file-based configuration, which replaces all nodes of
+ * the parent configuration. The subnode configuration per default still
+ * references the old nodes. Another example are list structures: a subnode
+ * configuration can be created to point on the <em>i</em>th element of the
+ * list. Now list elements can be added or removed, so that the list elements'
+ * indices change. In such a scenario the subnode configuration would always
+ * point to the same list element, regardless of its current index.
+ * </p>
+ * <p>
+ * To solve these problems and make a subnode configuration aware of
+ * such structural changes of its parent, it is possible to associate a
+ * subnode configuration with a configuration key. This can be done by calling
+ * the {@code setSubnodeKey()} method. If here a key is set, the subnode
+ * configuration will evaluate it on each access, thus ensuring that it is
+ * always in sync with its parent. In this mode the subnode configuration really
+ * behaves like a live-view on its parent. The price for this is a decreased
+ * performance because now an additional evaluation has to be performed on each
+ * property access. So this mode should only be used if necessary; if for
+ * instance a subnode configuration is only used for a temporary convenient
+ * access to a complex configuration, there is no need to make it aware for
+ * structural changes of its parent. If a subnode configuration is created
+ * using the {@link HierarchicalConfiguration#configurationAt(String, boolean)
+ * configurationAt()} method of {@code HierarchicalConfiguration}
+ * (which should be the preferred way), with an additional boolean parameter it
+ * can be specified whether the resulting subnode configuration should be
+ * aware of structural changes or not. Then the configuration key will be
+ * automatically set.
+ * </p>
+ * <p>
+ * <em>Note:</em> At the moment support for creating a subnode configuration
+ * that is aware of structural changes of its parent from another subnode
+ * configuration (a "sub subnode configuration") is limited. This only works if
+ * <ol><li>the subnode configuration that serves as the parent for the new
+ * subnode configuration is itself associated with a configuration key and</li>
+ * <li>the key passed in to create the new subnode configuration is not too
+ * complex (if configuration keys are used that contain indices, a corresponding
+ * key that is valid from the parent configuration's point of view cannot be
+ * constructed).</li></ol>
+ * </p>
+ * <p>
+ * When a subnode configuration is created, it inherits the settings of its
+ * parent configuration, e.g. some flags like the
+ * {@code throwExceptionOnMissing} flag or the settings for handling list
+ * delimiters) or the expression engine. If these settings are changed later in
+ * either the subnode or the parent configuration, the changes are not visible
+ * for each other. So you could create a subnode configuration, change its
+ * expression engine without affecting the parent configuration.
+ * </p>
+ * <p>
+ * From its purpose this class is quite similar to
+ * {@link SubsetConfiguration}. The difference is that a subset
+ * configuration of a hierarchical configuration may combine multiple
+ * configuration nodes from different sub trees of the configuration, while all
+ * nodes in a subnode configuration belong to the same sub tree. If an
+ * application can live with this limitation, it is recommended to use this
+ * class instead of {@code SubsetConfiguration} because creating a subset
+ * configuration is more expensive than creating a subnode configuration.
+ * </p>
+ *
+ * @since 1.3
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: SubnodeConfiguration.java 1210178 2011-12-04 18:58:51Z oheger $
+ */
+public class SubnodeConfiguration extends HierarchicalReloadableConfiguration
+{
+    /**
+     * The serial version UID.
+     */
+    private static final long serialVersionUID = 3105734147019386480L;
+
+    /** Stores the parent configuration. */
+    private HierarchicalConfiguration parent;
+
+    /** Stores the key that was used to construct this configuration.*/
+    private String subnodeKey;
+
+    /**
+     * Creates a new instance of {@code SubnodeConfiguration} and
+     * initializes it with the parent configuration and the new root node.
+     *
+     * @param parent the parent configuration
+     * @param root the root node of this subnode configuration
+     */
+    public SubnodeConfiguration(HierarchicalConfiguration parent, ConfigurationNode root)
+    {
+        super(parent instanceof Reloadable ? ((Reloadable) parent).getReloadLock() : null);
+        if (parent == null)
+        {
+            throw new IllegalArgumentException(
+                    "Parent configuration must not be null!");
+        }
+        if (root == null)
+        {
+            throw new IllegalArgumentException("Root node must not be null!");
+        }
+
+        setRootNode(root);
+        this.parent = parent;
+        initFromParent(parent);
+    }
+
+    /**
+     * Returns the parent configuration of this subnode configuration.
+     *
+     * @return the parent configuration
+     */
+    public HierarchicalConfiguration getParent()
+    {
+        return parent;
+    }
+
+    /**
+     * Returns the key that was used to construct this configuration. If here a
+     * non-<b>null</b> value is returned, the subnode configuration will
+     * always check its parent for structural changes and reconstruct itself if
+     * necessary.
+     *
+     * @return the key for selecting this configuration's root node
+     * @since 1.5
+     */
+    public String getSubnodeKey()
+    {
+        return subnodeKey;
+    }
+
+    /**
+     * Sets the key to the root node of this subnode configuration. If here a
+     * key is set, the subnode configuration will behave like a live-view on its
+     * parent for this key. See the class comment for more details.
+     *
+     * @param subnodeKey the key used to construct this configuration
+     * @since 1.5
+     */
+    public void setSubnodeKey(String subnodeKey)
+    {
+        this.subnodeKey = subnodeKey;
+    }
+
+    /**
+     * Returns the root node for this configuration. If a subnode key is set,
+     * this implementation re-evaluates this key to find out if this subnode
+     * configuration needs to be reconstructed. This ensures that the subnode
+     * configuration is always synchronized with its parent configuration.
+     *
+     * @return the root node of this configuration
+     * @since 1.5
+     * @see #setSubnodeKey(String)
+     */
+    @Override
+    public ConfigurationNode getRootNode()
+    {
+        if (getSubnodeKey() != null)
+        {
+            try
+            {
+                List<ConfigurationNode> nodes = getParent().fetchNodeList(getSubnodeKey());
+                if (nodes.size() != 1)
+                {
+                    // key is invalid, so detach this subnode configuration
+                    setSubnodeKey(null);
+                }
+                else
+                {
+                    ConfigurationNode currentRoot = nodes.get(0);
+                    if (currentRoot != super.getRootNode())
+                    {
+                        // the root node was changed due to a change of the
+                        // parent
+                        fireEvent(EVENT_SUBNODE_CHANGED, null, null, true);
+                        setRootNode(currentRoot);
+                        fireEvent(EVENT_SUBNODE_CHANGED, null, null, false);
+                    }
+                    return currentRoot;
+                }
+            }
+            catch (Exception ex)
+            {
+                // Evaluation of the key caused an exception. Probably the
+                // expression engine has changed on the parent. Detach this
+                // configuration, there is not much we can do about this.
+                setSubnodeKey(null);
+            }
+        }
+
+        return super.getRootNode(); // use stored root node
+    }
+
+    /**
+     * Returns a hierarchical configuration object for the given sub node.
+     * This implementation will ensure that the returned
+     * {@code SubnodeConfiguration} object will have the same parent than
+     * this object.
+     *
+     * @param node the sub node, for which the configuration is to be created
+     * @return a hierarchical configuration for this sub node
+     */
+    @Override
+    protected SubnodeConfiguration createSubnodeConfiguration(ConfigurationNode node)
+    {
+        SubnodeConfiguration result = new SubnodeConfiguration(getParent(), node);
+        getParent().registerSubnodeConfiguration(result);
+        return result;
+    }
+
+    /**
+     * Returns a hierarchical configuration object for the given sub node that
+     * is aware of structural changes of its parent. Works like the method with
+     * the same name, but also sets the subnode key for the new subnode
+     * configuration, so it can check whether the parent has been changed. This
+     * only works if this subnode configuration has itself a valid subnode key.
+     * So if a subnode configuration that should be aware of structural changes
+     * is created from an already existing subnode configuration, this subnode
+     * configuration must also be aware of such changes.
+     *
+     * @param node the sub node, for which the configuration is to be created
+     * @param subnodeKey the construction key
+     * @return a hierarchical configuration for this sub node
+     * @since 1.5
+     */
+    @Override
+    protected SubnodeConfiguration createSubnodeConfiguration(
+            ConfigurationNode node, String subnodeKey)
+    {
+        SubnodeConfiguration result = createSubnodeConfiguration(node);
+
+        if (getSubnodeKey() != null)
+        {
+            // construct the correct subnode key
+            // determine path to root node
+            List<ConfigurationNode> lstPathToRoot = new ArrayList<ConfigurationNode>();
+            ConfigurationNode top = super.getRootNode();
+            ConfigurationNode nd = node;
+            while (nd != top)
+            {
+                lstPathToRoot.add(nd);
+                nd = nd.getParentNode();
+            }
+
+            // construct the keys for the nodes on this path
+            Collections.reverse(lstPathToRoot);
+            String key = getSubnodeKey();
+            for (ConfigurationNode pathNode : lstPathToRoot)
+            {
+                key = getParent().getExpressionEngine().nodeKey(pathNode, key);
+            }
+            result.setSubnodeKey(key);
+        }
+
+        return result;
+    }
+
+    /**
+     * Creates a new node. This task is delegated to the parent.
+     *
+     * @param name the node's name
+     * @return the new node
+     */
+    @Override
+    protected Node createNode(String name)
+    {
+        return getParent().createNode(name);
+    }
+
+    /**
+     * Initializes this subnode configuration from the given parent
+     * configuration. This method is called by the constructor. It will copy
+     * many settings from the parent.
+     *
+     * @param parentConfig the parent configuration
+     */
+    protected void initFromParent(HierarchicalConfiguration parentConfig)
+    {
+        setExpressionEngine(parentConfig.getExpressionEngine());
+        setListDelimiter(parentConfig.getListDelimiter());
+        setDelimiterParsingDisabled(parentConfig.isDelimiterParsingDisabled());
+        setThrowExceptionOnMissing(parentConfig.isThrowExceptionOnMissing());
+    }
+
+    /**
+     * Creates a ConfigurationInterpolator with a chain to the parent's
+     * interpolator.
+     *
+     * @return the new interpolator
+     */
+    @Override
+    protected ConfigurationInterpolator createInterpolator()
+    {
+        ConfigurationInterpolator interpolator = super.createInterpolator();
+        interpolator.setParentInterpolator(getParent().getInterpolator());
+        return interpolator;
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/SubsetConfiguration.java b/src/main/java/org/apache/commons/configuration/SubsetConfiguration.java
new file mode 100644
index 0000000..62f943d
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/SubsetConfiguration.java
@@ -0,0 +1,386 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.util.Iterator;
+
+import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
+
+/**
+ * <p>A subset of another configuration. The new Configuration object contains
+ * every key from the parent Configuration that starts with prefix. The prefix
+ * is removed from the keys in the subset.</p>
+ * <p>It is usually not necessary to use this class directly. Instead the
+ * {@link Configuration#subset(String)} method should be used,
+ * which will return a correctly initialized instance.</p>
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: SubsetConfiguration.java 1210202 2011-12-04 20:30:46Z oheger $
+ */
+public class SubsetConfiguration extends AbstractConfiguration
+{
+    /** The parent configuration. */
+    protected Configuration parent;
+
+    /** The prefix used to select the properties. */
+    protected String prefix;
+
+    /** The prefix delimiter */
+    protected String delimiter;
+
+    /**
+     * Create a subset of the specified configuration
+     *
+     * @param parent The parent configuration
+     * @param prefix The prefix used to select the properties
+     */
+    public SubsetConfiguration(Configuration parent, String prefix)
+    {
+        this.parent = parent;
+        this.prefix = prefix;
+    }
+
+    /**
+     * Create a subset of the specified configuration
+     *
+     * @param parent    The parent configuration
+     * @param prefix    The prefix used to select the properties
+     * @param delimiter The prefix delimiter
+     */
+    public SubsetConfiguration(Configuration parent, String prefix, String delimiter)
+    {
+        this.parent = parent;
+        this.prefix = prefix;
+        this.delimiter = delimiter;
+    }
+
+    /**
+     * Return the key in the parent configuration associated to the specified
+     * key in this subset.
+     *
+     * @param key The key in the subset.
+     * @return the key as to be used by the parent
+     */
+    protected String getParentKey(String key)
+    {
+        if ("".equals(key) || key == null)
+        {
+            return prefix;
+        }
+        else
+        {
+            return delimiter == null ? prefix + key : prefix + delimiter + key;
+        }
+    }
+
+    /**
+     * Return the key in the subset configuration associated to the specified
+     * key in the parent configuration.
+     *
+     * @param key The key in the parent configuration.
+     * @return the key in the context of this subset configuration
+     */
+    protected String getChildKey(String key)
+    {
+        if (!key.startsWith(prefix))
+        {
+            throw new IllegalArgumentException("The parent key '" + key + "' is not in the subset.");
+        }
+        else
+        {
+            String modifiedKey = null;
+            if (key.length() == prefix.length())
+            {
+                modifiedKey = "";
+            }
+            else
+            {
+                int i = prefix.length() + (delimiter != null ? delimiter.length() : 0);
+                modifiedKey = key.substring(i);
+            }
+
+            return modifiedKey;
+        }
+    }
+
+    /**
+     * Return the parent configuration for this subset.
+     *
+     * @return the parent configuration
+     */
+    public Configuration getParent()
+    {
+        return parent;
+    }
+
+    /**
+     * Return the prefix used to select the properties in the parent configuration.
+     *
+     * @return the prefix used by this subset
+     */
+    public String getPrefix()
+    {
+        return prefix;
+    }
+
+    /**
+     * Set the prefix used to select the properties in the parent configuration.
+     *
+     * @param prefix the prefix
+     */
+    public void setPrefix(String prefix)
+    {
+        this.prefix = prefix;
+    }
+
+    @Override
+    public Configuration subset(String prefix)
+    {
+        return parent.subset(getParentKey(prefix));
+    }
+
+    public boolean isEmpty()
+    {
+        return !getKeys().hasNext();
+    }
+
+    public boolean containsKey(String key)
+    {
+        return parent.containsKey(getParentKey(key));
+    }
+
+    @Override
+    public void addPropertyDirect(String key, Object value)
+    {
+        parent.addProperty(getParentKey(key), value);
+    }
+
+    @Override
+    protected void clearPropertyDirect(String key)
+    {
+        parent.clearProperty(getParentKey(key));
+    }
+
+    public Object getProperty(String key)
+    {
+        return parent.getProperty(getParentKey(key));
+    }
+
+    @Override
+    public Iterator<String> getKeys(String prefix)
+    {
+        return new SubsetIterator(parent.getKeys(getParentKey(prefix)));
+    }
+
+    public Iterator<String> getKeys()
+    {
+        return new SubsetIterator(parent.getKeys(prefix));
+    }
+
+    @Override
+    protected Object interpolate(Object base)
+    {
+        if (delimiter == null && "".equals(prefix))
+        {
+            return super.interpolate(base);
+        }
+        else
+        {
+            SubsetConfiguration config = new SubsetConfiguration(parent, "");
+            ConfigurationInterpolator interpolator = config.getInterpolator();
+            getInterpolator().registerLocalLookups(interpolator);
+            if (parent instanceof AbstractConfiguration)
+            {
+                interpolator.setParentInterpolator(((AbstractConfiguration) parent).getInterpolator());
+            }
+            return config.interpolate(base);
+        }
+    }
+
+    @Override
+    protected String interpolate(String base)
+    {
+        return super.interpolate(base);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Change the behavior of the parent configuration if it supports this feature.
+     */
+    @Override
+    public void setThrowExceptionOnMissing(boolean throwExceptionOnMissing)
+    {
+        if (parent instanceof AbstractConfiguration)
+        {
+            ((AbstractConfiguration) parent).setThrowExceptionOnMissing(throwExceptionOnMissing);
+        }
+        else
+        {
+            super.setThrowExceptionOnMissing(throwExceptionOnMissing);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * The subset inherits this feature from its parent if it supports this feature.
+     */
+    @Override
+    public boolean isThrowExceptionOnMissing()
+    {
+        if (parent instanceof AbstractConfiguration)
+        {
+            return ((AbstractConfiguration) parent).isThrowExceptionOnMissing();
+        }
+        else
+        {
+            return super.isThrowExceptionOnMissing();
+        }
+    }
+
+    /**
+     * Returns the list delimiter. This property will be fetched from the parent
+     * configuration if supported.
+     *
+     * @return the list delimiter
+     * @since 1.4
+     */
+    @Override
+    public char getListDelimiter()
+    {
+        return (parent instanceof AbstractConfiguration) ? ((AbstractConfiguration) parent)
+                .getListDelimiter()
+                : super.getListDelimiter();
+    }
+
+    /**
+     * Sets the list delimiter. If the parent configuration supports this
+     * feature, the delimiter will be set at the parent.
+     *
+     * @param delim the new list delimiter
+     * @since 1.4
+     */
+    @Override
+    public void setListDelimiter(char delim)
+    {
+        if (parent instanceof AbstractConfiguration)
+        {
+            ((AbstractConfiguration) parent).setListDelimiter(delim);
+        }
+        else
+        {
+            super.setListDelimiter(delim);
+        }
+    }
+
+    /**
+     * Returns a flag whether string properties should be checked for list
+     * delimiter characters. This implementation ensures that this flag is kept
+     * in sync with the parent configuration if this object supports this
+     * feature.
+     *
+     * @return the delimiter parsing disabled flag
+     * @since 1.4
+     */
+    @Override
+    public boolean isDelimiterParsingDisabled()
+    {
+        return (parent instanceof AbstractConfiguration) ? ((AbstractConfiguration) parent)
+                .isDelimiterParsingDisabled()
+                : super.isDelimiterParsingDisabled();
+    }
+
+    /**
+     * Sets a flag whether list parsing is disabled. This implementation will
+     * also set the flag at the parent configuration if this object supports
+     * this feature.
+     *
+     * @param delimiterParsingDisabled the delimiter parsing disabled flag
+     * @since 1.4
+     */
+    @Override
+    public void setDelimiterParsingDisabled(boolean delimiterParsingDisabled)
+    {
+        if (parent instanceof AbstractConfiguration)
+        {
+            ((AbstractConfiguration) parent)
+                    .setDelimiterParsingDisabled(delimiterParsingDisabled);
+        }
+        else
+        {
+            super.setDelimiterParsingDisabled(delimiterParsingDisabled);
+        }
+    }
+
+
+    /**
+     * A specialized iterator to be returned by the {@code getKeys()}
+     * methods. This implementation wraps an iterator from the parent
+     * configuration. The keys returned by this iterator are correspondingly
+     * transformed.
+     */
+    private class SubsetIterator implements Iterator<String>
+    {
+        /** Stores the wrapped iterator. */
+        private final Iterator<String> parentIterator;
+
+        /**
+         * Creates a new instance of {@code SubsetIterator} and
+         * initializes it with the parent iterator.
+         *
+         * @param it the iterator of the parent configuration
+         */
+        public SubsetIterator(Iterator<String> it)
+        {
+            parentIterator = it;
+        }
+
+        /**
+         * Checks whether there are more elements. Delegates to the parent
+         * iterator.
+         *
+         * @return a flag whether there are more elements
+         */
+        public boolean hasNext()
+        {
+            return parentIterator.hasNext();
+        }
+
+        /**
+         * Returns the next element in the iteration. This is the next key from
+         * the parent configuration, transformed to correspond to the point of
+         * view of this subset configuration.
+         *
+         * @return the next element
+         */
+        public String next()
+        {
+            return getChildKey(parentIterator.next());
+        }
+
+        /**
+         * Removes the current element from the iteration. Delegates to the
+         * parent iterator.
+         */
+        public void remove()
+        {
+            parentIterator.remove();
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/SystemConfiguration.java b/src/main/java/org/apache/commons/configuration/SystemConfiguration.java
new file mode 100644
index 0000000..91620f5
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/SystemConfiguration.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.util.Iterator;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * A configuration based on the system properties.
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: SystemConfiguration.java 1210204 2011-12-04 20:38:02Z oheger $
+ * @since 1.1
+ */
+public class SystemConfiguration extends MapConfiguration
+{
+    /** The logger. */
+    private static Log log = LogFactory.getLog(SystemConfiguration.class);
+
+    /**
+     * Create a Configuration based on the system properties.
+     *
+     * @see System#getProperties
+     */
+    public SystemConfiguration()
+    {
+        super(System.getProperties());
+    }
+
+    /**
+     * The method allows system properties to be set from a property file.
+     * @param fileName The name of the property file.
+     * @throws Exception if an error occurs.
+     * @since 1.6
+     */
+    public static void setSystemProperties(String fileName) throws Exception
+    {
+        setSystemProperties(null, fileName);
+    }
+
+    /**
+     * The method allows system properties to be set from a property file.
+     * @param basePath The base path to look for the property file.
+     * @param fileName The name of the property file.
+     * @throws Exception if an error occurs.
+     * @since 1.6
+     */
+    public static void setSystemProperties(String basePath, String fileName) throws Exception
+    {
+        PropertiesConfiguration config = fileName.endsWith(".xml")
+            ? new XMLPropertiesConfiguration() : new PropertiesConfiguration();
+        if (basePath != null)
+        {
+            config.setBasePath(basePath);
+        }
+        config.setFileName(fileName);
+        config.load();
+        setSystemProperties(config);
+    }
+
+    /**
+     * Set System properties from a configuration file.
+     * @param systemConfig The configuration containing the properties to be set.
+     * @since 1.6
+     */
+    public static void setSystemProperties(PropertiesConfiguration systemConfig)
+    {
+        Iterator<String> iter = systemConfig.getKeys();
+        while (iter.hasNext())
+        {
+            String key = iter.next();
+            String value = (String) systemConfig.getProperty(key);
+            if (log.isDebugEnabled())
+            {
+                log.debug("Setting system property " + key + " to " + value);
+            }
+            System.setProperty(key, value);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/VFSFileSystem.java b/src/main/java/org/apache/commons/configuration/VFSFileSystem.java
new file mode 100644
index 0000000..0bf9147
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/VFSFileSystem.java
@@ -0,0 +1,409 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.util.Map;
+
+import org.apache.commons.vfs2.FileContent;
+import org.apache.commons.vfs2.FileName;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemConfigBuilder;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileSystemManager;
+import org.apache.commons.vfs2.FileSystemOptions;
+import org.apache.commons.vfs2.FileType;
+import org.apache.commons.vfs2.VFS;
+import org.apache.commons.vfs2.provider.UriParser;
+
+/**
+ * FileSystem that uses Commons VFS
+ * @since 1.7
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a>
+ * @version $Id: VFSFileSystem.java 1210205 2011-12-04 20:38:19Z oheger $
+ */
+public class VFSFileSystem extends DefaultFileSystem
+{
+    public VFSFileSystem()
+    {
+    }
+
+    @Override
+    public InputStream getInputStream(String basePath, String fileName)
+        throws ConfigurationException
+    {
+        try
+        {
+            FileSystemManager manager = VFS.getManager();
+            FileName path;
+            if (basePath != null)
+            {
+                FileName base = manager.resolveURI(basePath);
+                path = manager.resolveName(base, fileName);
+            }
+            else
+            {
+                FileName file = manager.resolveURI(fileName);
+                FileName base = file.getParent();
+                path = manager.resolveName(base, file.getBaseName());
+            }
+            FileSystemOptions opts = getOptions(path.getScheme());
+            FileObject file = (opts == null) ? manager.resolveFile(path.getURI())
+                    : manager.resolveFile(path.getURI(), opts);
+            FileContent content = file.getContent();
+            if (content == null)
+            {
+                String msg = "Cannot access content of " + file.getName().getFriendlyURI();
+                throw new ConfigurationException(msg);
+            }
+            return content.getInputStream();
+        }
+        catch (ConfigurationException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new ConfigurationException("Unable to load the configuration file " + fileName, e);
+        }
+    }
+
+    @Override
+    public InputStream getInputStream(URL url) throws ConfigurationException
+    {
+        FileObject file;
+        try
+        {
+            FileSystemOptions opts = getOptions(url.getProtocol());
+            file = (opts == null) ? VFS.getManager().resolveFile(url.toString())
+                    : VFS.getManager().resolveFile(url.toString(), opts);
+            if (file.getType() != FileType.FILE)
+            {
+                throw new ConfigurationException("Cannot load a configuration from a directory");
+            }
+            FileContent content = file.getContent();
+            if (content == null)
+            {
+                String msg = "Cannot access content of " + file.getName().getFriendlyURI();
+                throw new ConfigurationException(msg);
+            }
+            return content.getInputStream();
+        }
+        catch (FileSystemException fse)
+        {
+            String msg = "Unable to access " + url.toString();
+            throw new ConfigurationException(msg, fse);
+        }
+    }
+
+    @Override
+    public OutputStream getOutputStream(URL url) throws ConfigurationException
+    {
+        try
+        {
+            FileSystemOptions opts = getOptions(url.getProtocol());
+            FileSystemManager fsManager = VFS.getManager();
+            FileObject file = (opts == null) ? fsManager.resolveFile(url.toString())
+                    : fsManager.resolveFile(url.toString(), opts);
+            // throw an exception if the target URL is a directory
+            if (file == null || file.getType() == FileType.FOLDER)
+            {
+                throw new ConfigurationException("Cannot save a configuration to a directory");
+            }
+            FileContent content = file.getContent();
+
+            if (content == null)
+            {
+                throw new ConfigurationException("Cannot access content of " + url);
+            }
+            return content.getOutputStream();
+        }
+        catch (FileSystemException fse)
+        {
+            throw new ConfigurationException("Unable to access " + url, fse);
+        }
+    }
+
+    @Override
+    public String getPath(File file, URL url, String basePath, String fileName)
+    {
+        if (file != null)
+        {
+            return super.getPath(file, url, basePath, fileName);
+        }
+        try
+        {
+            FileSystemManager fsManager = VFS.getManager();
+            if (url != null)
+            {
+                FileName name = fsManager.resolveURI(url.toString());
+                if (name != null)
+                {
+                    return name.toString();
+                }
+            }
+
+            if (UriParser.extractScheme(fileName) != null)
+            {
+                return fileName;
+            }
+            else if (basePath != null)
+            {
+                FileName base = fsManager.resolveURI(basePath);
+                return fsManager.resolveName(base, fileName).getURI();
+            }
+            else
+            {
+                FileName name = fsManager.resolveURI(fileName);
+                FileName base = name.getParent();
+                return fsManager.resolveName(base, name.getBaseName()).getURI();
+            }
+        }
+        catch (FileSystemException fse)
+        {
+            fse.printStackTrace();
+            return null;
+        }
+    }
+
+    @Override
+    public String getBasePath(String path)
+    {
+        if (UriParser.extractScheme(path) == null)
+        {
+            return super.getBasePath(path);
+        }
+        try
+        {
+            FileSystemManager fsManager = VFS.getManager();
+            FileName name = fsManager.resolveURI(path);
+            return name.getParent().getURI();
+        }
+        catch (FileSystemException fse)
+        {
+            fse.printStackTrace();
+            return null;
+        }
+    }
+
+    @Override
+    public String getFileName(String path)
+    {
+        if (UriParser.extractScheme(path) == null)
+        {
+            return super.getFileName(path);
+        }
+        try
+        {
+            FileSystemManager fsManager = VFS.getManager();
+            FileName name = fsManager.resolveURI(path);
+            return name.getBaseName();
+        }
+        catch (FileSystemException fse)
+        {
+            fse.printStackTrace();
+            return null;
+        }
+    }
+
+    @Override
+    public URL getURL(String basePath, String file) throws MalformedURLException
+    {
+        if ((basePath != null && UriParser.extractScheme(basePath) == null)
+            || (basePath == null && UriParser.extractScheme(file) == null))
+        {
+            return super.getURL(basePath, file);
+        }
+        try
+        {
+            FileSystemManager fsManager = VFS.getManager();
+
+            FileName path;
+            if (basePath != null && UriParser.extractScheme(file) == null)
+            {
+                FileName base = fsManager.resolveURI(basePath);
+                path = fsManager.resolveName(base, file);
+            }
+            else
+            {
+                path = fsManager.resolveURI(file);
+            }
+
+            URLStreamHandler handler = new VFSURLStreamHandler(path);
+            return new URL(null, path.getURI(), handler);
+        }
+        catch (FileSystemException fse)
+        {
+            throw new ConfigurationRuntimeException("Could not parse basePath: " + basePath
+                + " and fileName: " + file, fse);
+        }
+    }
+
+    @Override
+    public URL locateFromURL(String basePath, String fileName)
+    {
+        String fileScheme = UriParser.extractScheme(fileName);
+
+        // Use DefaultFileSystem if basePath and fileName don't have a scheme.
+        if ((basePath == null || UriParser.extractScheme(basePath) == null) && fileScheme == null)
+        {
+            return super.locateFromURL(basePath, fileName);
+        }
+        try
+        {
+            FileSystemManager fsManager = VFS.getManager();
+
+            FileObject file;
+            // Only use the base path if the file name doesn't have a scheme.
+            if (basePath != null && fileScheme == null)
+            {
+                String scheme = UriParser.extractScheme(basePath);
+                FileSystemOptions opts = (scheme != null) ? getOptions(scheme) : null;
+                FileObject base = (opts == null) ? fsManager.resolveFile(basePath)
+                        : fsManager.resolveFile(basePath, opts);
+                if (base.getType() == FileType.FILE)
+                {
+                    base = base.getParent();
+                }
+
+                file = fsManager.resolveFile(base, fileName);
+            }
+            else
+            {
+                FileSystemOptions opts = (fileScheme != null) ? getOptions(fileScheme) : null;
+                file = (opts == null) ? fsManager.resolveFile(fileName)
+                        : fsManager.resolveFile(fileName, opts);
+            }
+
+            if (!file.exists())
+            {
+                return null;
+            }
+            FileName path = file.getName();
+            URLStreamHandler handler = new VFSURLStreamHandler(path);
+            return new URL(null, path.getURI(), handler);
+        }
+        catch (FileSystemException fse)
+        {
+            return null;
+        }
+        catch (MalformedURLException ex)
+        {
+            return null;
+        }
+    }
+
+    private FileSystemOptions getOptions(String scheme)
+    {
+        FileSystemOptions opts = new FileSystemOptions();
+        FileSystemConfigBuilder builder;
+        try
+        {
+            builder = VFS.getManager().getFileSystemConfigBuilder(scheme);
+        }
+        catch (Exception ex)
+        {
+            return null;
+        }
+        FileOptionsProvider provider = getFileOptionsProvider();
+        if (provider != null)
+        {
+            Map<String, Object> map = provider.getOptions();
+            if (map == null)
+            {
+                return null;
+            }
+            int count = 0;
+            for (Map.Entry<String, Object> entry : map.entrySet())
+            {
+                try
+                {
+                    String key = entry.getKey();
+                    if (FileOptionsProvider.CURRENT_USER.equals(key))
+                    {
+                        key = "creatorName";
+                    }
+                    setProperty(builder, opts, key, entry.getValue());
+                    ++count;
+                }
+                catch (Exception ex)
+                {
+                    // Ignore an incorrect property.
+                    continue;
+                }
+            }
+            if (count > 0)
+            {
+                return opts;
+            }
+        }
+        return null;
+
+    }
+
+    private void setProperty(FileSystemConfigBuilder builder, FileSystemOptions options,
+                             String key, Object value)
+    {
+        String methodName = "set" + key.substring(0, 1).toUpperCase() + key.substring(1);
+        Class<?>[] paramTypes = new Class<?>[2];
+        paramTypes[0] = FileSystemOptions.class;
+        paramTypes[1] = value.getClass();
+
+        try
+        {
+            Method method = builder.getClass().getMethod(methodName, paramTypes);
+            Object[] params = new Object[2];
+            params[0] = options;
+            params[1] = value;
+            method.invoke(builder, params);
+        }
+        catch (Exception ex)
+        {
+            return;
+        }
+
+    }
+
+    /**
+     * Stream handler required to create URL.
+     */
+    private static class VFSURLStreamHandler extends URLStreamHandler
+    {
+        /** The Protocol used */
+        private final String protocol;
+
+        public VFSURLStreamHandler(FileName file)
+        {
+            this.protocol = file.getScheme();
+        }
+
+        @Override
+        protected URLConnection openConnection(URL url) throws IOException
+        {
+            throw new IOException("VFS URLs can only be used with VFS APIs");
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/VerifiableOutputStream.java b/src/main/java/org/apache/commons/configuration/VerifiableOutputStream.java
new file mode 100644
index 0000000..cfc53fa
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/VerifiableOutputStream.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import java.io.OutputStream;
+import java.io.IOException;
+
+/**
+ * OutputStream that can be checked for errors after it is written to.
+ * @since 1.7
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a>
+ */
+abstract class VerifiableOutputStream extends OutputStream
+{
+    public abstract void verify() throws IOException;
+}
diff --git a/src/main/java/org/apache/commons/configuration/XMLConfiguration.java b/src/main/java/org/apache/commons/configuration/XMLConfiguration.java
new file mode 100644
index 0000000..d6b4df1
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/XMLConfiguration.java
@@ -0,0 +1,1650 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.configuration.resolver.DefaultEntityResolver;
+import org.apache.commons.configuration.resolver.EntityRegistry;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.LogFactory;
+import org.w3c.dom.Attr;
+import org.w3c.dom.CDATASection;
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Text;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * <p>A specialized hierarchical configuration class that is able to parse XML
+ * documents.</p>
+ *
+ * <p>The parsed document will be stored keeping its structure. The class also
+ * tries to preserve as much information from the loaded XML document as
+ * possible, including comments and processing instructions. These will be
+ * contained in documents created by the {@code save()} methods, too.</p>
+ *
+ * <p>Like other file based configuration classes this class maintains the name
+ * and path to the loaded configuration file. These properties can be altered
+ * using several setter methods, but they are not modified by {@code save()}
+ * and {@code load()} methods. If XML documents contain relative paths to
+ * other documents (e.g. to a DTD), these references are resolved based on the
+ * path set for this configuration.</p>
+ *
+ * <p>By inheriting from {@link AbstractConfiguration} this class
+ * provides some extended functionality, e.g. interpolation of property values.
+ * Like in {@link PropertiesConfiguration} property values can
+ * contain delimiter characters (the comma ',' per default) and are then split
+ * into multiple values. This works for XML attributes and text content of
+ * elements as well. The delimiter can be escaped by a backslash. As an example
+ * consider the following XML fragment:</p>
+ *
+ * <p>
+ * <pre>
+ * <config>
+ *   <array>10,20,30,40</array>
+ *   <scalar>3\,1415</scalar>
+ *   <cite text="To be or not to be\, this is the question!"/>
+ * </config>
+ * </pre>
+ * </p>
+ * <p>Here the content of the {@code array} element will be split at
+ * the commas, so the {@code array} key will be assigned 4 values. In the
+ * {@code scalar} property and the {@code text} attribute of the
+ * {@code cite} element the comma is escaped, so that no splitting is
+ * performed.</p>
+ *
+ * <p>The configuration API allows setting multiple values for a single attribute,
+ * e.g. something like the following is legal (assuming that the default
+ * expression engine is used):
+ * <pre>
+ * XMLConfiguration config = new XMLConfiguration();
+ * config.addProperty("test.dir[@name]", "C:\\Temp\\");
+ * config.addProperty("test.dir[@name]", "D:\\Data\\");
+ * </pre></p>
+ *
+ * <p>Because in XML such a constellation is not directly supported (an attribute
+ * can appear only once for a single element), the values are concatenated to a
+ * single value. If delimiter parsing is enabled (refer to the
+ * {@link #setDelimiterParsingDisabled(boolean)} method), the
+ * current list delimiter character will be used as separator. Otherwise the
+ * pipe symbol ("|") will be used for this purpose. No matter which character is
+ * used as delimiter, it can always be escaped with a backslash. A backslash
+ * itself can also be escaped with another backslash. Consider the following
+ * example fragment from a configuration file:
+ * <pre>
+ * <directories names="C:\Temp\\|D:\Data\"/>
+ * </pre>
+ * Here the backslash after Temp is escaped. This is necessary because it
+ * would escape the list delimiter (the pipe symbol assuming that list delimiter
+ * parsing is disabled) otherwise. So this attribute would have two values.</p>
+ *
+ * <p>Note: You should ensure that the <em>delimiter parsing disabled</em>
+ * property is always consistent when you load and save a configuration file.
+ * Otherwise the values of properties can become corrupted.</p>
+ *
+ * <p>Whitespace in the content of XML documents is trimmed per default. In most
+ * cases this is desired. However, sometimes whitespace is indeed important and
+ * should be treated as part of the value of a property as in the following
+ * example:
+ * <pre>
+ *   <indent>    </indent>
+ * </pre></p>
+ *
+ * <p>Per default the spaces in the {@code indent} element will be trimmed
+ * resulting in an empty element. To tell {@code XMLConfiguration} that
+ * spaces are relevant the {@code xml:space} attribute can be used, which is
+ * defined in the <a href="http://www.w3.org/TR/REC-xml/#sec-white-space">XML
+ * specification</a>. This will look as follows:
+ * <pre>
+ *   <indent <strong>xml:space="preserve"</strong>>    </indent>
+ * </pre>
+ * The value of the {@code indent} property will now contain the spaces.</p>
+ *
+ * <p>{@code XMLConfiguration} implements the {@link FileConfiguration}
+ * interface and thus provides full support for loading XML documents from
+ * different sources like files, URLs, or streams. A full description of these
+ * features can be found in the documentation of
+ * {@link AbstractFileConfiguration}.</p>
+ *
+ * <p><em>Note:</em>Configuration objects of this type can be read concurrently
+ * by multiple threads. However if one of these threads modifies the object,
+ * synchronization has to be performed manually.</p>
+ *
+ * @since commons-configuration 1.0
+ *
+ * @author Jörg Schaible
+ * @version $Id: XMLConfiguration.java 1534429 2013-10-22 00:45:36Z henning $
+ */
+public class XMLConfiguration extends AbstractHierarchicalFileConfiguration
+    implements EntityResolver, EntityRegistry
+{
+    /**
+     * The serial version UID.
+     */
+    private static final long serialVersionUID = 2453781111653383552L;
+
+    /** Constant for the default root element name. */
+    private static final String DEFAULT_ROOT_NAME = "configuration";
+
+    /** Constant for the name of the space attribute.*/
+    private static final String ATTR_SPACE = "xml:space";
+
+    /** Constant for the xml:space value for preserving whitespace.*/
+    private static final String VALUE_PRESERVE = "preserve";
+
+    /** Constant for the delimiter for multiple attribute values.*/
+    private static final char ATTR_VALUE_DELIMITER = '|';
+
+    /** Schema Langauge key for the parser */
+    private static final String JAXP_SCHEMA_LANGUAGE =
+        "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
+
+    /** Schema Language for the parser */
+    private static final String W3C_XML_SCHEMA =
+        "http://www.w3.org/2001/XMLSchema";
+
+    /** The document from this configuration's data source. */
+    private Document document;
+
+    /** Stores the name of the root element. */
+    private String rootElementName;
+
+    /** Stores the public ID from the DOCTYPE.*/
+    private String publicID;
+
+    /** Stores the system ID from the DOCTYPE.*/
+    private String systemID;
+
+    /** Stores the document builder that should be used for loading.*/
+    private DocumentBuilder documentBuilder;
+
+    /** Stores a flag whether DTD or Schema validation should be performed.*/
+    private boolean validating;
+
+    /** Stores a flag whether DTD or Schema validation is used */
+    private boolean schemaValidation;
+
+    /** A flag whether attribute splitting is disabled.*/
+    private boolean attributeSplittingDisabled;
+
+    /** The EntityResolver to use */
+    private EntityResolver entityResolver = new DefaultEntityResolver();
+
+    /**
+     * Creates a new instance of {@code XMLConfiguration}.
+     */
+    public XMLConfiguration()
+    {
+        super();
+        setLogger(LogFactory.getLog(XMLConfiguration.class));
+    }
+
+    /**
+     * Creates a new instance of {@code XMLConfiguration} and copies the
+     * content of the passed in configuration into this object. Note that only
+     * the data of the passed in configuration will be copied. If, for instance,
+     * the other configuration is a {@code XMLConfiguration}, too,
+     * things like comments or processing instructions will be lost.
+     *
+     * @param c the configuration to copy
+     * @since 1.4
+     */
+    public XMLConfiguration(HierarchicalConfiguration c)
+    {
+        super(c);
+        clearReferences(getRootNode());
+        setRootElementName(getRootNode().getName());
+        setLogger(LogFactory.getLog(XMLConfiguration.class));
+    }
+
+    /**
+     * Creates a new instance of{@code XMLConfiguration}. The
+     * configuration is loaded from the specified file
+     *
+     * @param fileName the name of the file to load
+     * @throws ConfigurationException if the file cannot be loaded
+     */
+    public XMLConfiguration(String fileName) throws ConfigurationException
+    {
+        super(fileName);
+        setLogger(LogFactory.getLog(XMLConfiguration.class));
+    }
+
+    /**
+     * Creates a new instance of {@code XMLConfiguration}.
+     * The configuration is loaded from the specified file.
+     *
+     * @param file the file
+     * @throws ConfigurationException if an error occurs while loading the file
+     */
+    public XMLConfiguration(File file) throws ConfigurationException
+    {
+        super(file);
+        setLogger(LogFactory.getLog(XMLConfiguration.class));
+    }
+
+    /**
+     * Creates a new instance of {@code XMLConfiguration}.
+     * The configuration is loaded from the specified URL.
+     *
+     * @param url the URL
+     * @throws ConfigurationException if loading causes an error
+     */
+    public XMLConfiguration(URL url) throws ConfigurationException
+    {
+        super(url);
+        setLogger(LogFactory.getLog(XMLConfiguration.class));
+    }
+
+    /**
+     * Returns the name of the root element. If this configuration was loaded
+     * from a XML document, the name of this document's root element is
+     * returned. Otherwise it is possible to set a name for the root element
+     * that will be used when this configuration is stored.
+     *
+     * @return the name of the root element
+     */
+    public String getRootElementName()
+    {
+        if (getDocument() == null)
+        {
+            return (rootElementName == null) ? DEFAULT_ROOT_NAME : rootElementName;
+        }
+        else
+        {
+            return getDocument().getDocumentElement().getNodeName();
+        }
+    }
+
+    /**
+     * Sets the name of the root element. This name is used when this
+     * configuration object is stored in an XML file. Note that setting the name
+     * of the root element works only if this configuration has been newly
+     * created. If the configuration was loaded from an XML file, the name
+     * cannot be changed and an {@code UnsupportedOperationException}
+     * exception is thrown. Whether this configuration has been loaded from an
+     * XML document or not can be found out using the {@code getDocument()}
+     * method.
+     *
+     * @param name the name of the root element
+     */
+    public void setRootElementName(String name)
+    {
+        if (getDocument() != null)
+        {
+            throw new UnsupportedOperationException("The name of the root element "
+                    + "cannot be changed when loaded from an XML document!");
+        }
+        rootElementName = name;
+        getRootNode().setName(name);
+    }
+
+    /**
+     * Returns the {@code DocumentBuilder} object that is used for
+     * loading documents. If no specific builder has been set, this method
+     * returns <b>null</b>.
+     *
+     * @return the {@code DocumentBuilder} for loading new documents
+     * @since 1.2
+     */
+    public DocumentBuilder getDocumentBuilder()
+    {
+        return documentBuilder;
+    }
+
+    /**
+     * Sets the {@code DocumentBuilder} object to be used for loading
+     * documents. This method makes it possible to specify the exact document
+     * builder. So an application can create a builder, configure it for its
+     * special needs, and then pass it to this method.
+     *
+     * @param documentBuilder the document builder to be used; if undefined, a
+     * default builder will be used
+     * @since 1.2
+     */
+    public void setDocumentBuilder(DocumentBuilder documentBuilder)
+    {
+        this.documentBuilder = documentBuilder;
+    }
+
+    /**
+     * Returns the public ID of the DOCTYPE declaration from the loaded XML
+     * document. This is <b>null</b> if no document has been loaded yet or if
+     * the document does not contain a DOCTYPE declaration with a public ID.
+     *
+     * @return the public ID
+     * @since 1.3
+     */
+    public String getPublicID()
+    {
+        return publicID;
+    }
+
+    /**
+     * Sets the public ID of the DOCTYPE declaration. When this configuration is
+     * saved, a DOCTYPE declaration will be constructed that contains this
+     * public ID.
+     *
+     * @param publicID the public ID
+     * @since 1.3
+     */
+    public void setPublicID(String publicID)
+    {
+        this.publicID = publicID;
+    }
+
+    /**
+     * Returns the system ID of the DOCTYPE declaration from the loaded XML
+     * document. This is <b>null</b> if no document has been loaded yet or if
+     * the document does not contain a DOCTYPE declaration with a system ID.
+     *
+     * @return the system ID
+     * @since 1.3
+     */
+    public String getSystemID()
+    {
+        return systemID;
+    }
+
+    /**
+     * Sets the system ID of the DOCTYPE declaration. When this configuration is
+     * saved, a DOCTYPE declaration will be constructed that contains this
+     * system ID.
+     *
+     * @param systemID the system ID
+     * @since 1.3
+     */
+    public void setSystemID(String systemID)
+    {
+        this.systemID = systemID;
+    }
+
+    /**
+     * Returns the value of the validating flag.
+     *
+     * @return the validating flag
+     * @since 1.2
+     */
+    public boolean isValidating()
+    {
+        return validating;
+    }
+
+    /**
+     * Sets the value of the validating flag. This flag determines whether
+     * DTD/Schema validation should be performed when loading XML documents. This
+     * flag is evaluated only if no custom {@code DocumentBuilder} was set.
+     *
+     * @param validating the validating flag
+     * @since 1.2
+     */
+    public void setValidating(boolean validating)
+    {
+        if (!schemaValidation)
+        {
+            this.validating = validating;
+        }
+    }
+
+
+    /**
+     * Returns the value of the schemaValidation flag.
+     *
+     * @return the schemaValidation flag
+     * @since 1.7
+     */
+    public boolean isSchemaValidation()
+    {
+        return schemaValidation;
+    }
+
+    /**
+     * Sets the value of the schemaValidation flag. This flag determines whether
+     * DTD or Schema validation should be used. This
+     * flag is evaluated only if no custom {@code DocumentBuilder} was set.
+     * If set to true the XML document must contain a schemaLocation definition
+     * that provides resolvable hints to the required schemas.
+     *
+     * @param schemaValidation the validating flag
+     * @since 1.7
+     */
+    public void setSchemaValidation(boolean schemaValidation)
+    {
+        this.schemaValidation = schemaValidation;
+        if (schemaValidation)
+        {
+            this.validating = true;
+        }
+    }
+
+    /**
+     * Sets a new EntityResolver. Setting this will cause RegisterEntityId to have no
+     * effect.
+     * @param resolver The EntityResolver to use.
+     * @since 1.7
+     */
+    public void setEntityResolver(EntityResolver resolver)
+    {
+        this.entityResolver = resolver;
+    }
+
+    /**
+     * Returns the EntityResolver.
+     * @return The EntityResolver.
+     * @since 1.7
+     */
+    public EntityResolver getEntityResolver()
+    {
+        return this.entityResolver;
+    }
+
+    /**
+     * Returns the flag whether attribute splitting is disabled.
+     *
+     * @return the flag whether attribute splitting is disabled
+     * @see #setAttributeSplittingDisabled(boolean)
+     * @since 1.6
+     */
+    public boolean isAttributeSplittingDisabled()
+    {
+        return attributeSplittingDisabled;
+    }
+
+    /**
+     * <p>
+     * Sets a flag whether attribute splitting is disabled.
+     * </p>
+     * <p>
+     * The Configuration API allows adding multiple values to an attribute. This
+     * is problematic when storing the configuration because in XML an attribute
+     * can appear only once with a single value. To solve this problem, per
+     * default multiple attribute values are concatenated using a special
+     * separator character and split again when the configuration is loaded. The
+     * separator character is either the list delimiter character (see
+     * {@link #setListDelimiter(char)}) or the pipe symbol ("|") if
+     * list delimiter parsing is disabled.
+     * </p>
+     * <p>
+     * In some constellations the splitting of attribute values can have
+     * undesired effects, especially if list delimiter parsing is disabled and
+     * attributes may contain the "|" character. In these cases it is
+     * possible to disable the attribute splitting mechanism by calling this
+     * method with a boolean value set to <b>false</b>. If attribute splitting
+     * is disabled, the values of attributes will not be processed, but stored
+     * as configuration properties exactly as they are returned by the XML
+     * parser.
+     * </p>
+     * <p>
+     * Note that in this mode multiple attribute values cannot be handled
+     * correctly. It is possible to create a {@code XMLConfiguration}
+     * object, add multiple values to an attribute and save it. When the
+     * configuration is loaded again and attribute splitting is disabled, the
+     * attribute will only have a single value, which is the concatenation of
+     * all values set before. So it lies in the responsibility of the
+     * application to carefully set the values of attributes.
+     * </p>
+     * <p>
+     * As is true for the {@link #setDelimiterParsingDisabled(boolean)} method,
+     * this method must be called before the configuration is loaded. So it
+     * can't be used together with one of the constructors expecting the
+     * specification of the file to load. Instead the default constructor has to
+     * be used, then {@code setAttributeSplittingDisabled(false)} has to be
+     * called, and finally the configuration can be loaded using one of its
+     * {@code load()} methods.
+     * </p>
+     *
+     * @param attributeSplittingDisabled <b>true</b> for disabling attribute
+     *        splitting, <b>false</b> for enabling it
+     * @see #setDelimiterParsingDisabled(boolean)
+     * @since 1.6
+     */
+    public void setAttributeSplittingDisabled(boolean attributeSplittingDisabled)
+    {
+        this.attributeSplittingDisabled = attributeSplittingDisabled;
+    }
+
+    /**
+     * Returns the XML document this configuration was loaded from. The return
+     * value is <b>null</b> if this configuration was not loaded from a XML
+     * document.
+     *
+     * @return the XML document this configuration was loaded from
+     */
+    public Document getDocument()
+    {
+        return document;
+    }
+
+    /**
+     * Removes all properties from this configuration. If this configuration
+     * was loaded from a file, the associated DOM document is also cleared.
+     */
+    @Override
+    public void clear()
+    {
+        super.clear();
+        setRoot(new Node());
+        document = null;
+    }
+
+    /**
+     * Initializes this configuration from an XML document.
+     *
+     * @param document the document to be parsed
+     * @param elemRefs a flag whether references to the XML elements should be set
+     */
+    public void initProperties(Document document, boolean elemRefs)
+    {
+        if (document.getDoctype() != null)
+        {
+            setPublicID(document.getDoctype().getPublicId());
+            setSystemID(document.getDoctype().getSystemId());
+        }
+
+        constructHierarchy(getRoot(), document.getDocumentElement(), elemRefs, true);
+        getRootNode().setName(document.getDocumentElement().getNodeName());
+        if (elemRefs)
+        {
+            getRoot().setReference(document.getDocumentElement());
+        }
+    }
+
+    /**
+     * Helper method for building the internal storage hierarchy. The XML
+     * elements are transformed into node objects.
+     *
+     * @param node the actual node
+     * @param element the actual XML element
+     * @param elemRefs a flag whether references to the XML elements should be
+     *        set
+     * @param trim a flag whether the text content of elements should be
+     *        trimmed; this controls the whitespace handling
+     * @return a map with all attribute values extracted for the current node;
+     *         this map also contains the value of the trim flag for this node
+     *         under the key {@value #ATTR_SPACE}
+     */
+    private Map<String, Collection<String>> constructHierarchy(Node node,
+            Element element, boolean elemRefs, boolean trim)
+    {
+        boolean trimFlag = shouldTrim(element, trim);
+        Map<String, Collection<String>> attributes =
+                processAttributes(node, element, elemRefs);
+        attributes.put(ATTR_SPACE, Collections.singleton(String.valueOf(trimFlag)));
+        StringBuilder buffer = new StringBuilder();
+        NodeList list = element.getChildNodes();
+        for (int i = 0; i < list.getLength(); i++)
+        {
+            org.w3c.dom.Node w3cNode = list.item(i);
+            if (w3cNode instanceof Element)
+            {
+                Element child = (Element) w3cNode;
+                Node childNode = new XMLNode(child.getTagName(),
+                        elemRefs ? child : null);
+                Map<String, Collection<String>> attrmap =
+                        constructHierarchy(childNode, child, elemRefs, trimFlag);
+                node.addChild(childNode);
+                Collection<String> attrSpace = attrmap.remove(ATTR_SPACE);
+
+                Boolean childTrim = CollectionUtils.isEmpty(attrSpace)
+                    ? Boolean.FALSE
+                    : Boolean.valueOf(attrSpace.iterator().next());
+
+                handleDelimiters(node, childNode, childTrim.booleanValue(), attrmap);
+            }
+            else if (w3cNode instanceof Text)
+            {
+                Text data = (Text) w3cNode;
+                buffer.append(data.getData());
+            }
+        }
+
+        String text = determineValue(node, buffer.toString(), trimFlag);
+        if (text.length() > 0 || (!node.hasChildren() && node != getRoot()))
+        {
+            node.setValue(text);
+        }
+        return attributes;
+    }
+
+    /**
+     * Determines the value of a configuration node. This method mainly checks
+     * whether the text value is to be trimmed or not. This is normally defined
+     * by the trim flag. However, if the node has children and its content is
+     * only whitespace, then it makes no sense to store any value; this would
+     * only scramble layout when the configuration is saved again.
+     *
+     * @param node the current {@code ConfigurationNode}
+     * @param content the text content of this node
+     * @param trimFlag the trim flag
+     * @return the value to be stored for this node
+     */
+    private static String determineValue(ConfigurationNode node,
+            String content, boolean trimFlag)
+    {
+        boolean shouldTrim =
+                trimFlag
+                        || (StringUtils.isBlank(content) && node
+                                .getChildrenCount() > 0);
+        return shouldTrim ? content.trim() : content;
+    }
+
+    /**
+     * Helper method for constructing node objects for the attributes of the
+     * given XML element.
+     *
+     * @param node the current node
+     * @param element the actual XML element
+     * @param elemRefs a flag whether references to the XML elements should be set
+     * @return a map with all attribute values extracted for the current node
+     */
+    private Map<String, Collection<String>> processAttributes(Node node,
+            Element element, boolean elemRefs)
+    {
+        NamedNodeMap attributes = element.getAttributes();
+        Map<String, Collection<String>> attrmap = new HashMap<String, Collection<String>>();
+
+        for (int i = 0; i < attributes.getLength(); ++i)
+        {
+            org.w3c.dom.Node w3cNode = attributes.item(i);
+            if (w3cNode instanceof Attr)
+            {
+                Attr attr = (Attr) w3cNode;
+                List<String> values;
+                if (isAttributeSplittingDisabled())
+                {
+                    values = Collections.singletonList(attr.getValue());
+                }
+                else
+                {
+                    values = PropertyConverter.split(attr.getValue(),
+                            isDelimiterParsingDisabled() ? ATTR_VALUE_DELIMITER
+                                    : getListDelimiter());
+                }
+
+                appendAttributes(node, element, elemRefs, attr.getName(), values);
+                attrmap.put(attr.getName(), values);
+            }
+        }
+
+        return attrmap;
+    }
+
+    /**
+     * Adds attribute nodes to the given node. For each attribute value, a new
+     * attribute node is created and added as child to the current node.
+     *
+     * @param node the current node
+     * @param element the corresponding XML element
+     * @param attr the name of the attribute
+     * @param values the attribute values
+     */
+    private void appendAttributes(Node node, Element element, boolean elemRefs,
+            String attr, Collection<String> values)
+    {
+        for (String value : values)
+        {
+            Node child = new XMLNode(attr, elemRefs ? element : null);
+            child.setValue(value);
+            node.addAttribute(child);
+        }
+    }
+
+    /**
+     * Deals with elements whose value is a list. In this case multiple child
+     * elements must be added.
+     *
+     * @param parent the parent element
+     * @param child the child element
+     * @param trim flag whether texts of elements should be trimmed
+     * @param attrmap a map with the attributes of the current node
+     */
+    private void handleDelimiters(Node parent, Node child, boolean trim,
+            Map<String, Collection<String>> attrmap)
+    {
+        if (child.getValue() != null)
+        {
+            List<String> values;
+            if (isDelimiterParsingDisabled())
+            {
+                values = new ArrayList<String>();
+                values.add(child.getValue().toString());
+            }
+            else
+            {
+                values = PropertyConverter.split(child.getValue().toString(),
+                    getListDelimiter(), trim);
+            }
+
+            if (values.size() > 1)
+            {
+                Iterator<String> it = values.iterator();
+                // Create new node for the original child's first value
+                Node c = createNode(child.getName());
+                c.setValue(it.next());
+                // Copy original attributes to the new node
+                for (ConfigurationNode ndAttr : child.getAttributes())
+                {
+                    ndAttr.setReference(null);
+                    c.addAttribute(ndAttr);
+                }
+                parent.remove(child);
+                parent.addChild(c);
+
+                // add multiple new children
+                while (it.hasNext())
+                {
+                    c = new XMLNode(child.getName(), null);
+                    c.setValue(it.next());
+                    for (Map.Entry<String, Collection<String>> e : attrmap
+                            .entrySet())
+                    {
+                        appendAttributes(c, null, false, e.getKey(),
+                                e.getValue());
+                    }
+                    parent.addChild(c);
+                }
+            }
+            else if (values.size() == 1)
+            {
+                // we will have to replace the value because it might
+                // contain escaped delimiters
+                child.setValue(values.get(0));
+            }
+        }
+    }
+
+    /**
+     * Checks whether the content of the current XML element should be trimmed.
+     * This method checks whether a {@code xml:space} attribute is
+     * present and evaluates its value. See <a
+     * href="http://www.w3.org/TR/REC-xml/#sec-white-space">
+     * http://www.w3.org/TR/REC-xml/#sec-white-space</a> for more details.
+     *
+     * @param element the current XML element
+     * @param currentTrim the current trim flag
+     * @return a flag whether the content of this element should be trimmed
+     */
+    private boolean shouldTrim(Element element, boolean currentTrim)
+    {
+        Attr attr = element.getAttributeNode(ATTR_SPACE);
+
+        if (attr == null)
+        {
+            return currentTrim;
+        }
+        else
+        {
+            return !VALUE_PRESERVE.equals(attr.getValue());
+        }
+    }
+
+    /**
+     * Creates the {@code DocumentBuilder} to be used for loading files.
+     * This implementation checks whether a specific
+     * {@code DocumentBuilder} has been set. If this is the case, this
+     * one is used. Otherwise a default builder is created. Depending on the
+     * value of the validating flag this builder will be a validating or a non
+     * validating {@code DocumentBuilder}.
+     *
+     * @return the {@code DocumentBuilder} for loading configuration
+     * files
+     * @throws ParserConfigurationException if an error occurs
+     * @since 1.2
+     */
+    protected DocumentBuilder createDocumentBuilder()
+            throws ParserConfigurationException
+    {
+        if (getDocumentBuilder() != null)
+        {
+            return getDocumentBuilder();
+        }
+        else
+        {
+            DocumentBuilderFactory factory = DocumentBuilderFactory
+                    .newInstance();
+            if (isValidating())
+            {
+                factory.setValidating(true);
+                if (isSchemaValidation())
+                {
+                    factory.setNamespaceAware(true);
+                    factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
+                }
+            }
+
+            DocumentBuilder result = factory.newDocumentBuilder();
+            result.setEntityResolver(this.entityResolver);
+
+            if (isValidating())
+            {
+                // register an error handler which detects validation errors
+                result.setErrorHandler(new DefaultHandler()
+                {
+                    @Override
+                    public void error(SAXParseException ex) throws SAXException
+                    {
+                        throw ex;
+                    }
+                });
+            }
+            return result;
+        }
+    }
+
+    /**
+     * Creates a DOM document from the internal tree of configuration nodes.
+     *
+     * @return the new document
+     * @throws ConfigurationException if an error occurs
+     */
+    protected Document createDocument() throws ConfigurationException
+    {
+        try
+        {
+            if (document == null)
+            {
+                DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+                Document newDocument = builder.newDocument();
+                Element rootElem = newDocument.createElement(getRootElementName());
+                newDocument.appendChild(rootElem);
+                document = newDocument;
+            }
+
+            XMLBuilderVisitor builder = new XMLBuilderVisitor(document,
+                    isDelimiterParsingDisabled() ? (char) 0 : getListDelimiter(),
+                    isAttributeSplittingDisabled());
+            builder.processDocument(getRoot());
+            initRootElementText(document, getRootNode().getValue());
+            return document;
+        }
+        catch (DOMException domEx)
+        {
+            throw new ConfigurationException(domEx);
+        }
+        catch (ParserConfigurationException pex)
+        {
+            throw new ConfigurationException(pex);
+        }
+    }
+
+    /**
+     * Sets the text of the root element of a newly created XML Document.
+     *
+     * @param doc the document
+     * @param value the new text to be set
+     */
+    private void initRootElementText(Document doc, Object value)
+    {
+        Element elem = doc.getDocumentElement();
+        NodeList children = elem.getChildNodes();
+
+        // Remove all existing text nodes
+        for (int i = 0; i < children.getLength(); i++)
+        {
+            org.w3c.dom.Node nd = children.item(i);
+            if (nd.getNodeType() == org.w3c.dom.Node.TEXT_NODE)
+            {
+                elem.removeChild(nd);
+            }
+        }
+
+        if (value != null)
+        {
+            // Add a new text node
+            elem.appendChild(doc.createTextNode(String.valueOf(value)));
+        }
+    }
+
+    /**
+     * Creates a new node object. This implementation returns an instance of the
+     * {@code XMLNode} class.
+     *
+     * @param name the node's name
+     * @return the new node
+     */
+    @Override
+    protected Node createNode(String name)
+    {
+        return new XMLNode(name, null);
+    }
+
+    /**
+     * Loads the configuration from the given input stream.
+     *
+     * @param in the input stream
+     * @throws ConfigurationException if an error occurs
+     */
+    @Override
+    public void load(InputStream in) throws ConfigurationException
+    {
+        load(new InputSource(in));
+    }
+
+    /**
+     * Load the configuration from the given reader.
+     * Note that the {@code clear()} method is not called, so
+     * the properties contained in the loaded file will be added to the
+     * actual set of properties.
+     *
+     * @param in An InputStream.
+     *
+     * @throws ConfigurationException if an error occurs
+     */
+    public void load(Reader in) throws ConfigurationException
+    {
+        load(new InputSource(in));
+    }
+
+    /**
+     * Loads a configuration file from the specified input source.
+     * @param source the input source
+     * @throws ConfigurationException if an error occurs
+     */
+    private void load(InputSource source) throws ConfigurationException
+    {
+        try
+        {
+            URL sourceURL = getDelegate().getURL();
+            if (sourceURL != null)
+            {
+                source.setSystemId(sourceURL.toString());
+            }
+
+            DocumentBuilder builder = createDocumentBuilder();
+            Document newDocument = builder.parse(source);
+            Document oldDocument = document;
+            document = null;
+            initProperties(newDocument, oldDocument == null);
+            document = (oldDocument == null) ? newDocument : oldDocument;
+        }
+        catch (SAXParseException spe)
+        {
+            throw new ConfigurationException("Error parsing " + source.getSystemId(), spe);
+        }
+        catch (Exception e)
+        {
+            this.getLogger().debug("Unable to load the configuraton", e);
+            throw new ConfigurationException("Unable to load the configuration", e);
+        }
+    }
+
+    /**
+     * Saves the configuration to the specified writer.
+     *
+     * @param writer the writer used to save the configuration
+     * @throws ConfigurationException if an error occurs
+     */
+    public void save(Writer writer) throws ConfigurationException
+    {
+        try
+        {
+            Transformer transformer = createTransformer();
+            Source source = new DOMSource(createDocument());
+            Result result = new StreamResult(writer);
+            transformer.transform(source, result);
+        }
+        catch (TransformerException e)
+        {
+            throw new ConfigurationException("Unable to save the configuration", e);
+        }
+        catch (TransformerFactoryConfigurationError e)
+        {
+            throw new ConfigurationException("Unable to save the configuration", e);
+        }
+    }
+
+    /**
+     * Validate the document against the Schema.
+     * @throws ConfigurationException if the validation fails.
+     */
+    public void validate() throws ConfigurationException
+    {
+        try
+        {
+            Transformer transformer = createTransformer();
+            Source source = new DOMSource(createDocument());
+            StringWriter writer = new StringWriter();
+            Result result = new StreamResult(writer);
+            transformer.transform(source, result);
+            Reader reader = new StringReader(writer.getBuffer().toString());
+            DocumentBuilder builder = createDocumentBuilder();
+            builder.parse(new InputSource(reader));
+        }
+        catch (SAXException e)
+        {
+            throw new ConfigurationException("Validation failed", e);
+        }
+        catch (IOException e)
+        {
+            throw new ConfigurationException("Validation failed", e);
+        }
+        catch (TransformerException e)
+        {
+            throw new ConfigurationException("Validation failed", e);
+        }
+        catch (ParserConfigurationException pce)
+        {
+            throw new ConfigurationException("Validation failed", pce);
+        }
+    }
+
+    /**
+     * Creates and initializes the transformer used for save operations. This
+     * base implementation initializes all of the default settings like
+     * indention mode and the DOCTYPE. Derived classes may overload this method
+     * if they have specific needs.
+     *
+     * @return the transformer to use for a save operation
+     * @throws TransformerException if an error occurs
+     * @since 1.3
+     */
+    protected Transformer createTransformer() throws TransformerException
+    {
+        Transformer transformer = TransformerFactory.newInstance()
+                .newTransformer();
+
+        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+        if (getEncoding() != null)
+        {
+            transformer.setOutputProperty(OutputKeys.ENCODING, getEncoding());
+        }
+        if (getPublicID() != null)
+        {
+            transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC,
+                    getPublicID());
+        }
+        if (getSystemID() != null)
+        {
+            transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,
+                    getSystemID());
+        }
+
+        return transformer;
+    }
+
+    /**
+     * Creates a copy of this object. The new configuration object will contain
+     * the same properties as the original, but it will lose any connection to a
+     * source document (if one exists). This is to avoid race conditions if both
+     * the original and the copy are modified and then saved.
+     *
+     * @return the copy
+     */
+    @Override
+    public Object clone()
+    {
+        XMLConfiguration copy = (XMLConfiguration) super.clone();
+
+        // clear document related properties
+        copy.document = null;
+        copy.setDelegate(copy.createDelegate());
+        // clear all references in the nodes, too
+        clearReferences(copy.getRootNode());
+
+        return copy;
+    }
+
+    /**
+     * Creates the file configuration delegate for this object. This implementation
+     * will return an instance of a class derived from {@code FileConfigurationDelegate}
+     * that deals with some specialties of {@code XMLConfiguration}.
+     * @return the delegate for this object
+     */
+    @Override
+    protected FileConfigurationDelegate createDelegate()
+    {
+        return new XMLFileConfigurationDelegate();
+    }
+
+    /**
+     * Adds a collection of nodes directly to this configuration. This
+     * implementation ensures that the nodes to be added are of the correct node
+     * type (they have to be converted to {@code XMLNode} if necessary).
+     *
+     * @param key the key where the nodes are to be added
+     * @param nodes the collection with the new nodes
+     * @since 1.5
+     */
+    @Override
+    public void addNodes(String key, Collection<? extends ConfigurationNode> nodes)
+    {
+        if (nodes != null && !nodes.isEmpty())
+        {
+            Collection<XMLNode> xmlNodes;
+            xmlNodes = new ArrayList<XMLNode>(nodes.size());
+            for (ConfigurationNode node : nodes)
+            {
+                xmlNodes.add(convertToXMLNode(node));
+            }
+            super.addNodes(key, xmlNodes);
+        }
+        else
+        {
+            super.addNodes(key, nodes);
+        }
+    }
+
+    /**
+     * Converts the specified node into a {@code XMLNode} if necessary.
+     * This is required for nodes that are directly added, e.g. by
+     * {@code addNodes()}. If the passed in node is already an instance
+     * of {@code XMLNode}, it is directly returned, and conversion
+     * stops. Otherwise a new {@code XMLNode} is created, and the
+     * children are also converted.
+     *
+     * @param node the node to be converted
+     * @return the converted node
+     */
+    private XMLNode convertToXMLNode(ConfigurationNode node)
+    {
+        if (node instanceof XMLNode)
+        {
+            return (XMLNode) node;
+        }
+
+        XMLNode nd = (XMLNode) createNode(node.getName());
+        nd.setValue(node.getValue());
+        nd.setAttribute(node.isAttribute());
+        for (ConfigurationNode child : node.getChildren())
+        {
+            nd.addChild(convertToXMLNode(child));
+        }
+        for (ConfigurationNode attr : node.getAttributes())
+        {
+            nd.addAttribute(convertToXMLNode(attr));
+        }
+        return nd;
+    }
+
+    /**
+     * <p>
+     * Registers the specified DTD URL for the specified public identifier.
+     * </p>
+     * <p>
+     * {@code XMLConfiguration} contains an internal
+     * {@code EntityResolver} implementation. This maps
+     * {@code PUBLICID}'s to URLs (from which the resource will be
+     * loaded). A common use case for this method is to register local URLs
+     * (possibly computed at runtime by a class loader) for DTDs. This allows
+     * the performance advantage of using a local version without having to
+     * ensure every {@code SYSTEM} URI on every processed XML document is
+     * local. This implementation provides only basic functionality. If more
+     * sophisticated features are required, using
+     * {@link #setDocumentBuilder(DocumentBuilder)} to set a custom
+     * {@code DocumentBuilder} (which also can be initialized with a
+     * custom {@code EntityResolver}) is recommended.
+     * </p>
+     * <p>
+     * <strong>Note:</strong> This method will have no effect when a custom
+     * {@code DocumentBuilder} has been set. (Setting a custom
+     * {@code DocumentBuilder} overrides the internal implementation.)
+     * </p>
+     * <p>
+     * <strong>Note:</strong> This method must be called before the
+     * configuration is loaded. So the default constructor of
+     * {@code XMLConfiguration} should be used, the location of the
+     * configuration file set, {@code registerEntityId()} called, and
+     * finally the {@code load()} method can be invoked.
+     * </p>
+     *
+     * @param publicId Public identifier of the DTD to be resolved
+     * @param entityURL The URL to use for reading this DTD
+     * @throws IllegalArgumentException if the public ID is undefined
+     * @since 1.5
+     */
+    public void registerEntityId(String publicId, URL entityURL)
+    {
+        if (entityResolver instanceof EntityRegistry)
+        {
+            ((EntityRegistry) entityResolver).registerEntityId(publicId, entityURL);
+        }
+    }
+
+    /**
+     * Resolves the requested external entity. This is the default
+     * implementation of the {@code EntityResolver} interface. It checks
+     * the passed in public ID against the registered entity IDs and uses a
+     * local URL if possible.
+     *
+     * @param publicId the public identifier of the entity being referenced
+     * @param systemId the system identifier of the entity being referenced
+     * @return an input source for the specified entity
+     * @throws SAXException if a parsing exception occurs
+     * @since 1.5
+     * @deprecated Use getEntityResolver().resolveEntity()
+     */
+    @Deprecated
+    public InputSource resolveEntity(String publicId, String systemId)
+            throws SAXException
+    {
+        try
+        {
+            return entityResolver.resolveEntity(publicId, systemId);
+        }
+        catch (IOException e)
+        {
+            throw new SAXException(e);
+        }
+    }
+
+    /**
+     * Returns a map with the entity IDs that have been registered using the
+     * {@code registerEntityId()} method.
+     *
+     * @return a map with the registered entity IDs
+     */
+    public Map<String, URL> getRegisteredEntities()
+    {
+        if (entityResolver instanceof EntityRegistry)
+        {
+            return ((EntityRegistry) entityResolver).getRegisteredEntities();
+        }
+        return new HashMap<String, URL>();
+    }
+
+    /**
+     * A specialized {@code Node} class that is connected with an XML
+     * element. Changes on a node are also performed on the associated element.
+     */
+    class XMLNode extends Node
+    {
+        /**
+         * The serial version UID.
+         */
+        private static final long serialVersionUID = -4133988932174596562L;
+
+        /**
+         * Creates a new instance of {@code XMLNode} and initializes it
+         * with a name and the corresponding XML element.
+         *
+         * @param name the node's name
+         * @param elem the XML element
+         */
+        public XMLNode(String name, Element elem)
+        {
+            super(name);
+            setReference(elem);
+        }
+
+        /**
+         * Sets the value of this node. If this node is associated with an XML
+         * element, this element will be updated, too.
+         *
+         * @param value the node's new value
+         */
+        @Override
+        public void setValue(Object value)
+        {
+            super.setValue(value);
+
+            if (getReference() != null && document != null)
+            {
+                if (isAttribute())
+                {
+                    updateAttribute();
+                }
+                else
+                {
+                    updateElement(value);
+                }
+            }
+        }
+
+        /**
+         * Updates the associated XML elements when a node is removed.
+         */
+        @Override
+        protected void removeReference()
+        {
+            if (getReference() != null)
+            {
+                Element element = (Element) getReference();
+                if (isAttribute())
+                {
+                    updateAttribute();
+                }
+                else
+                {
+                    org.w3c.dom.Node parentElem = element.getParentNode();
+                    if (parentElem != null)
+                    {
+                        parentElem.removeChild(element);
+                    }
+                }
+            }
+        }
+
+        /**
+         * Updates the node's value if it represents an element node.
+         *
+         * @param value the new value
+         */
+        private void updateElement(Object value)
+        {
+            Text txtNode = findTextNodeForUpdate();
+            if (value == null)
+            {
+                // remove text
+                if (txtNode != null)
+                {
+                    ((Element) getReference()).removeChild(txtNode);
+                }
+            }
+            else
+            {
+                if (txtNode == null)
+                {
+                    String newValue = isDelimiterParsingDisabled() ? value.toString()
+                        : PropertyConverter.escapeDelimiters(value.toString(), getListDelimiter());
+                    txtNode = document.createTextNode(newValue);
+                    if (((Element) getReference()).getFirstChild() != null)
+                    {
+                        ((Element) getReference()).insertBefore(txtNode,
+                                ((Element) getReference()).getFirstChild());
+                    }
+                    else
+                    {
+                        ((Element) getReference()).appendChild(txtNode);
+                    }
+                }
+                else
+                {
+                    String newValue = isDelimiterParsingDisabled() ? value.toString()
+                        : PropertyConverter.escapeDelimiters(value.toString(), getListDelimiter());
+                    txtNode.setNodeValue(newValue);
+                }
+            }
+        }
+
+        /**
+         * Updates the node's value if it represents an attribute.
+         *
+         */
+        private void updateAttribute()
+        {
+            XMLBuilderVisitor.updateAttribute(getParent(), getName(), getListDelimiter(),
+                    isAttributeSplittingDisabled());
+        }
+
+        /**
+         * Returns the only text node of this element for update. This method is
+         * called when the element's text changes. Then all text nodes except
+         * for the first are removed. A reference to the first is returned or
+         * <b>null </b> if there is no text node at all.
+         *
+         * @return the first and only text node
+         */
+        private Text findTextNodeForUpdate()
+        {
+            Text result = null;
+            Element elem = (Element) getReference();
+            // Find all Text nodes
+            NodeList children = elem.getChildNodes();
+            Collection<org.w3c.dom.Node> textNodes = new ArrayList<org.w3c.dom.Node>();
+            for (int i = 0; i < children.getLength(); i++)
+            {
+                org.w3c.dom.Node nd = children.item(i);
+                if (nd instanceof Text)
+                {
+                    if (result == null)
+                    {
+                        result = (Text) nd;
+                    }
+                    else
+                    {
+                        textNodes.add(nd);
+                    }
+                }
+            }
+
+            // We don't want CDATAs
+            if (result instanceof CDATASection)
+            {
+                textNodes.add(result);
+                result = null;
+            }
+
+            // Remove all but the first Text node
+            for (org.w3c.dom.Node tn : textNodes)
+            {
+                elem.removeChild(tn);
+            }
+            return result;
+        }
+    }
+
+    /**
+     * A concrete {@code BuilderVisitor} that can construct XML
+     * documents.
+     */
+    static class XMLBuilderVisitor extends BuilderVisitor
+    {
+        /** Stores the document to be constructed. */
+        private Document document;
+
+        /** Stores the list delimiter.*/
+        private final char listDelimiter;
+
+        /** True if attributes should not be split */
+        private boolean isAttributeSplittingDisabled;
+
+        /**
+         * Creates a new instance of {@code XMLBuilderVisitor}.
+         *
+         * @param doc the document to be created
+         * @param listDelimiter the delimiter for attribute properties with multiple values
+         * @param isAttributeSplittingDisabled true if attribute splitting is disabled.
+         */
+        public XMLBuilderVisitor(Document doc, char listDelimiter, boolean isAttributeSplittingDisabled)
+        {
+            document = doc;
+            this.listDelimiter = listDelimiter;
+            this.isAttributeSplittingDisabled = isAttributeSplittingDisabled;
+        }
+
+        /**
+         * Processes the node hierarchy and adds new nodes to the document.
+         *
+         * @param rootNode the root node
+         */
+        public void processDocument(Node rootNode)
+        {
+            rootNode.visit(this, null);
+        }
+
+        /**
+         * Inserts a new node. This implementation ensures that the correct
+         * XML element is created and inserted between the given siblings.
+         *
+         * @param newNode the node to insert
+         * @param parent the parent node
+         * @param sibling1 the first sibling
+         * @param sibling2 the second sibling
+         * @return the new node
+         */
+        @Override
+        protected Object insert(Node newNode, Node parent, Node sibling1, Node sibling2)
+        {
+            if (newNode.isAttribute())
+            {
+                updateAttribute(parent, getElement(parent), newNode.getName(), listDelimiter,
+                    isAttributeSplittingDisabled);
+                return null;
+            }
+
+            else
+            {
+                Element elem = document.createElement(newNode.getName());
+                if (newNode.getValue() != null)
+                {
+                    String txt = newNode.getValue().toString();
+                    if (listDelimiter != 0)
+                    {
+                        txt = PropertyConverter.escapeListDelimiter(txt, listDelimiter);
+                    }
+                    elem.appendChild(document.createTextNode(txt));
+                }
+                if (sibling2 == null)
+                {
+                    getElement(parent).appendChild(elem);
+                }
+                else if (sibling1 != null)
+                {
+                    getElement(parent).insertBefore(elem, getElement(sibling1).getNextSibling());
+                }
+                else
+                {
+                    getElement(parent).insertBefore(elem, getElement(parent).getFirstChild());
+                }
+                return elem;
+            }
+        }
+
+        /**
+         * Helper method for updating the value of the specified node's
+         * attribute with the given name.
+         *
+         * @param node the affected node
+         * @param elem the element that is associated with this node
+         * @param name the name of the affected attribute
+         * @param listDelimiter the delimiter for attributes with multiple values
+         * @param isAttributeSplittingDisabled true if attribute splitting is disabled.
+         */
+        private static void updateAttribute(Node node, Element elem, String name, char listDelimiter,
+                                            boolean isAttributeSplittingDisabled)
+        {
+            if (node != null && elem != null)
+            {
+                boolean hasAttribute = false;
+                List<ConfigurationNode> attrs = node.getAttributes(name);
+                StringBuilder buf = new StringBuilder();
+                char delimiter = (listDelimiter != 0) ? listDelimiter : ATTR_VALUE_DELIMITER;
+                for (ConfigurationNode attr : attrs)
+                {
+                    if (attr.getValue() != null)
+                    {
+                        hasAttribute = true;
+                        if (buf.length() > 0)
+                        {
+                            buf.append(delimiter);
+                        }
+                        String value = isAttributeSplittingDisabled ? attr.getValue().toString()
+                            : PropertyConverter.escapeDelimiters(attr.getValue().toString(),
+                                    delimiter);
+                        buf.append(value);
+                    }
+                    attr.setReference(elem);
+                }
+
+                if (!hasAttribute)
+                {
+                    elem.removeAttribute(name);
+                }
+                else
+                {
+                    elem.setAttribute(name, buf.toString());
+                }
+            }
+        }
+
+        /**
+         * Updates the value of the specified attribute of the given node.
+         * Because there can be multiple child nodes representing this attribute
+         * the new value is determined by iterating over all those child nodes.
+         *
+         * @param node the affected node
+         * @param name the name of the attribute
+         * @param listDelimiter the delimiter for attributes with multiple values
+         * @param isAttributeSplittingDisabled true if attributes splitting is disabled.
+         */
+        static void updateAttribute(Node node, String name, char listDelimiter,
+                                    boolean isAttributeSplittingDisabled)
+        {
+            if (node != null)
+            {
+                updateAttribute(node, (Element) node.getReference(), name, listDelimiter,
+                        isAttributeSplittingDisabled);
+            }
+        }
+
+        /**
+         * Helper method for accessing the element of the specified node.
+         *
+         * @param node the node
+         * @return the element of this node
+         */
+        private Element getElement(Node node)
+        {
+            // special treatment for root node of the hierarchy
+            return (node.getName() != null && node.getReference() != null) ? (Element) node
+                    .getReference()
+                    : document.getDocumentElement();
+        }
+    }
+
+    /**
+     * A special implementation of the {@code FileConfiguration} interface that is
+     * used internally to implement the {@code FileConfiguration} methods
+     * for {@code XMLConfiguration}, too.
+     */
+    private class XMLFileConfigurationDelegate extends FileConfigurationDelegate
+    {
+        @Override
+        public void load(InputStream in) throws ConfigurationException
+        {
+            XMLConfiguration.this.load(in);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/XMLPropertiesConfiguration.java b/src/main/java/org/apache/commons/configuration/XMLPropertiesConfiguration.java
new file mode 100644
index 0000000..63f55eb
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/XMLPropertiesConfiguration.java
@@ -0,0 +1,425 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URL;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.commons.lang.StringEscapeUtils;
+import org.apache.commons.lang.StringUtils;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.Attributes;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * This configuration implements the XML properties format introduced in Java
+ * 5.0, see http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html.
+ * An XML properties file looks like this:
+ *
+ * <pre>
+ * <?xml version="1.0"?>
+ * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
+ * <properties>
+ *   <comment>Description of the property list</comment>
+ *   <entry key="key1">value1</entry>
+ *   <entry key="key2">value2</entry>
+ *   <entry key="key3">value3</entry>
+ * </properties>
+ * </pre>
+ *
+ * The Java 5.0 runtime is not required to use this class. The default encoding
+ * for this configuration format is UTF-8. Note that unlike
+ * {@code PropertiesConfiguration}, {@code XMLPropertiesConfiguration}
+ * does not support includes.
+ *
+ * <em>Note:</em>Configuration objects of this type can be read concurrently
+ * by multiple threads. However if one of these threads modifies the object,
+ * synchronization has to be performed manually.
+ *
+ * @author Emmanuel Bourg
+ * @author Alistair Young
+ * @version $Id: XMLPropertiesConfiguration.java 1534399 2013-10-21 22:25:03Z henning $
+ * @since 1.1
+ */
+public class XMLPropertiesConfiguration extends PropertiesConfiguration
+{
+    /**
+     * The default encoding (UTF-8 as specified by http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html)
+     */
+    private static final String DEFAULT_ENCODING = "UTF-8";
+
+    /**
+     * Default string used when the XML is malformed
+     */
+    private static final String MALFORMED_XML_EXCEPTION = "Malformed XML";
+
+    // initialization block to set the encoding before loading the file in the constructors
+    {
+        setEncoding(DEFAULT_ENCODING);
+    }
+
+    /**
+     * Creates an empty XMLPropertyConfiguration object which can be
+     * used to synthesize a new Properties file by adding values and
+     * then saving(). An object constructed by this C'tor can not be
+     * tickled into loading included files because it cannot supply a
+     * base for relative includes.
+     */
+    public XMLPropertiesConfiguration()
+    {
+        super();
+    }
+
+    /**
+     * Creates and loads the xml properties from the specified file.
+     * The specified file can contain "include" properties which then
+     * are loaded and merged into the properties.
+     *
+     * @param fileName The name of the properties file to load.
+     * @throws ConfigurationException Error while loading the properties file
+     */
+    public XMLPropertiesConfiguration(String fileName) throws ConfigurationException
+    {
+        super(fileName);
+    }
+
+    /**
+     * Creates and loads the xml properties from the specified file.
+     * The specified file can contain "include" properties which then
+     * are loaded and merged into the properties.
+     *
+     * @param file The properties file to load.
+     * @throws ConfigurationException Error while loading the properties file
+     */
+    public XMLPropertiesConfiguration(File file) throws ConfigurationException
+    {
+        super(file);
+    }
+
+    /**
+     * Creates and loads the xml properties from the specified URL.
+     * The specified file can contain "include" properties which then
+     * are loaded and merged into the properties.
+     *
+     * @param url The location of the properties file to load.
+     * @throws ConfigurationException Error while loading the properties file
+     */
+    public XMLPropertiesConfiguration(URL url) throws ConfigurationException
+    {
+        super(url);
+    }
+
+    /**
+     * Creates and loads the xml properties from the specified DOM node.
+     *
+     * @param element The DOM element
+     * @throws ConfigurationException Error while loading the properties file
+     * @since 2.0
+     */
+    public XMLPropertiesConfiguration(Element element) throws ConfigurationException
+    {
+        super();
+        this.load(element);
+    }
+
+    @Override
+    public void load(Reader in) throws ConfigurationException
+    {
+        SAXParserFactory factory = SAXParserFactory.newInstance();
+        factory.setNamespaceAware(false);
+        factory.setValidating(true);
+
+        try
+        {
+            SAXParser parser = factory.newSAXParser();
+
+            XMLReader xmlReader = parser.getXMLReader();
+            xmlReader.setEntityResolver(new EntityResolver()
+            {
+                public InputSource resolveEntity(String publicId, String systemId)
+                {
+                    return new InputSource(getClass().getClassLoader().getResourceAsStream("properties.dtd"));
+                }
+            });
+            xmlReader.setContentHandler(new XMLPropertiesHandler());
+            xmlReader.parse(new InputSource(in));
+        }
+        catch (Exception e)
+        {
+            throw new ConfigurationException("Unable to parse the configuration file", e);
+        }
+
+        // todo: support included properties ?
+    }
+
+    /**
+     * Parses a DOM element containing the properties. The DOM element has to follow
+     * the XML properties format introduced in Java 5.0,
+     * see http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html
+     *
+     * @param element The DOM element
+     * @throws ConfigurationException Error while interpreting the DOM
+     * @since 2.0
+     */
+    public void load(Element element) throws ConfigurationException
+    {
+        if (!element.getNodeName().equals("properties"))
+        {
+            throw new ConfigurationException(MALFORMED_XML_EXCEPTION);
+        }
+        NodeList childNodes = element.getChildNodes();
+        for (int i = 0; i < childNodes.getLength(); i++)
+        {
+            Node item = childNodes.item(i);
+            if (item instanceof Element)
+            {
+                if (item.getNodeName().equals("comment"))
+                {
+                    setHeader(item.getTextContent());
+                }
+                else if (item.getNodeName().equals("entry"))
+                {
+                    String key = ((Element) item).getAttribute("key");
+                    addProperty(key, item.getTextContent());
+                }
+                else
+                {
+                    throw new ConfigurationException(MALFORMED_XML_EXCEPTION);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void save(Writer out) throws ConfigurationException
+    {
+        PrintWriter writer = new PrintWriter(out);
+
+        String encoding = getEncoding() != null ? getEncoding() : DEFAULT_ENCODING;
+        writer.println("<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>");
+        writer.println("<!DOCTYPE properties SYSTEM \"http://java.sun.com/dtd/properties.dtd\">");
+        writer.println("<properties>");
+
+        if (getHeader() != null)
+        {
+            writer.println("  <comment>" + StringEscapeUtils.escapeXml(getHeader()) + "</comment>");
+        }
+
+        Iterator<String> keys = getKeys();
+        while (keys.hasNext())
+        {
+            String key = keys.next();
+            Object value = getProperty(key);
+
+            if (value instanceof List)
+            {
+                writeProperty(writer, key, (List<?>) value);
+            }
+            else
+            {
+                writeProperty(writer, key, value);
+            }
+        }
+
+        writer.println("</properties>");
+        writer.flush();
+    }
+
+    /**
+     * Write a property.
+     *
+     * @param out the output stream
+     * @param key the key of the property
+     * @param value the value of the property
+     */
+    private void writeProperty(PrintWriter out, String key, Object value)
+    {
+        // escape the key
+        String k = StringEscapeUtils.escapeXml(key);
+
+        if (value != null)
+        {
+            // escape the value
+            String v = StringEscapeUtils.escapeXml(String.valueOf(value));
+            v = StringUtils.replace(v, String.valueOf(getListDelimiter()), "\\" + getListDelimiter());
+
+            out.println("  <entry key=\"" + k + "\">" + v + "</entry>");
+        }
+        else
+        {
+            out.println("  <entry key=\"" + k + "\"/>");
+        }
+    }
+
+    /**
+     * Write a list property.
+     *
+     * @param out the output stream
+     * @param key the key of the property
+     * @param values a list with all property values
+     */
+    private void writeProperty(PrintWriter out, String key, List<?> values)
+    {
+        for (Object value : values)
+        {
+            writeProperty(out, key, value);
+        }
+    }
+
+    /**
+     * Writes the configuration as child to the given DOM node
+     *
+     * @param document The DOM document to add the configuration to
+     * @param parent The DOM parent node
+     * @since 2.0
+     */
+    public void save(Document document, Node parent)
+    {
+        Element properties = document.createElement("properties");
+        parent.appendChild(properties);
+        if (getHeader() != null)
+        {
+            Element comment = document.createElement("comment");
+            properties.appendChild(comment);
+            comment.setTextContent(StringEscapeUtils.escapeXml(getHeader()));
+        }
+
+        Iterator<String> keys = getKeys();
+        while (keys.hasNext())
+        {
+            String key = keys.next();
+            Object value = getProperty(key);
+
+            if (value instanceof List)
+            {
+                writeProperty(document, properties, key, (List<?>) value);
+            }
+            else
+            {
+                writeProperty(document, properties, key, value);
+            }
+        }
+    }
+
+    private void writeProperty(Document document, Node properties, String key, Object value)
+    {
+        Element entry = document.createElement("entry");
+        properties.appendChild(entry);
+
+        // escape the key
+        String k = StringEscapeUtils.escapeXml(key);
+        entry.setAttribute("key", k);
+
+        if (value != null)
+        {
+            // escape the value
+            String v = StringEscapeUtils.escapeXml(String.valueOf(value));
+            v = StringUtils.replace(v, String.valueOf(getListDelimiter()), "\\" + getListDelimiter());
+            entry.setTextContent(v);
+        }
+    }
+
+    private void writeProperty(Document document, Node properties, String key, List<?> values)
+    {
+        for (Object value : values)
+        {
+            writeProperty(document, properties, key, value);
+        }
+    }
+
+    /**
+     * SAX Handler to parse a XML properties file.
+     *
+     * @author Alistair Young
+     * @since 1.2
+     */
+    private class XMLPropertiesHandler extends DefaultHandler
+    {
+        /** The key of the current entry being parsed. */
+        private String key;
+
+        /** The value of the current entry being parsed. */
+        private StringBuilder value = new StringBuilder();
+
+        /** Indicates that a comment is being parsed. */
+        private boolean inCommentElement;
+
+        /** Indicates that an entry is being parsed. */
+        private boolean inEntryElement;
+
+        @Override
+        public void startElement(String uri, String localName, String qName, Attributes attrs)
+        {
+            if ("comment".equals(qName))
+            {
+                inCommentElement = true;
+            }
+
+            if ("entry".equals(qName))
+            {
+                key = attrs.getValue("key");
+                inEntryElement = true;
+            }
+        }
+
+        @Override
+        public void endElement(String uri, String localName, String qName)
+        {
+            if (inCommentElement)
+            {
+                // We've just finished a <comment> element so set the header
+                setHeader(value.toString());
+                inCommentElement = false;
+            }
+
+            if (inEntryElement)
+            {
+                // We've just finished an <entry> element, so add the key/value pair
+                addProperty(key, value.toString());
+                inEntryElement = false;
+            }
+
+            // Clear the element value buffer
+            value = new StringBuilder();
+        }
+
+        @Override
+        public void characters(char[] chars, int start, int length)
+        {
+            /**
+             * We're currently processing an element. All character data from now until
+             * the next endElement() call will be the data for this  element.
+             */
+            value.append(chars, start, length);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/beanutils/BeanDeclaration.java b/src/main/java/org/apache/commons/configuration/beanutils/BeanDeclaration.java
new file mode 100644
index 0000000..47d0bd6
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/beanutils/BeanDeclaration.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.beanutils;
+
+import java.util.Map;
+
+/**
+ * <p>
+ * Definition of an interface for declaring a bean in a configuration file.
+ * </p>
+ * <p>
+ * Commons Configurations allows to define beans (i.e. simple Java objects) in
+ * configuration files, which can be created at runtime. This is especially
+ * useful if you program against interfaces and want to define the concrete
+ * implementation class is a configuration file.
+ * </p>
+ * <p>
+ * This interface defines methods for retrieving all information about a bean
+ * that should be created from a configuration file, e.g. the bean's properties
+ * or the factory to use for creating the instance. With different
+ * implementations different "layouts" of bean declarations can be
+ * supported. For instance if an XML configuration file is used, all features of
+ * XML (e.g. attributes, nested elements) can be used to define the bean. In a
+ * properties file the declaration format is more limited. The purpose of this
+ * interface is to abstract from the concrete declaration format.
+ * </p>
+ *
+ * @since 1.3
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: BeanDeclaration.java 1208756 2011-11-30 20:37:32Z oheger $
+ */
+public interface BeanDeclaration
+{
+    /**
+     * Returns the name of the {@code BeanFactory} that should be used
+     * for creating the bean instance. This can be <b>null</b>, then a default
+     * factory will be used.
+     *
+     * @return the name of the bean factory
+     */
+    String getBeanFactoryName();
+
+    /**
+     * Here an arbitrary object can be returned that will be passed to the bean
+     * factory. Its meaning is not further specified. The purpose of this
+     * additional parameter is to support a further configuration of the bean
+     * factory that can be placed directly at the bean declaration.
+     *
+     * @return a parameter for the bean factory
+     */
+    Object getBeanFactoryParameter();
+
+    /**
+     * Returns the name of the bean class, from which an instance is to be
+     * created. This value must be defined unless a default class is provided
+     * for the bean creation operation.
+     *
+     * @return the name of the bean class
+     */
+    String getBeanClassName();
+
+    /**
+     * Returns a map with properties that should be initialized on the newly
+     * created bean. The map's keys are the names of the properties; the
+     * corresponding values are the properties' values. The return value can be
+     * <b>null</b> if no properties should be set.
+     *
+     * @return a map with properties to be initialized
+     */
+    Map<String, Object> getBeanProperties();
+
+    /**
+     * Returns a map with declarations for beans that should be set as
+     * properties of the newly created bean. This allows for complex
+     * initialization scenarios: a bean for a bean that contains complex
+     * properties (e.g. other beans) can have nested declarations for defining
+     * these complex properties. The returned map's key are the names of the
+     * properties to initialize. The values are either {@code BeanDeclaration}
+     * implementations or collections thereof. They will be treated like this
+     * declaration (in a* recursive manner), and the resulting beans are
+     * assigned to the corresponding properties.
+     *
+     * @return a map with nested bean declarations
+     */
+    Map<String, Object> getNestedBeanDeclarations();
+}
diff --git a/src/main/java/org/apache/commons/configuration/beanutils/BeanFactory.java b/src/main/java/org/apache/commons/configuration/beanutils/BeanFactory.java
new file mode 100644
index 0000000..3762fcc
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/beanutils/BeanFactory.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.beanutils;
+
+/**
+ * <p>
+ * Definition of an interface for bean factories.
+ * </p>
+ * <p>
+ * Beans defined in configuration files are not directly created, but by so
+ * called <em>bean factories</em>. This additional level of indirection
+ * provides for high flexibility in the creation process. For instance one
+ * implementation of this interface could be very simple and create a new
+ * instance of the specified class for each invocation. A different
+ * implementation could cache already created beans and ensure that always the
+ * same bean of the given class will be returned - this would be an easy mean
+ * for creating singleton objects.
+ * </p>
+ * <p>
+ * The interface itself is quite simple. There is a single method for creating a
+ * bean of a given class. All necessary parameters are obtained from an also
+ * passed in {@link BeanDeclaration} object. It is also possible
+ * (but optional) for a bean factory to declare the default class of the bean it
+ * creates. Then it is not necessary to specify a bean class in the bean
+ * declaration.
+ * </p>
+ *
+ * @since 1.3
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: BeanFactory.java 1208757 2011-11-30 20:38:15Z oheger $
+ */
+public interface BeanFactory
+{
+    /**
+     * Returns a bean instance for the given class. The bean will be initialized
+     * from the specified bean declaration object. It is up to a concrete
+     * implementation how the bean will be created and initialized.
+     *
+     * @param beanClass the class for the bean
+     * @param data the bean declaration object containing all data about the
+     * bean to be created
+     * @param param an additional parameter that may be passed by calling code;
+     * it is up to a concrete implementation how this parameter is evaluated
+     * @return the new bean instance (should not be <b>null</b>)
+     * @throws Exception if an error occurs (the helper classes for creating
+     * beans will catch this unspecific exception and wrap it in a configuration
+     * exception)
+     */
+    Object createBean(Class<?> beanClass, BeanDeclaration data, Object param)
+            throws Exception;
+
+    /**
+     * Returns the default bean class of this bean factory. If an implementation
+     * here returns a non <b>null</b> value, bean declarations using this
+     * factory do not need to provide the name of the bean class. In such a case
+     * an instance of the default class will be created.
+     *
+     * @return the default class of this factory or <b>null</b> if there is
+     * none
+     */
+    Class<?> getDefaultBeanClass();
+}
diff --git a/src/main/java/org/apache/commons/configuration/beanutils/BeanHelper.java b/src/main/java/org/apache/commons/configuration/beanutils/BeanHelper.java
new file mode 100644
index 0000000..53f1c2c
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/beanutils/BeanHelper.java
@@ -0,0 +1,521 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.beanutils;
+
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.commons.beanutils.BeanUtils;
+import org.apache.commons.beanutils.PropertyUtils;
+import org.apache.commons.configuration.ConfigurationRuntimeException;
+import org.apache.commons.lang.ClassUtils;
+
+/**
+ * <p>
+ * A helper class for creating bean instances that are defined in configuration
+ * files.
+ * </p>
+ * <p>
+ * This class provides static utility methods related to bean creation
+ * operations. These methods simplify such operations because a client need not
+ * deal with all involved interfaces. Usually, if a bean declaration has already
+ * been obtained, a single method call is necessary to create a new bean
+ * instance.
+ * </p>
+ * <p>
+ * This class also supports the registration of custom bean factories.
+ * Implementations of the {@link BeanFactory} interface can be
+ * registered under a symbolic name using the {@code registerBeanFactory()}
+ * method. In the configuration file the name of the bean factory can be
+ * specified in the bean declaration. Then this factory will be used to create
+ * the bean.
+ * </p>
+ *
+ * @since 1.3
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: BeanHelper.java 1534393 2013-10-21 22:02:27Z henning $
+ */
+public final class BeanHelper
+{
+    /** Stores a map with the registered bean factories. */
+    private static final Map<String, BeanFactory> BEAN_FACTORIES = Collections
+            .synchronizedMap(new HashMap<String, BeanFactory>());
+
+    /**
+     * Stores the default bean factory, which will be used if no other factory
+     * is provided.
+     */
+    private static BeanFactory defaultBeanFactory = DefaultBeanFactory.INSTANCE;
+
+    /**
+     * Private constructor, so no instances can be created.
+     */
+    private BeanHelper()
+    {
+    }
+
+    /**
+     * Register a bean factory under a symbolic name. This factory object can
+     * then be specified in bean declarations with the effect that this factory
+     * will be used to obtain an instance for the corresponding bean
+     * declaration.
+     *
+     * @param name the name of the factory
+     * @param factory the factory to be registered
+     */
+    public static void registerBeanFactory(String name, BeanFactory factory)
+    {
+        if (name == null)
+        {
+            throw new IllegalArgumentException(
+                    "Name for bean factory must not be null!");
+        }
+        if (factory == null)
+        {
+            throw new IllegalArgumentException("Bean factory must not be null!");
+        }
+
+        BEAN_FACTORIES.put(name, factory);
+    }
+
+    /**
+     * Deregisters the bean factory with the given name. After that this factory
+     * cannot be used any longer.
+     *
+     * @param name the name of the factory to be deregistered
+     * @return the factory that was registered under this name; <b>null</b> if
+     * there was no such factory
+     */
+    public static BeanFactory deregisterBeanFactory(String name)
+    {
+        return BEAN_FACTORIES.remove(name);
+    }
+
+    /**
+     * Returns a set with the names of all currently registered bean factories.
+     *
+     * @return a set with the names of the registered bean factories
+     */
+    public static Set<String> registeredFactoryNames()
+    {
+        return BEAN_FACTORIES.keySet();
+    }
+
+    /**
+     * Returns the default bean factory.
+     *
+     * @return the default bean factory
+     */
+    public static BeanFactory getDefaultBeanFactory()
+    {
+        return defaultBeanFactory;
+    }
+
+    /**
+     * Sets the default bean factory. This factory will be used for all create
+     * operations, for which no special factory is provided in the bean
+     * declaration.
+     *
+     * @param factory the default bean factory (must not be <b>null</b>)
+     */
+    public static void setDefaultBeanFactory(BeanFactory factory)
+    {
+        if (factory == null)
+        {
+            throw new IllegalArgumentException(
+                    "Default bean factory must not be null!");
+        }
+        defaultBeanFactory = factory;
+    }
+
+    /**
+     * Initializes the passed in bean. This method will obtain all the bean's
+     * properties that are defined in the passed in bean declaration. These
+     * properties will be set on the bean. If necessary, further beans will be
+     * created recursively.
+     *
+     * @param bean the bean to be initialized
+     * @param data the bean declaration
+     * @throws ConfigurationRuntimeException if a property cannot be set
+     */
+    public static void initBean(Object bean, BeanDeclaration data)
+            throws ConfigurationRuntimeException
+    {
+        initBeanProperties(bean, data);
+
+        Map<String, Object> nestedBeans = data.getNestedBeanDeclarations();
+        if (nestedBeans != null)
+        {
+            if (bean instanceof Collection)
+            {
+                // This is safe because the collection stores the values of the
+                // nested beans.
+                @SuppressWarnings("unchecked")
+                Collection<Object> coll = (Collection<Object>) bean;
+                if (nestedBeans.size() == 1)
+                {
+                    Map.Entry<String, Object> e = nestedBeans.entrySet().iterator().next();
+                    String propName = e.getKey();
+                    Class<?> defaultClass = getDefaultClass(bean, propName);
+                    if (e.getValue() instanceof List)
+                    {
+                        // This is safe, provided that the bean declaration is implemented
+                        // correctly.
+                        @SuppressWarnings("unchecked")
+                        List<BeanDeclaration> decls = (List<BeanDeclaration>) e.getValue();
+                        for (BeanDeclaration decl : decls)
+                        {
+                            coll.add(createBean(decl, defaultClass));
+                        }
+                    }
+                    else
+                    {
+                        BeanDeclaration decl = (BeanDeclaration) e.getValue();
+                        coll.add(createBean(decl, defaultClass));
+                    }
+                }
+            }
+            else
+            {
+                for (Map.Entry<String, Object> e : nestedBeans.entrySet())
+                {
+                    String propName = e.getKey();
+                    Class<?> defaultClass = getDefaultClass(bean, propName);
+
+                    Object prop = e.getValue();
+
+                    if (prop instanceof Collection)
+                    {
+                        Collection<Object> beanCollection =
+                                createPropertyCollection(propName, defaultClass);
+
+                        for (Object elemDef : (Collection<?>) prop)
+                        {
+                            beanCollection
+                                    .add(createBean((BeanDeclaration) elemDef));
+                        }
+
+                        initProperty(bean, propName, beanCollection);
+                    }
+                    else
+                    {
+                        initProperty(bean, propName, createBean(
+                            (BeanDeclaration) e.getValue(), defaultClass));
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Initializes the beans properties.
+     *
+     * @param bean the bean to be initialized
+     * @param data the bean declaration
+     * @throws ConfigurationRuntimeException if a property cannot be set
+     */
+    public static void initBeanProperties(Object bean, BeanDeclaration data)
+            throws ConfigurationRuntimeException
+    {
+        Map<String, Object> properties = data.getBeanProperties();
+        if (properties != null)
+        {
+            for (Map.Entry<String, Object> e : properties.entrySet())
+            {
+                String propName = e.getKey();
+                initProperty(bean, propName, e.getValue());
+            }
+        }
+    }
+
+    /**
+     * Return the Class of the property if it can be determined.
+     * @param bean The bean containing the property.
+     * @param propName The name of the property.
+     * @return The class associated with the property or null.
+     */
+    private static Class<?> getDefaultClass(Object bean, String propName)
+    {
+        try
+        {
+            PropertyDescriptor desc = PropertyUtils.getPropertyDescriptor(bean, propName);
+            if (desc == null)
+            {
+                return null;
+            }
+            return desc.getPropertyType();
+        }
+        catch (Exception ex)
+        {
+            return null;
+        }
+    }
+
+    /**
+     * Sets a property on the given bean using Common Beanutils.
+     *
+     * @param bean the bean
+     * @param propName the name of the property
+     * @param value the property's value
+     * @throws ConfigurationRuntimeException if the property is not writeable or
+     * an error occurred
+     */
+    private static void initProperty(Object bean, String propName, Object value)
+            throws ConfigurationRuntimeException
+    {
+        if (!PropertyUtils.isWriteable(bean, propName))
+        {
+            throw new ConfigurationRuntimeException("Property " + propName
+                    + " cannot be set on " + bean.getClass().getName());
+        }
+
+        try
+        {
+            BeanUtils.setProperty(bean, propName, value);
+        }
+        catch (IllegalAccessException iaex)
+        {
+            throw new ConfigurationRuntimeException(iaex);
+        }
+        catch (InvocationTargetException itex)
+        {
+            throw new ConfigurationRuntimeException(itex);
+        }
+    }
+
+    /**
+     * Creates a concrete collection instance to populate a property of type
+     * collection. This method tries to guess an appropriate collection type.
+     * Mostly the type of the property will be one of the collection interfaces
+     * rather than a concrete class; so we have to create a concrete equivalent.
+     *
+     * @param propName the name of the collection property
+     * @param propertyClass the type of the property
+     * @return the newly created collection
+     */
+    private static Collection<Object> createPropertyCollection(String propName,
+            Class<?> propertyClass)
+    {
+        Collection<Object> beanCollection = null;
+
+        if (List.class.isAssignableFrom(propertyClass))
+        {
+            beanCollection = new ArrayList<Object>();
+        }
+        else if (Set.class.isAssignableFrom(propertyClass))
+        {
+            beanCollection = new TreeSet<Object>();
+        }
+        else
+        {
+            throw new UnsupportedOperationException(
+                    "Unable to handle collection of type : "
+                            + propertyClass.getName() + " for property "
+                            + propName);
+        }
+        return beanCollection;
+    }
+
+    /**
+     * Set a property on the bean only if the property exists
+     *
+     * @param bean the bean
+     * @param propName the name of the property
+     * @param value the property's value
+     * @throws ConfigurationRuntimeException if the property is not writeable or
+     *         an error occurred
+     */
+    public static void setProperty(Object bean, String propName, Object value)
+    {
+        if (PropertyUtils.isWriteable(bean, propName))
+        {
+            initProperty(bean, propName, value);
+        }
+    }
+
+    /**
+     * The main method for creating and initializing beans from a configuration.
+     * This method will return an initialized instance of the bean class
+     * specified in the passed in bean declaration. If this declaration does not
+     * contain the class of the bean, the passed in default class will be used.
+     * From the bean declaration the factory to be used for creating the bean is
+     * queried. The declaration may here return <b>null</b>, then a default
+     * factory is used. This factory is then invoked to perform the create
+     * operation.
+     *
+     * @param data the bean declaration
+     * @param defaultClass the default class to use
+     * @param param an additional parameter that will be passed to the bean
+     * factory; some factories may support parameters and behave different
+     * depending on the value passed in here
+     * @return the new bean
+     * @throws ConfigurationRuntimeException if an error occurs
+     */
+    public static Object createBean(BeanDeclaration data, Class<?> defaultClass,
+            Object param) throws ConfigurationRuntimeException
+    {
+        if (data == null)
+        {
+            throw new IllegalArgumentException(
+                    "Bean declaration must not be null!");
+        }
+
+        BeanFactory factory = fetchBeanFactory(data);
+        try
+        {
+            return factory.createBean(fetchBeanClass(data, defaultClass,
+                    factory), data, param);
+        }
+        catch (Exception ex)
+        {
+            throw new ConfigurationRuntimeException(ex);
+        }
+    }
+
+    /**
+     * Returns a bean instance for the specified declaration. This method is a
+     * short cut for {@code createBean(data, null, null);}.
+     *
+     * @param data the bean declaration
+     * @param defaultClass the class to be used when in the declaration no class
+     * is specified
+     * @return the new bean
+     * @throws ConfigurationRuntimeException if an error occurs
+     */
+    public static Object createBean(BeanDeclaration data, Class<?> defaultClass)
+            throws ConfigurationRuntimeException
+    {
+        return createBean(data, defaultClass, null);
+    }
+
+    /**
+     * Returns a bean instance for the specified declaration. This method is a
+     * short cut for {@code createBean(data, null);}.
+     *
+     * @param data the bean declaration
+     * @return the new bean
+     * @throws ConfigurationRuntimeException if an error occurs
+     */
+    public static Object createBean(BeanDeclaration data)
+            throws ConfigurationRuntimeException
+    {
+        return createBean(data, null);
+    }
+
+    /**
+     * Returns a {@code java.lang.Class} object for the specified name.
+     * Because class loading can be tricky in some environments the code for
+     * retrieving a class by its name was extracted into this helper method. So
+     * if changes are necessary, they can be made at a single place.
+     *
+     * @param name the name of the class to be loaded
+     * @param callingClass the calling class
+     * @return the class object for the specified name
+     * @throws ClassNotFoundException if the class cannot be loaded
+     */
+    static Class<?> loadClass(String name, Class<?> callingClass)
+            throws ClassNotFoundException
+    {
+        return ClassUtils.getClass(name);
+    }
+
+    /**
+     * Determines the class of the bean to be created. If the bean declaration
+     * contains a class name, this class is used. Otherwise it is checked
+     * whether a default class is provided. If this is not the case, the
+     * factory's default class is used. If this class is undefined, too, an
+     * exception is thrown.
+     *
+     * @param data the bean declaration
+     * @param defaultClass the default class
+     * @param factory the bean factory to use
+     * @return the class of the bean to be created
+     * @throws ConfigurationRuntimeException if the class cannot be determined
+     */
+    private static Class<?> fetchBeanClass(BeanDeclaration data,
+            Class<?> defaultClass, BeanFactory factory)
+            throws ConfigurationRuntimeException
+    {
+        String clsName = data.getBeanClassName();
+        if (clsName != null)
+        {
+            try
+            {
+                return loadClass(clsName, factory.getClass());
+            }
+            catch (ClassNotFoundException cex)
+            {
+                throw new ConfigurationRuntimeException(cex);
+            }
+        }
+
+        if (defaultClass != null)
+        {
+            return defaultClass;
+        }
+
+        Class<?> clazz = factory.getDefaultBeanClass();
+        if (clazz == null)
+        {
+            throw new ConfigurationRuntimeException(
+                    "Bean class is not specified!");
+        }
+        return clazz;
+    }
+
+    /**
+     * Obtains the bean factory to use for creating the specified bean. This
+     * method will check whether a factory is specified in the bean declaration.
+     * If this is not the case, the default bean factory will be used.
+     *
+     * @param data the bean declaration
+     * @return the bean factory to use
+     * @throws ConfigurationRuntimeException if the factory cannot be determined
+     */
+    private static BeanFactory fetchBeanFactory(BeanDeclaration data)
+            throws ConfigurationRuntimeException
+    {
+        String factoryName = data.getBeanFactoryName();
+        if (factoryName != null)
+        {
+            BeanFactory factory = BEAN_FACTORIES.get(factoryName);
+            if (factory == null)
+            {
+                throw new ConfigurationRuntimeException(
+                        "Unknown bean factory: " + factoryName);
+            }
+            else
+            {
+                return factory;
+            }
+        }
+        else
+        {
+            return getDefaultBeanFactory();
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/beanutils/ConfigurationDynaBean.java b/src/main/java/org/apache/commons/configuration/beanutils/ConfigurationDynaBean.java
new file mode 100644
index 0000000..3475b87
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/beanutils/ConfigurationDynaBean.java
@@ -0,0 +1,244 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.beanutils;
+
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.commons.beanutils.DynaBean;
+import org.apache.commons.beanutils.DynaClass;
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationMap;
+import org.apache.commons.configuration.SubsetConfiguration;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * The <tt>ConfigurationDynaBean</tt> dynamically reads and writes
+ * configurations properties from a wrapped configuration-collection
+ * {@link org.apache.commons.configuration.Configuration} instance. It also
+ * implements a {@link java.util.Map} interface so that it can be used in
+ * JSP 2.0 Expression Language expressions.
+ *
+ * <p>The {@code ConfigurationDynaBean} maps nested and mapped properties
+ * to the appropriate {@code Configuration} subset using the
+ * {@link org.apache.commons.configuration.Configuration#subset}
+ * method. Similarly, indexed properties reference lists of configuration
+ * properties using the
+ * {@link org.apache.commons.configuration.Configuration#getList(String)}
+ * method. Setting an indexed property is supported, too.</p>
+ *
+ * <p>Note: Some of the methods expect that a dot (".") is used as
+ * property delimiter for the wrapped configuration. This is true for most of
+ * the default configurations. Hierarchical configurations, for which a specific
+ * expression engine is set, may cause problems.</p>
+ *
+ * @author <a href="mailto:ricardo.gladwell at btinternet.com">Ricardo Gladwell</a>
+ * @version $Id: ConfigurationDynaBean.java 1366932 2012-07-29 20:06:31Z oheger $
+ * @since 1.0-rc1
+ */
+public class ConfigurationDynaBean extends ConfigurationMap implements DynaBean
+{
+    /** Constant for the property delimiter.*/
+    private static final String PROPERTY_DELIMITER = ".";
+
+    /** The logger.*/
+    private static final Log LOG = LogFactory.getLog(ConfigurationDynaBean.class);
+
+    /**
+     * Creates a new instance of {@code ConfigurationDynaBean} and sets
+     * the configuration this bean is associated with.
+     *
+     * @param configuration the configuration
+     */
+    public ConfigurationDynaBean(Configuration configuration)
+    {
+        super(configuration);
+        if (LOG.isTraceEnabled())
+        {
+            LOG.trace("ConfigurationDynaBean(" + configuration + ")");
+        }
+    }
+
+    public void set(String name, Object value)
+    {
+        if (LOG.isTraceEnabled())
+        {
+            LOG.trace("set(" + name + "," + value + ")");
+        }
+
+        if (value == null)
+        {
+            throw new NullPointerException("Error trying to set property to null.");
+        }
+
+        if (value instanceof Collection)
+        {
+            Collection<?> collection = (Collection<?>) value;
+            for (Object v : collection)
+            {
+                getConfiguration().addProperty(name, v);
+            }
+        }
+        else if (value.getClass().isArray())
+        {
+            int length = Array.getLength(value);
+            for (int i = 0; i < length; i++)
+            {
+                getConfiguration().addProperty(name, Array.get(value, i));
+            }
+        }
+        else
+        {
+            getConfiguration().setProperty(name, value);
+        }
+    }
+
+    public Object get(String name)
+    {
+        if (LOG.isTraceEnabled())
+        {
+            LOG.trace("get(" + name + ")");
+        }
+
+        // get configuration property
+        Object result = getConfiguration().getProperty(name);
+        if (result == null)
+        {
+            // otherwise attempt to create bean from configuration subset
+            Configuration subset = new SubsetConfiguration(getConfiguration(), name, PROPERTY_DELIMITER);
+            if (!subset.isEmpty())
+            {
+                result = new ConfigurationDynaBean(subset);
+            }
+        }
+
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug(name + "=[" + result + "]");
+        }
+
+        if (result == null)
+        {
+            throw new IllegalArgumentException("Property '" + name + "' does not exist.");
+        }
+        return result;
+    }
+
+    public boolean contains(String name, String key)
+    {
+        Configuration subset = getConfiguration().subset(name);
+        if (subset == null)
+        {
+            throw new IllegalArgumentException("Mapped property '" + name + "' does not exist.");
+        }
+
+        return subset.containsKey(key);
+    }
+
+    public Object get(String name, int index)
+    {
+        if (!checkIndexedProperty(name))
+        {
+            throw new IllegalArgumentException("Property '" + name
+                    + "' is not indexed.");
+        }
+
+        List<Object> list = getConfiguration().getList(name);
+        return list.get(index);
+    }
+
+    public Object get(String name, String key)
+    {
+        Configuration subset = getConfiguration().subset(name);
+        if (subset == null)
+        {
+            throw new IllegalArgumentException("Mapped property '" + name + "' does not exist.");
+        }
+
+        return subset.getProperty(key);
+    }
+
+    public DynaClass getDynaClass()
+    {
+        return new ConfigurationDynaClass(getConfiguration());
+    }
+
+    public void remove(String name, String key)
+    {
+        Configuration subset = new SubsetConfiguration(getConfiguration(), name, PROPERTY_DELIMITER);
+        subset.setProperty(key, null);
+    }
+
+    public void set(String name, int index, Object value)
+    {
+        if (!checkIndexedProperty(name) && index > 0)
+        {
+            throw new IllegalArgumentException("Property '" + name
+                    + "' is not indexed.");
+        }
+
+        Object property = getConfiguration().getProperty(name);
+
+        if (property instanceof List)
+        {
+            // This is safe because multiple values of a configuration property
+            // are always stored as lists of type Object.
+            @SuppressWarnings("unchecked")
+            List<Object> list = (List<Object>) property;
+            list.set(index, value);
+            getConfiguration().setProperty(name, list);
+        }
+        else if (property.getClass().isArray())
+        {
+            Array.set(property, index, value);
+        }
+        else if (index == 0)
+        {
+            getConfiguration().setProperty(name, value);
+        }
+    }
+
+    public void set(String name, String key, Object value)
+    {
+        getConfiguration().setProperty(name + "." + key, value);
+    }
+
+    /**
+     * Tests whether the given name references an indexed property. This
+     * implementation tests for properties of type list or array. If the
+     * property does not exist, an exception is thrown.
+     *
+     * @param name the name of the property to check
+     * @return a flag whether this is an indexed property
+     * @throws IllegalArgumentException if the property does not exist
+     */
+    private boolean checkIndexedProperty(String name)
+    {
+        Object property = getConfiguration().getProperty(name);
+
+        if (property == null)
+        {
+            throw new IllegalArgumentException("Property '" + name
+                    + "' does not exist.");
+        }
+
+        return (property instanceof List) || property.getClass().isArray();
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/beanutils/ConfigurationDynaClass.java b/src/main/java/org/apache/commons/configuration/beanutils/ConfigurationDynaClass.java
new file mode 100644
index 0000000..5bdb1c7
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/beanutils/ConfigurationDynaClass.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.beanutils;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.beanutils.DynaBean;
+import org.apache.commons.beanutils.DynaClass;
+import org.apache.commons.beanutils.DynaProperty;
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * The <tt>ConfigurationDynaClass</tt> dynamically determines properties for
+ * a {@code ConfigurationDynaBean} from a wrapped configuration-collection
+ * {@link org.apache.commons.configuration.Configuration} instance.
+ *
+ * @author <a href="mailto:ricardo.gladwell at btinternet.com">Ricardo Gladwell</a>
+ * @version $Id: ConfigurationDynaClass.java 1366932 2012-07-29 20:06:31Z oheger $
+ * @since 1.0-rc1
+ */
+public class ConfigurationDynaClass implements DynaClass
+{
+    /** The logger.*/
+    private static final Log LOG = LogFactory.getLog(ConfigurationDynaClass.class);
+
+    /** Stores the associated configuration.*/
+    private final Configuration configuration;
+
+    /**
+     * Construct an instance of a {@code ConfigurationDynaClass}
+     * wrapping the specified {@code Configuration} instance.
+     * @param configuration {@code Configuration} instance.
+     */
+    public ConfigurationDynaClass(Configuration configuration)
+    {
+        super();
+        if (LOG.isTraceEnabled())
+        {
+            LOG.trace("ConfigurationDynaClass(" + configuration + ")");
+        }
+        this.configuration = configuration;
+    }
+
+    public DynaProperty getDynaProperty(String name)
+    {
+        if (LOG.isTraceEnabled())
+        {
+            LOG.trace("getDynaProperty(" + name + ")");
+        }
+
+        if (name == null)
+        {
+            throw new IllegalArgumentException("Property name must not be null!");
+        }
+
+        Object value = configuration.getProperty(name);
+        if (value == null)
+        {
+            return null;
+        }
+        else
+        {
+            Class<?> type = value.getClass();
+
+            if (type == Byte.class)
+            {
+                type = Byte.TYPE;
+            }
+            if (type == Character.class)
+            {
+                type = Character.TYPE;
+            }
+            else if (type == Boolean.class)
+            {
+                type = Boolean.TYPE;
+            }
+            else if (type == Double.class)
+            {
+                type = Double.TYPE;
+            }
+            else if (type == Float.class)
+            {
+                type = Float.TYPE;
+            }
+            else if (type == Integer.class)
+            {
+                type = Integer.TYPE;
+            }
+            else if (type == Long.class)
+            {
+                type = Long.TYPE;
+            }
+            else if (type == Short.class)
+            {
+                type = Short.TYPE;
+            }
+
+            return new DynaProperty(name, type);
+        }
+    }
+
+    public DynaProperty[] getDynaProperties()
+    {
+        if (LOG.isTraceEnabled())
+        {
+            LOG.trace("getDynaProperties()");
+        }
+
+        Iterator<String> keys = configuration.getKeys();
+        List<DynaProperty> properties = new ArrayList<DynaProperty>();
+        while (keys.hasNext())
+        {
+            String key = keys.next();
+            DynaProperty property = getDynaProperty(key);
+            properties.add(property);
+        }
+
+        DynaProperty[] propertyArray = new DynaProperty[properties.size()];
+        properties.toArray(propertyArray);
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("Found " + properties.size() + " properties.");
+        }
+
+        return propertyArray;
+    }
+
+    public String getName()
+    {
+        return ConfigurationDynaBean.class.getName();
+    }
+
+    public DynaBean newInstance() throws IllegalAccessException, InstantiationException
+    {
+        return new ConfigurationDynaBean(configuration);
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/beanutils/DefaultBeanFactory.java b/src/main/java/org/apache/commons/configuration/beanutils/DefaultBeanFactory.java
new file mode 100644
index 0000000..a3a501c
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/beanutils/DefaultBeanFactory.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.beanutils;
+
+/**
+ * <p>
+ * The default implementation of the {@code BeanFactory} interface.
+ * </p>
+ * <p>
+ * This class creates beans of arbitrary types using reflection. Each time the
+ * {@code createBean()} method is invoked, a new bean instance is
+ * created. A default bean class is not supported.
+ * </p>
+ * <p>
+ * An instance of this factory class will be set as the default bean factory for
+ * the {@link BeanHelper} class. This means that if not bean
+ * factory is specified in a {@link BeanDeclaration}, this
+ * default instance will be used.
+ * </p>
+ *
+ * @since 1.3
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: DefaultBeanFactory.java 1208758 2011-11-30 20:38:59Z oheger $
+ */
+public class DefaultBeanFactory implements BeanFactory
+{
+    /** Stores the default instance of this class. */
+    public static final DefaultBeanFactory INSTANCE = new DefaultBeanFactory();
+
+    /**
+     * Creates a new bean instance. This implementation delegates to the
+     * protected methods {@code createBeanInstance()} and
+     * {@code initBeanInstance()} for creating and initializing the bean.
+     * This makes it easier for derived classes that need to change specific
+     * functionality of the base class.
+     *
+     * @param beanClass the class of the bean, from which an instance is to be
+     * created
+     * @param data the bean declaration object
+     * @param parameter an additional parameter (ignored by this implementation)
+     * @return the new bean instance
+     * @throws Exception if an error occurs
+     */
+    public Object createBean(Class<?> beanClass, BeanDeclaration data,
+            Object parameter) throws Exception
+    {
+        Object result = createBeanInstance(beanClass, data);
+        initBeanInstance(result, data);
+        return result;
+    }
+
+    /**
+     * Returns the default bean class used by this factory. This is always
+     * <b>null</b> for this implementation.
+     *
+     * @return the default bean class
+     */
+    public Class<?> getDefaultBeanClass()
+    {
+        return null;
+    }
+
+    /**
+     * Creates the bean instance. This method is called by
+     * {@code createBean()}. It uses reflection to create a new instance
+     * of the specified class.
+     *
+     * @param beanClass the class of the bean to be created
+     * @param data the bean declaration
+     * @return the new bean instance
+     * @throws Exception if an error occurs
+     */
+    protected Object createBeanInstance(Class<?> beanClass, BeanDeclaration data)
+            throws Exception
+    {
+        return beanClass.newInstance();
+    }
+
+    /**
+     * Initializes the newly created bean instance. This method is called by
+     * {@code createBean()}. It calls the
+     * {@link BeanHelper#initBean(Object, BeanDeclaration) initBean()}
+     * of {@link BeanHelper} for performing the initialization.
+     *
+     * @param bean the newly created bean instance
+     * @param data the bean declaration object
+     * @throws Exception if an error occurs
+     */
+    protected void initBeanInstance(Object bean, BeanDeclaration data)
+            throws Exception
+    {
+        BeanHelper.initBean(bean, data);
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/beanutils/XMLBeanDeclaration.java b/src/main/java/org/apache/commons/configuration/beanutils/XMLBeanDeclaration.java
new file mode 100644
index 0000000..e3fb5de
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/beanutils/XMLBeanDeclaration.java
@@ -0,0 +1,428 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.beanutils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.configuration.ConfigurationRuntimeException;
+import org.apache.commons.configuration.HierarchicalConfiguration;
+import org.apache.commons.configuration.PropertyConverter;
+import org.apache.commons.configuration.SubnodeConfiguration;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.DefaultConfigurationNode;
+
+/**
+ * <p>
+ * An implementation of the {@code BeanDeclaration} interface that is
+ * suitable for XML configuration files.
+ * </p>
+ * <p>
+ * This class defines the standard layout of a bean declaration in an XML
+ * configuration file. Such a declaration must look like the following example
+ * fragment:
+ * </p>
+ * <p>
+ *
+ * <pre>
+ *   ...
+ *   <personBean config-class="my.model.PersonBean"
+ *       lastName="Doe" firstName="John">
+ *       <address config-class="my.model.AddressBean"
+ *           street="21st street 11" zip="1234"
+ *           city="TestCity"/>
+ *   </personBean>
+ * </pre>
+ *
+ * </p>
+ * <p>
+ * The bean declaration can be contained in an arbitrary element. Here it is the
+ * {@code personBean} element. In the attributes of this element
+ * there can occur some reserved attributes, which have the following meaning:
+ * <dl>
+ * <dt>{@code config-class}</dt>
+ * <dd>Here the full qualified name of the bean's class can be specified. An
+ * instance of this class will be created. If this attribute is not specified,
+ * the bean class must be provided in another way, e.g. as the
+ * {@code defaultClass} passed to the {@code BeanHelper} class.</dd>
+ * <dt>{@code config-factory}</dt>
+ * <dd>This attribute can contain the name of the
+ * {@link BeanFactory} that should be used for creating the bean.
+ * If it is defined, a factory with this name must have been registered at the
+ * {@code BeanHelper} class. If this attribute is missing, the default
+ * bean factory will be used.</dd>
+ * <dt>{@code config-factoryParam}</dt>
+ * <dd>With this attribute a parameter can be specified that will be passed to
+ * the bean factory. This may be useful for custom bean factories.</dd>
+ * </dl>
+ * </p>
+ * <p>
+ * All further attributes starting with the {@code config-} prefix are
+ * considered as meta data and will be ignored. All other attributes are treated
+ * as properties of the bean to be created, i.e. corresponding setter methods of
+ * the bean will be invoked with the values specified here.
+ * </p>
+ * <p>
+ * If the bean to be created has also some complex properties (which are itself
+ * beans), their values cannot be initialized from attributes. For this purpose
+ * nested elements can be used. The example listing shows how an address bean
+ * can be initialized. This is done in a nested element whose name must match
+ * the name of a property of the enclosing bean declaration. The format of this
+ * nested element is exactly the same as for the bean declaration itself, i.e.
+ * it can have attributes defining meta data or bean properties and even further
+ * nested elements for complex bean properties.
+ * </p>
+ * <p>
+ * A {@code XMLBeanDeclaration} object is usually created from a
+ * {@code HierarchicalConfiguration}. From this it will derive a
+ * {@code SubnodeConfiguration}, which is used to access the needed
+ * properties. This subnode configuration can be obtained using the
+ * {@link #getConfiguration()} method. All of its properties can
+ * be accessed in the usual way. To ensure that the property keys used by this
+ * class are understood by the configuration, the default expression engine will
+ * be set.
+ * </p>
+ *
+ * @since 1.3
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: XMLBeanDeclaration.java 1301959 2012-03-17 16:43:18Z oheger $
+ */
+public class XMLBeanDeclaration implements BeanDeclaration
+{
+    /** Constant for the prefix of reserved attributes. */
+    public static final String RESERVED_PREFIX = "config-";
+
+    /** Constant for the prefix for reserved attributes.*/
+    public static final String ATTR_PREFIX = "[@" + RESERVED_PREFIX;
+
+    /** Constant for the bean class attribute. */
+    public static final String ATTR_BEAN_CLASS = ATTR_PREFIX + "class]";
+
+    /** Constant for the bean factory attribute. */
+    public static final String ATTR_BEAN_FACTORY = ATTR_PREFIX + "factory]";
+
+    /** Constant for the bean factory parameter attribute. */
+    public static final String ATTR_FACTORY_PARAM = ATTR_PREFIX
+            + "factoryParam]";
+
+    /** Stores the associated configuration. */
+    private final SubnodeConfiguration configuration;
+
+    /** Stores the configuration node that contains the bean declaration. */
+    private final ConfigurationNode node;
+
+    /**
+     * Creates a new instance of {@code XMLBeanDeclaration} and
+     * initializes it from the given configuration. The passed in key points to
+     * the bean declaration.
+     *
+     * @param config the configuration
+     * @param key the key to the bean declaration (this key must point to
+     * exactly one bean declaration or a {@code IllegalArgumentException}
+     * exception will be thrown)
+     */
+    public XMLBeanDeclaration(HierarchicalConfiguration config, String key)
+    {
+        this(config, key, false);
+    }
+
+    /**
+     * Creates a new instance of {@code XMLBeanDeclaration} and
+     * initializes it from the given configuration. The passed in key points to
+     * the bean declaration. If the key does not exist and the boolean argument
+     * is <b>true</b>, the declaration is initialized with an empty
+     * configuration. It is possible to create objects from such an empty
+     * declaration if a default class is provided. If the key on the other hand
+     * has multiple values or is undefined and the boolean argument is <b>false</b>,
+     * a {@code IllegalArgumentException} exception will be thrown.
+     *
+     * @param config the configuration
+     * @param key the key to the bean declaration
+     * @param optional a flag whether this declaration is optional; if set to
+     * <b>true</b>, no exception will be thrown if the passed in key is
+     * undefined
+     */
+    public XMLBeanDeclaration(HierarchicalConfiguration config, String key,
+            boolean optional)
+    {
+        if (config == null)
+        {
+            throw new IllegalArgumentException(
+                    "Configuration must not be null!");
+        }
+
+        SubnodeConfiguration tmpconfiguration = null;
+        ConfigurationNode tmpnode = null;
+        try
+        {
+            tmpconfiguration = config.configurationAt(key);
+            tmpnode = tmpconfiguration.getRootNode();
+        }
+        catch (IllegalArgumentException iex)
+        {
+            // If we reach this block, the key does not have exactly one value
+            if (!optional || config.getMaxIndex(key) > 0)
+            {
+                throw iex;
+            }
+            tmpconfiguration = config.configurationAt(null);
+            tmpnode = new DefaultConfigurationNode();
+        }
+        this.node = tmpnode;
+        this.configuration = tmpconfiguration;
+        initSubnodeConfiguration(getConfiguration());
+    }
+
+    /**
+     * Creates a new instance of {@code XMLBeanDeclaration} and
+     * initializes it from the given configuration. The configuration's root
+     * node must contain the bean declaration.
+     *
+     * @param config the configuration with the bean declaration
+     */
+    public XMLBeanDeclaration(HierarchicalConfiguration config)
+    {
+        this(config, (String) null);
+    }
+
+    /**
+     * Creates a new instance of {@code XMLBeanDeclaration} and
+     * initializes it with the configuration node that contains the bean
+     * declaration.
+     *
+     * @param config the configuration
+     * @param node the node with the bean declaration.
+     */
+    public XMLBeanDeclaration(SubnodeConfiguration config,
+            ConfigurationNode node)
+    {
+        if (config == null)
+        {
+            throw new IllegalArgumentException(
+                    "Configuration must not be null!");
+        }
+        if (node == null)
+        {
+            throw new IllegalArgumentException("Node must not be null!");
+        }
+
+        this.node = node;
+        configuration = config;
+        initSubnodeConfiguration(config);
+    }
+
+    /**
+     * Returns the configuration object this bean declaration is based on.
+     *
+     * @return the associated configuration
+     */
+    public SubnodeConfiguration getConfiguration()
+    {
+        return configuration;
+    }
+
+    /**
+     * Returns the node that contains the bean declaration.
+     *
+     * @return the configuration node this bean declaration is based on
+     */
+    public ConfigurationNode getNode()
+    {
+        return node;
+    }
+
+    /**
+     * Returns the name of the bean factory. This information is fetched from
+     * the {@code config-factory} attribute.
+     *
+     * @return the name of the bean factory
+     */
+    public String getBeanFactoryName()
+    {
+        return getConfiguration().getString(ATTR_BEAN_FACTORY);
+    }
+
+    /**
+     * Returns a parameter for the bean factory. This information is fetched
+     * from the {@code config-factoryParam} attribute.
+     *
+     * @return the parameter for the bean factory
+     */
+    public Object getBeanFactoryParameter()
+    {
+        return getConfiguration().getProperty(ATTR_FACTORY_PARAM);
+    }
+
+    /**
+     * Returns the name of the class of the bean to be created. This information
+     * is obtained from the {@code config-class} attribute.
+     *
+     * @return the name of the bean's class
+     */
+    public String getBeanClassName()
+    {
+        return getConfiguration().getString(ATTR_BEAN_CLASS);
+    }
+
+    /**
+     * Returns a map with the bean's (simple) properties. The properties are
+     * collected from all attribute nodes, which are not reserved.
+     *
+     * @return a map with the bean's properties
+     */
+    public Map<String, Object> getBeanProperties()
+    {
+        Map<String, Object> props = new HashMap<String, Object>();
+        for (ConfigurationNode attr : getNode().getAttributes())
+        {
+            if (!isReservedNode(attr))
+            {
+                props.put(attr.getName(), interpolate(attr .getValue()));
+            }
+        }
+
+        return props;
+    }
+
+    /**
+     * Returns a map with bean declarations for the complex properties of the
+     * bean to be created. These declarations are obtained from the child nodes
+     * of this declaration's root node.
+     *
+     * @return a map with bean declarations for complex properties
+     */
+    public Map<String, Object> getNestedBeanDeclarations()
+    {
+        Map<String, Object> nested = new HashMap<String, Object>();
+        for (ConfigurationNode child : getNode().getChildren())
+        {
+            if (!isReservedNode(child))
+            {
+                if (nested.containsKey(child.getName()))
+                {
+                    Object obj = nested.get(child.getName());
+                    List<BeanDeclaration> list;
+                    if (obj instanceof List)
+                    {
+                        // Safe because we created the lists ourselves.
+                        @SuppressWarnings("unchecked")
+                        List<BeanDeclaration> tmpList = (List<BeanDeclaration>) obj;
+                        list = tmpList;
+                    }
+                    else
+                    {
+                        list = new ArrayList<BeanDeclaration>();
+                        list.add((BeanDeclaration) obj);
+                        nested.put(child.getName(), list);
+                    }
+                    list.add(createBeanDeclaration(child));
+                }
+                else
+                {
+                    nested.put(child.getName(), createBeanDeclaration(child));
+                }
+            }
+        }
+
+        return nested;
+    }
+
+    /**
+     * Performs interpolation for the specified value. This implementation will
+     * interpolate against the current subnode configuration's parent. If sub
+     * classes need a different interpolation mechanism, they should override
+     * this method.
+     *
+     * @param value the value that is to be interpolated
+     * @return the interpolated value
+     */
+    protected Object interpolate(Object value)
+    {
+        return PropertyConverter.interpolate(value, getConfiguration()
+                .getParent());
+    }
+
+    /**
+     * Checks if the specified node is reserved and thus should be ignored. This
+     * method is called when the maps for the bean's properties and complex
+     * properties are collected. It checks whether the given node is an
+     * attribute node and if its name starts with the reserved prefix.
+     *
+     * @param nd the node to be checked
+     * @return a flag whether this node is reserved (and does not point to a
+     * property)
+     */
+    protected boolean isReservedNode(ConfigurationNode nd)
+    {
+        return nd.isAttribute()
+                && (nd.getName() == null || nd.getName().startsWith(
+                        RESERVED_PREFIX));
+    }
+
+    /**
+     * Creates a new {@code BeanDeclaration} for a child node of the
+     * current configuration node. This method is called by
+     * {@code getNestedBeanDeclarations()} for all complex sub properties
+     * detected by this method. Derived classes can hook in if they need a
+     * specific initialization. This base implementation creates a
+     * {@code XMLBeanDeclaration} that is properly initialized from the
+     * passed in node.
+     *
+     * @param node the child node, for which a {@code BeanDeclaration} is
+     *        to be created
+     * @return the {@code BeanDeclaration} for this child node
+     * @since 1.6
+     */
+    protected BeanDeclaration createBeanDeclaration(ConfigurationNode node)
+    {
+        List<HierarchicalConfiguration> list = getConfiguration().configurationsAt(node.getName());
+        if (list.size() == 1)
+        {
+            return new XMLBeanDeclaration((SubnodeConfiguration) list.get(0), node);
+        }
+        else
+        {
+            Iterator<HierarchicalConfiguration> iter = list.iterator();
+            while (iter.hasNext())
+            {
+                SubnodeConfiguration config = (SubnodeConfiguration) iter.next();
+                if (config.getRootNode().equals(node))
+                {
+                    return new XMLBeanDeclaration(config, node);
+                }
+            }
+            throw new ConfigurationRuntimeException("Unable to match node for " + node.getName());
+        }
+    }
+
+    /**
+     * Initializes the internally managed subnode configuration. This method
+     * will set some default values for some properties.
+     *
+     * @param conf the configuration to initialize
+     */
+    private void initSubnodeConfiguration(SubnodeConfiguration conf)
+    {
+        conf.setThrowExceptionOnMissing(false);
+        conf.setExpressionEngine(null);
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/beanutils/package.html b/src/main/java/org/apache/commons/configuration/beanutils/package.html
new file mode 100644
index 0000000..433cead
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/beanutils/package.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<html>
+<head>
+</head>
+<body>
+
+<p>
+In this package a <code>Configuration</code> implementation can be found that
+implements the <code>DynaBean</code> interface. It allows to access or modify
+a configuration using the classes from the Commons Beanutils package.
+There are also classes for declaring beans in configuration files, from which
+then instances can be created.
+</p>
+<p>
+<font size="-2">$Id: package.html 439648 2006-09-02 20:42:10Z oheger $</font>
+</p>
+
+</body>
+</html>
diff --git a/src/main/java/org/apache/commons/configuration/event/ConfigurationErrorEvent.java b/src/main/java/org/apache/commons/configuration/event/ConfigurationErrorEvent.java
new file mode 100644
index 0000000..1979f72
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/event/ConfigurationErrorEvent.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.event;
+
+/**
+ * <p>
+ * An event class that is used for reporting errors that occurred while
+ * processing configuration properties.
+ * </p>
+ * <p>
+ * Some configuration implementations (e.g.
+ * {@link org.apache.commons.configuration.DatabaseConfiguration}
+ * or {@link org.apache.commons.configuration.JNDIConfiguration}
+ * use an underlying storage that can throw an exception on each property
+ * access. In earlier versions of this library such exceptions were logged and
+ * then silently ignored. This makes it impossible for a client to find out that
+ * something went wrong.
+ * </p>
+ * <p>
+ * To give clients better control over the handling of errors that occur during
+ * access of a configuration object a new event listener mechanism specific for
+ * exceptions is introduced: Clients can register itself at a configuration
+ * object as an <em>error listener</em> and are then notified about all
+ * internal errors related to the source configuration object.
+ * </p>
+ * <p>
+ * By inheriting from {@code ConfigurationEvent} this event class
+ * supports all properties that describe an operation on a configuration
+ * instance. In addition a {@code Throwable} object is available
+ * representing the occurred error. The event's type determines the operation
+ * that caused the error. Note that depending on the event type and the occurred
+ * exception not all of the other properties (e.g. name of the affected property
+ * or its value) may be available.
+ * </p>
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: ConfigurationErrorEvent.java 1207610 2011-11-28 21:06:22Z oheger $
+ * @since 1.4
+ * @see ConfigurationEvent
+ */
+public class ConfigurationErrorEvent extends ConfigurationEvent
+{
+    /**
+     * The serial version UID.
+     */
+    private static final long serialVersionUID = -7433184493062648409L;
+
+    /** Stores the exception that caused this event. */
+    private Throwable cause;
+
+    /**
+     * Creates a new instance of {@code ConfigurationErrorEvent} and
+     * initializes it.
+     *
+     * @param source the event source
+     * @param type the event's type
+     * @param propertyName the name of the affected property
+     * @param propertyValue the value of the affected property
+     * @param cause the exception object that caused this event
+     */
+    public ConfigurationErrorEvent(Object source, int type,
+            String propertyName, Object propertyValue, Throwable cause)
+    {
+        super(source, type, propertyName, propertyValue, true);
+        this.cause = cause;
+    }
+
+    /**
+     * Returns the cause of this error event. This is the {@code Throwable}
+     * object that caused this event to be fired.
+     *
+     * @return the cause of this error event
+     */
+    public Throwable getCause()
+    {
+        return cause;
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/event/ConfigurationErrorListener.java b/src/main/java/org/apache/commons/configuration/event/ConfigurationErrorListener.java
new file mode 100644
index 0000000..2766891
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/event/ConfigurationErrorListener.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.event;
+
+/**
+ * <p>
+ * An event listener interface to be implemented by observers that are
+ * interested in internal errors caused by processing of configuration
+ * properties.
+ * </p>
+ * <p>
+ * Some configuration classes use an underlying storage where each access of a
+ * property can cause an exception. In earlier versions of this library such
+ * exceptions were typically ignored. By implementing this interface and
+ * registering at a configuration object as an error listener it is now possible
+ * for clients to receive notifications about those internal problems.
+ * </p>
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: ConfigurationErrorListener.java 1207610 2011-11-28 21:06:22Z oheger $
+ * @since 1.4
+ * @see ConfigurationErrorEvent
+ */
+public interface ConfigurationErrorListener
+{
+    /**
+     * Notifies this listener that in an observed configuration an error
+     * occurred. All information available about this error, including the
+     * causing {@code Throwable} object, can be obtained from the passed
+     * in event object.
+     *
+     * @param event the event object with information about the error
+     */
+    void configurationError(ConfigurationErrorEvent event);
+}
diff --git a/src/main/java/org/apache/commons/configuration/event/ConfigurationEvent.java b/src/main/java/org/apache/commons/configuration/event/ConfigurationEvent.java
new file mode 100644
index 0000000..2042137
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/event/ConfigurationEvent.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.event;
+
+import java.util.EventObject;
+
+/**
+ * <p>
+ * An event class for reporting updates on a configuration object.
+ * </p>
+ * <p>
+ * Event objects of this type are used for "raw" events, i.e.
+ * unfiltered modifications of any kind. A level with semantically higher events
+ * (e.g. for property changes) may be built on top of this fundamental event
+ * mechanism.
+ * </p>
+ * <p>
+ * Each event can contain the following data:
+ * <ul>
+ * <li>A source object, which is usually the configuration object that was
+ * modified.</li>
+ * <li>The event's type. This is a numeric value that corresponds to constant
+ * declarations in concrete configuration classes. It describes what exactly has
+ * happended.</li>
+ * <li>If available, the name of the property whose modification caused the
+ * event.</li>
+ * <li>If available, the value of the property that caused this event.</li>
+ * <li>A flag whether this event was generated before or after the update of
+ * the source configuration. A modification of a configuration typically causes
+ * two events: one event before and one event after the modification is
+ * performed. This allows event listeners to react at the correct point of time.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * The following standard events are generated by typical configuration
+ * implementations (the constants for the event types are defined in
+ * {@link org.apache.commons.configuration.AbstractConfiguration}):
+ * <dl>
+ * <dt>EVENT_ADD_PROPERTY</dt>
+ * <dd>This event is triggered for each call of the {@code addProperty()}
+ * method of a configuration object. It contains the name of the property, to
+ * which new data is added, and the value object that is added to this property
+ * (this may be an array or a list if multiple values are added).</dd>
+ * <dt>EVENT_SET_PROPERTY</dt>
+ * <dd>Calling the {@code setProperty()} method triggers this event. The
+ * event object stores the name of the affected property and its new value.</dd>
+ * <dt>EVENT_CLEAR_PROPERTY</dt>
+ * <dd>If a property is removed from a configuration (by calling the
+ * {@code clearProperty()} method), an event of this type is fired. In
+ * this case the event object only stores the name of the removed property, the
+ * value is <b>null</b>.</dd>
+ * <dt>EVENT_CLEAR</dt>
+ * <dd>This event is fired when the whole configuration is cleared. The
+ * corresponding event object contains no additional data.</dd>
+ * </dl>
+ * </p>
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: ConfigurationEvent.java 1207610 2011-11-28 21:06:22Z oheger $
+ * @since 1.3
+ */
+public class ConfigurationEvent extends EventObject
+{
+    /**
+     * The serial version UID.
+     */
+    private static final long serialVersionUID = 3277238219073504136L;
+
+    /** Stores the event type. */
+    private int type;
+
+    /** Stores the property name. */
+    private String propertyName;
+
+    /** Stores the property value. */
+    private Object propertyValue;
+
+    /** Stores the before update flag. */
+    private boolean beforeUpdate;
+
+    /**
+     * Creates a new instance of {@code ConfigurationEvent} and
+     * initializes it.
+     *
+     * @param source the event source
+     * @param type the event's type
+     * @param propertyName the name of the affected property
+     * @param propertyValue the value of the affected property
+     * @param beforeUpdate the before update flag
+     */
+    public ConfigurationEvent(Object source, int type, String propertyName,
+            Object propertyValue, boolean beforeUpdate)
+    {
+        super(source);
+        this.type = type;
+        this.propertyName = propertyName;
+        this.propertyValue = propertyValue;
+        this.beforeUpdate = beforeUpdate;
+    }
+
+    /**
+     * Returns the name of the affected property. This can be <b>null</b> if no
+     * property change has lead to this event.
+     *
+     * @return the name of the property
+     */
+    public String getPropertyName()
+    {
+        return propertyName;
+    }
+
+    /**
+     * Returns the value of the affected property if available.
+     *
+     * @return the value of the property; can be <b>null</b>
+     */
+    public Object getPropertyValue()
+    {
+        return propertyValue;
+    }
+
+    /**
+     * Returns the type of this event. This describes the update process that
+     * caused this event.
+     *
+     * @return the event's type
+     */
+    public int getType()
+    {
+        return type;
+    }
+
+    /**
+     * Returns a flag if this event was generated before or after an update.
+     *
+     * @return <b>true</b> if this event was generated before an update;
+     * <b>false</b> otherwise
+     */
+    public boolean isBeforeUpdate()
+    {
+        return beforeUpdate;
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/event/ConfigurationListener.java b/src/main/java/org/apache/commons/configuration/event/ConfigurationListener.java
new file mode 100644
index 0000000..9e2e907
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/event/ConfigurationListener.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.event;
+
+/**
+ * <p>
+ * A simple event listener interface for configuration observers.
+ * </p>
+ * <p>
+ * This interface can be implemented by classes that are interested in
+ * "raw" events caused by configuration objects. Each manipulation on
+ * a configuration object will generate such an event. There is only a single
+ * method that is invoked when an event occurs.
+ * </p>
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: ConfigurationListener.java 561230 2007-07-31 04:17:09Z rahul $
+ * @since 1.3
+ */
+public interface ConfigurationListener
+{
+    /**
+     * Notifies this listener about a manipulation on a monitored configuration
+     * object.
+     *
+     * @param event the event describing the manipulation
+     */
+    void configurationChanged(ConfigurationEvent event);
+}
diff --git a/src/main/java/org/apache/commons/configuration/event/EventSource.java b/src/main/java/org/apache/commons/configuration/event/EventSource.java
new file mode 100644
index 0000000..7074c5c
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/event/EventSource.java
@@ -0,0 +1,364 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.event;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * <p>
+ * A base class for objects that can generate configuration events.
+ * </p>
+ * <p>
+ * This class implements functionality for managing a set of event listeners
+ * that can be notified when an event occurs. It can be extended by
+ * configuration classes that support the event mechanism. In this case these
+ * classes only need to call the {@code fireEvent()} method when an event
+ * is to be delivered to the registered listeners.
+ * </p>
+ * <p>
+ * Adding and removing event listeners can happen concurrently to manipulations
+ * on a configuration that cause events. The operations are synchronized.
+ * </p>
+ * <p>
+ * With the {@code detailEvents} property the number of detail events can
+ * be controlled. Some methods in configuration classes are implemented in a way
+ * that they call other methods that can generate their own events. One example
+ * is the {@code setProperty()} method that can be implemented as a
+ * combination of {@code clearProperty()} and {@code addProperty()}.
+ * With {@code detailEvents} set to <b>true</b>, all involved methods
+ * will generate events (i.e. listeners will receive property set events,
+ * property clear events, and property add events). If this mode is turned off
+ * (which is the default), detail events are suppressed, so only property set
+ * events will be received. Note that the number of received detail events may
+ * differ for different configuration implementations.
+ * {@link org.apache.commons.configuration.HierarchicalConfiguration HierarchicalConfiguration}
+ * for instance has a custom implementation of {@code setProperty()},
+ * which does not generate any detail events.
+ * </p>
+ * <p>
+ * In addition to "normal" events, error events are supported. Such
+ * events signal an internal problem that occurred during access of properties.
+ * For them a special listener interface exists:
+ * {@link ConfigurationErrorListener}. There is another set of
+ * methods dealing with event listeners of this type. The
+ * {@code fireError()} method can be used by derived classes to send
+ * notifications about errors to registered observers.
+ * </p>
+ *
+ * @author <a href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a>
+ * @version $Id: EventSource.java 1234617 2012-01-22 21:31:01Z oheger $
+ * @since 1.3
+ */
+public class EventSource
+{
+    /** A collection for the registered event listeners. */
+    private Collection<ConfigurationListener> listeners;
+
+    /** A collection for the registered error listeners.*/
+    private Collection<ConfigurationErrorListener> errorListeners;
+
+    /** A lock object for guarding access to the detail events counter. */
+    private final Object lockDetailEventsCount = new Object();
+
+    /** A counter for the detail events. */
+    private int detailEvents;
+
+    /**
+     * Creates a new instance of {@code EventSource}.
+     */
+    public EventSource()
+    {
+        initListeners();
+    }
+
+    /**
+     * Adds a configuration listener to this object.
+     *
+     * @param l the listener to add
+     */
+    public void addConfigurationListener(ConfigurationListener l)
+    {
+        checkListener(l);
+        listeners.add(l);
+    }
+
+    /**
+     * Removes the specified event listener so that it does not receive any
+     * further events caused by this object.
+     *
+     * @param l the listener to be removed
+     * @return a flag whether the event listener was found
+     */
+    public boolean removeConfigurationListener(ConfigurationListener l)
+    {
+        return listeners.remove(l);
+    }
+
+    /**
+     * Returns a collection with all configuration event listeners that are
+     * currently registered at this object.
+     *
+     * @return a collection with the registered
+     * {@code ConfigurationListener}s (this collection is a snapshot
+     * of the currently registered listeners; manipulating it has no effect
+     * on this event source object)
+     */
+    public Collection<ConfigurationListener> getConfigurationListeners()
+    {
+        return Collections.unmodifiableCollection(new ArrayList<ConfigurationListener>(listeners));
+    }
+
+    /**
+     * Removes all registered configuration listeners.
+     */
+    public void clearConfigurationListeners()
+    {
+        listeners.clear();
+    }
+
+    /**
+     * Returns a flag whether detail events are enabled.
+     *
+     * @return a flag if detail events are generated
+     */
+    public boolean isDetailEvents()
+    {
+        return checkDetailEvents(0);
+    }
+
+    /**
+     * Determines whether detail events should be generated. If enabled, some
+     * methods can generate multiple update events. Note that this method
+     * records the number of calls, i.e. if for instance
+     * {@code setDetailEvents(false)} was called three times, you will
+     * have to invoke the method as often to enable the details.
+     *
+     * @param enable a flag if detail events should be enabled or disabled
+     */
+    public void setDetailEvents(boolean enable)
+    {
+        synchronized (lockDetailEventsCount)
+        {
+            if (enable)
+            {
+                detailEvents++;
+            }
+            else
+            {
+                detailEvents--;
+            }
+        }
+    }
+
+    /**
+     * Adds a new configuration error listener to this object. This listener
+     * will then be notified about internal problems.
+     *
+     * @param l the listener to register (must not be <b>null</b>)
+     * @since 1.4
+     */
+    public void addErrorListener(ConfigurationErrorListener l)
+    {
+        checkListener(l);
+        errorListeners.add(l);
+    }
+
+    /**
+     * Removes the specified error listener so that it does not receive any
+     * further events caused by this object.
+     *
+     * @param l the listener to remove
+     * @return a flag whether the listener could be found and removed
+     * @since 1.4
+     */
+    public boolean removeErrorListener(ConfigurationErrorListener l)
+    {
+        return errorListeners.remove(l);
+    }
+
+    /**
+     * Removes all registered error listeners.
+     *
+     * @since 1.4
+     */
+    public void clearErrorListeners()
+    {
+        errorListeners.clear();
+    }
+
+    /**
+     * Returns a collection with all configuration error listeners that are
+     * currently registered at this object.
+     *
+     * @return a collection with the registered
+     * {@code ConfigurationErrorListener}s (this collection is a
+     * snapshot of the currently registered listeners; it cannot be manipulated)
+     * @since 1.4
+     */
+    public Collection<ConfigurationErrorListener> getErrorListeners()
+    {
+        return Collections.unmodifiableCollection(new ArrayList<ConfigurationErrorListener>(errorListeners));
+    }
+
+    /**
+     * Creates an event object and delivers it to all registered event
+     * listeners. The method will check first if sending an event is allowed
+     * (making use of the {@code detailEvents} property), and if
+     * listeners are registered.
+     *
+     * @param type the event's type
+     * @param propName the name of the affected property (can be <b>null</b>)
+     * @param propValue the value of the affected property (can be <b>null</b>)
+     * @param before the before update flag
+     */
+    protected void fireEvent(int type, String propName, Object propValue, boolean before)
+    {
+        if (checkDetailEvents(-1))
+        {
+            Iterator<ConfigurationListener> it = listeners.iterator();
+            if (it.hasNext())
+            {
+                ConfigurationEvent event =
+                        createEvent(type, propName, propValue, before);
+                while (it.hasNext())
+                {
+                    it.next().configurationChanged(event);
+                }
+            }
+        }
+    }
+
+    /**
+     * Creates a {@code ConfigurationEvent} object based on the passed in
+     * parameters. This is called by {@code fireEvent()} if it decides
+     * that an event needs to be generated.
+     *
+     * @param type the event's type
+     * @param propName the name of the affected property (can be <b>null</b>)
+     * @param propValue the value of the affected property (can be <b>null</b>)
+     * @param before the before update flag
+     * @return the newly created event object
+     */
+    protected ConfigurationEvent createEvent(int type, String propName, Object propValue, boolean before)
+    {
+        return new ConfigurationEvent(this, type, propName, propValue, before);
+    }
+
+    /**
+     * Creates an error event object and delivers it to all registered error
+     * listeners.
+     *
+     * @param type the event's type
+     * @param propName the name of the affected property (can be <b>null</b>)
+     * @param propValue the value of the affected property (can be <b>null</b>)
+     * @param ex the {@code Throwable} object that caused this error event
+     * @since 1.4
+     */
+    protected void fireError(int type, String propName, Object propValue, Throwable ex)
+    {
+        Iterator<ConfigurationErrorListener> it = errorListeners.iterator();
+        if (it.hasNext())
+        {
+            ConfigurationErrorEvent event =
+                    createErrorEvent(type, propName, propValue, ex);
+            while (it.hasNext())
+            {
+                it.next().configurationError(event);
+            }
+        }
+    }
+
+    /**
+     * Creates a {@code ConfigurationErrorEvent} object based on the
+     * passed in parameters. This is called by {@code fireError()} if it
+     * decides that an event needs to be generated.
+     *
+     * @param type the event's type
+     * @param propName the name of the affected property (can be <b>null</b>)
+     * @param propValue the value of the affected property (can be <b>null</b>)
+     * @param ex the {@code Throwable} object that caused this error
+     * event
+     * @return the event object
+     * @since 1.4
+     */
+    protected ConfigurationErrorEvent createErrorEvent(int type, String propName, Object propValue, Throwable ex)
+    {
+        return new ConfigurationErrorEvent(this, type, propName, propValue, ex);
+    }
+
+    /**
+     * Overrides the {@code clone()} method to correctly handle so far
+     * registered event listeners. This implementation ensures that the clone
+     * will have empty event listener lists, i.e. the listeners registered at an
+     * {@code EventSource} object will not be copied.
+     *
+     * @return the cloned object
+     * @throws CloneNotSupportedException if cloning is not allowed
+     * @since 1.4
+     */
+    @Override
+    protected Object clone() throws CloneNotSupportedException
+    {
+        EventSource copy = (EventSource) super.clone();
+        copy.initListeners();
+        return copy;
+    }
+
+    /**
+     * Checks whether the specified event listener is not <b>null</b>. If this
+     * is the case, an {@code IllegalArgumentException} exception is thrown.
+     *
+     * @param l the listener to be checked
+     * @throws IllegalArgumentException if the listener is <b>null</b>
+     */
+    private static void checkListener(Object l)
+    {
+        if (l == null)
+        {
+            throw new IllegalArgumentException("Listener must not be null!");
+        }
+    }
+
+    /**
+     * Initializes the collections for storing registered event listeners.
+     */
+    private void initListeners()
+    {
+        listeners = new CopyOnWriteArrayList<ConfigurationListener>();
+        errorListeners = new CopyOnWriteArrayList<ConfigurationErrorListener>();
+    }
+
+    /**
+     * Helper method for checking the current counter for detail events. This
+     * method checks whether the counter is greater than the passed in limit.
+     *
+     * @param limit the limit to be compared to
+     * @return <b>true</b> if the counter is greater than the limit,
+     *         <b>false</b> otherwise
+     */
+    private boolean checkDetailEvents(int limit)
+    {
+        synchronized (lockDetailEventsCount)
+        {
+            return detailEvents > limit;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/event/package.html b/src/main/java/org/apache/commons/configuration/event/package.html
new file mode 100644
index 0000000..bf29ee8
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/event/package.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<html>
+<head>
+</head>
+<body>
+
+<p>
+This package contains interfaces and classes for receiving notifications
+about changes at configurations.
+</p>
+<p>
+<font size="-2">$Id: package.html 439648 2006-09-02 20:42:10Z oheger $</font>
+</p>
+
+</body>
+</html>
diff --git a/src/main/java/org/apache/commons/configuration/interpol/ConfigurationInterpolator.java b/src/main/java/org/apache/commons/configuration/interpol/ConfigurationInterpolator.java
new file mode 100644
index 0000000..c22d866
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/interpol/ConfigurationInterpolator.java
@@ -0,0 +1,389 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.interpol;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.text.StrLookup;
+
+/**
+ * <p>
+ * A class that handles interpolation (variable substitution) for configuration
+ * objects.
+ * </p>
+ * <p>
+ * Each instance of {@code AbstractConfiguration} is associated with an
+ * object of this class. All interpolation tasks are delegated to this object.
+ * </p>
+ * <p>
+ * {@code ConfigurationInterpolator} works together with the
+ * {@code StrSubstitutor} class from <a
+ * href="http://commons.apache.org/lang">Commons Lang</a>. By extending
+ * {@code StrLookup} it is able to provide values for variables that
+ * appear in expressions.
+ * </p>
+ * <p>
+ * The basic idea of this class is that it can maintain a set of primitive
+ * {@code StrLookup} objects, each of which is identified by a special
+ * prefix. The variables to be processed have the form
+ * <code>${prefix:name}</code>. {@code ConfigurationInterpolator} will
+ * extract the prefix and determine, which primitive lookup object is registered
+ * for it. Then the name of the variable is passed to this object to obtain the
+ * actual value. It is also possible to define a default lookup object, which
+ * will be used for variables that do not have a prefix or that cannot be
+ * resolved by their associated lookup object.
+ * </p>
+ * <p>
+ * When a new instance of this class is created it is initialized with a default
+ * set of primitive lookup objects. This set can be customized using the static
+ * methods {@code registerGlobalLookup()} and
+ * {@code deregisterGlobalLookup()}. Per default it contains the
+ * following standard lookup objects:
+ * </p>
+ * <p>
+ * <table border="1">
+ * <tr>
+ * <th>Prefix</th>
+ * <th>Lookup object</th>
+ * </tr>
+ * <tr>
+ * <td valign="top">sys</td>
+ * <td>With this prefix a lookup object is associated that is able to resolve
+ * system properties.</td>
+ * </tr>
+ * <tr>
+ * <td valign="top">const</td>
+ * <td>The {@code const} prefix indicates that a variable is to be
+ * interpreted as a constant member field of a class (i.e. a field with the
+ * <b>static final</b> modifiers). The name of the variable must be of the form
+ * {@code <full qualified class name>.<field name>}, e.g.
+ * {@code org.apache.commons.configuration.interpol.ConfigurationInterpolator.PREFIX_CONSTANTS}.
+ * </td>
+ * </tr>
+ * </table>
+ * </p>
+ * <p>
+ * After an instance has been created the current set of lookup objects can be
+ * modified using the {@code registerLookup()} and
+ * {@code deregisterLookup()} methods. The default lookup object (that is
+ * invoked for variables without a prefix) can be set with the
+ * {@code setDefaultLookup()} method. (If a
+ * {@code ConfigurationInterpolator} instance is created by a
+ * configuration object, this lookup points to the configuration itself, so that
+ * variables are resolved using the configuration's properties. This ensures
+ * backward compatibility to earlier version of Commons Configuration.)
+ * </p>
+ * <p>
+ * Implementation node: Instances of this class are not thread-safe related to
+ * modifications of their current set of registered lookup objects. It is
+ * intended that each instance is associated with a single
+ * {@code Configuration} object and used for its interpolation tasks.
+ * </p>
+ *
+ * @version $Id: ConfigurationInterpolator.java 1295276 2012-02-29 21:11:35Z oheger $
+ * @since 1.4
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ */
+public class ConfigurationInterpolator extends StrLookup
+{
+    /**
+     * Constant for the prefix of the standard lookup object for resolving
+     * system properties.
+     */
+    public static final String PREFIX_SYSPROPERTIES = "sys";
+
+    /**
+     * Constant for the prefix of the standard lookup object for resolving
+     * constant values.
+     */
+    public static final String PREFIX_CONSTANTS = "const";
+
+    /**
+     * Constant for the prefix of the standard lookup object for resolving
+     * environment properties.
+     * @since 1.7
+     */
+    public static final String PREFIX_ENVIRONMENT = "env";
+
+    /** Constant for the prefix separator. */
+    private static final char PREFIX_SEPARATOR = ':';
+
+    /** A map with the globally registered lookup objects. */
+    private static Map<String, StrLookup> globalLookups;
+
+    /** A map with the locally registered lookup objects. */
+    private Map<String, StrLookup> localLookups;
+
+    /** Stores the default lookup object. */
+    private StrLookup defaultLookup;
+
+    /** Stores a parent interpolator objects if the interpolator is nested hierarchically. */
+    private ConfigurationInterpolator parentInterpolator;
+
+    /**
+     * Creates a new instance of {@code ConfigurationInterpolator}.
+     */
+    public ConfigurationInterpolator()
+    {
+        synchronized (globalLookups)
+        {
+            localLookups = new HashMap<String, StrLookup>(globalLookups);
+        }
+    }
+
+    /**
+     * Registers the given lookup object for the specified prefix globally. This
+     * means that all instances that are created later will use this lookup
+     * object for this prefix. If for this prefix a lookup object is already
+     * registered, the new lookup object will replace the old one. Note that the
+     * lookup objects registered here will be shared between multiple clients.
+     * So they should be thread-safe.
+     *
+     * @param prefix the variable prefix (must not be <b>null</b>)
+     * @param lookup the lookup object to be used for this prefix (must not be
+     * <b>null</b>)
+     */
+    public static void registerGlobalLookup(String prefix, StrLookup lookup)
+    {
+        if (prefix == null)
+        {
+            throw new IllegalArgumentException(
+                    "Prefix for lookup object must not be null!");
+        }
+        if (lookup == null)
+        {
+            throw new IllegalArgumentException(
+                    "Lookup object must not be null!");
+        }
+        synchronized (globalLookups)
+        {
+            globalLookups.put(prefix, lookup);
+        }
+    }
+
+    /**
+     * Deregisters the global lookup object for the specified prefix. This means
+     * that this lookup object won't be available for later created instances
+     * any more. For already existing instances this operation does not have any
+     * impact.
+     *
+     * @param prefix the variable prefix
+     * @return a flag whether for this prefix a lookup object had been
+     * registered
+     */
+    public static boolean deregisterGlobalLookup(String prefix)
+    {
+        synchronized (globalLookups)
+        {
+            return globalLookups.remove(prefix) != null;
+        }
+    }
+
+    /**
+     * Registers the given lookup object for the specified prefix at this
+     * instance. From now on this lookup object will be used for variables that
+     * have the specified prefix.
+     *
+     * @param prefix the variable prefix (must not be <b>null</b>)
+     * @param lookup the lookup object to be used for this prefix (must not be
+     * <b>null</b>)
+     */
+    public void registerLookup(String prefix, StrLookup lookup)
+    {
+        if (prefix == null)
+        {
+            throw new IllegalArgumentException(
+                    "Prefix for lookup object must not be null!");
+        }
+        if (lookup == null)
+        {
+            throw new IllegalArgumentException(
+                    "Lookup object must not be null!");
+        }
+        localLookups.put(prefix, lookup);
+    }
+
+    /**
+     * Deregisters the lookup object for the specified prefix at this instance.
+     * It will be removed from this instance.
+     *
+     * @param prefix the variable prefix
+     * @return a flag whether for this prefix a lookup object had been
+     * registered
+     */
+    public boolean deregisterLookup(String prefix)
+    {
+        return localLookups.remove(prefix) != null;
+    }
+
+    /**
+     * Returns a set with the prefixes, for which lookup objects are registered
+     * at this instance. This means that variables with these prefixes can be
+     * processed.
+     *
+     * @return a set with the registered variable prefixes
+     */
+    public Set<String> prefixSet()
+    {
+        return localLookups.keySet();
+    }
+
+    /**
+     * Returns the default lookup object.
+     *
+     * @return the default lookup object
+     */
+    public StrLookup getDefaultLookup()
+    {
+        return defaultLookup;
+    }
+
+    /**
+     * Sets the default lookup object. This lookup object will be used for all
+     * variables without a special prefix. If it is set to <b>null</b>, such
+     * variables won't be processed.
+     *
+     * @param defaultLookup the new default lookup object
+     */
+    public void setDefaultLookup(StrLookup defaultLookup)
+    {
+        this.defaultLookup = defaultLookup;
+    }
+
+    /**
+     * Resolves the specified variable. This implementation will try to extract
+     * a variable prefix from the given variable name (the first colon (':') is
+     * used as prefix separator). It then passes the name of the variable with
+     * the prefix stripped to the lookup object registered for this prefix. If
+     * no prefix can be found or if the associated lookup object cannot resolve
+     * this variable, the default lookup object will be used.
+     *
+     * @param var the name of the variable whose value is to be looked up
+     * @return the value of this variable or <b>null</b> if it cannot be
+     * resolved
+     */
+    @Override
+    public String lookup(String var)
+    {
+        if (var == null)
+        {
+            return null;
+        }
+
+        int prefixPos = var.indexOf(PREFIX_SEPARATOR);
+        if (prefixPos >= 0)
+        {
+            String prefix = var.substring(0, prefixPos);
+            String name = var.substring(prefixPos + 1);
+            String value = fetchLookupForPrefix(prefix).lookup(name);
+            if (value == null && getParentInterpolator() != null)
+            {
+                value = getParentInterpolator().lookup(name);
+            }
+            if (value != null)
+            {
+                return value;
+            }
+        }
+        String value = fetchNoPrefixLookup().lookup(var);
+        if (value == null && getParentInterpolator() != null)
+        {
+            value = getParentInterpolator().lookup(var);
+        }
+        return value;
+    }
+
+    /**
+     * Returns the lookup object to be used for variables without a prefix. This
+     * implementation will check whether a default lookup object was set. If
+     * this is the case, it will be returned. Otherwise a <b>null</b> lookup
+     * object will be returned (never <b>null</b>).
+     *
+     * @return the lookup object to be used for variables without a prefix
+     */
+    protected StrLookup fetchNoPrefixLookup()
+    {
+        return (getDefaultLookup() != null) ? getDefaultLookup() : StrLookup.noneLookup();
+    }
+
+    /**
+     * Obtains the lookup object for the specified prefix. This method is called
+     * by the {@code lookup()} method. This implementation will check
+     * whether a lookup object is registered for the given prefix. If not, a
+     * <b>null</b> lookup object will be returned (never <b>null</b>).
+     *
+     * @param prefix the prefix
+     * @return the lookup object to be used for this prefix
+     */
+    protected StrLookup fetchLookupForPrefix(String prefix)
+    {
+        StrLookup lookup = localLookups.get(prefix);
+        if (lookup == null)
+        {
+            lookup = StrLookup.noneLookup();
+        }
+        return lookup;
+    }
+
+    /**
+     * Registers the local lookup instances for the given interpolator.
+     *
+     * @param interpolator the instance receiving the local lookups
+     * @since upcoming
+     */
+    public void registerLocalLookups(ConfigurationInterpolator interpolator)
+    {
+        interpolator.localLookups.putAll(localLookups);
+    }
+
+    /**
+     * Sets the parent interpolator. This object is used if the interpolation is nested
+     * hierarchically and the current interpolation object cannot resolve a variable.
+     *
+     * @param parentInterpolator the parent interpolator object or <b>null</b>
+     * @since upcoming
+     */
+    public void setParentInterpolator(ConfigurationInterpolator parentInterpolator)
+    {
+        this.parentInterpolator = parentInterpolator;
+    }
+
+    /**
+     * Requests the parent interpolator. This object is used if the interpolation is nested
+     * hierarchically and the current interpolation
+     *
+     * @return the parent interpolator or <b>null</b>
+     * @since upcoming
+     */
+    public ConfigurationInterpolator getParentInterpolator()
+    {
+        return this.parentInterpolator;
+    }
+
+    // static initializer, sets up the map with the standard lookups
+    static
+    {
+        globalLookups = new HashMap<String, StrLookup>();
+        globalLookups.put(PREFIX_SYSPROPERTIES, StrLookup.systemPropertiesLookup());
+        globalLookups.put(PREFIX_CONSTANTS, new ConstantLookup());
+        globalLookups.put(PREFIX_ENVIRONMENT, new EnvironmentLookup());
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/interpol/ConstantLookup.java b/src/main/java/org/apache/commons/configuration/interpol/ConstantLookup.java
new file mode 100644
index 0000000..9d9c189
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/interpol/ConstantLookup.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.interpol;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.lang.ClassUtils;
+import org.apache.commons.lang.text.StrLookup;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <p>
+ * A specialized lookup implementation that allows access to constant fields of
+ * classes.
+ * </p>
+ * <p>
+ * Sometimes it is necessary in a configuration file to refer to a constant
+ * defined in a class. This can be done with this lookup implementation.
+ * Variable names passed in must be of the form
+ * {@code mypackage.MyClass.FIELD}. The {@code lookup()} method
+ * will split the passed in string at the last dot, separating the fully
+ * qualified class name and the name of the constant (i.e. <strong>static final</strong>)
+ * member field. Then the class is loaded and the field's value is obtained
+ * using reflection.
+ * </p>
+ * <p>
+ * Once retrieved values are cached for fast access. This class is thread-safe.
+ * It can be used as a standard (i.e. global) lookup object and serve multiple
+ * clients concurrently.
+ * </p>
+ *
+ * @version $Id: ConstantLookup.java 1210218 2011-12-04 20:57:48Z oheger $
+ * @since 1.4
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ */
+public class ConstantLookup extends StrLookup
+{
+    /** Constant for the field separator. */
+    private static final char FIELD_SEPRATOR = '.';
+
+    /** An internally used cache for already retrieved values. */
+    private static Map<String, String> constantCache = new HashMap<String, String>();
+
+    /** The logger. */
+    private Log log = LogFactory.getLog(getClass());
+
+    /**
+     * Tries to resolve the specified variable. The passed in variable name is
+     * interpreted as the name of a <b>static final</b> member field of a
+     * class. If the value has already been obtained, it can be retrieved from
+     * an internal cache. Otherwise this method will invoke the
+     * {@code resolveField()} method and pass in the name of the class
+     * and the field.
+     *
+     * @param var the name of the variable to be resolved
+     * @return the value of this variable or <b>null</b> if it cannot be
+     * resolved
+     */
+    @Override
+    public String lookup(String var)
+    {
+        if (var == null)
+        {
+            return null;
+        }
+
+        String result;
+        synchronized (constantCache)
+        {
+            result = constantCache.get(var);
+        }
+        if (result != null)
+        {
+            return result;
+        }
+
+        int fieldPos = var.lastIndexOf(FIELD_SEPRATOR);
+        if (fieldPos < 0)
+        {
+            return null;
+        }
+        try
+        {
+            Object value = resolveField(var.substring(0, fieldPos), var
+                    .substring(fieldPos + 1));
+            if (value != null)
+            {
+                synchronized (constantCache)
+                {
+                    // In worst case, the value will be fetched multiple times
+                    // because of this lax synchronisation, but for constant
+                    // values this shouldn't be a problem.
+                    constantCache.put(var, String.valueOf(value));
+                }
+                result = value.toString();
+            }
+        }
+        catch (Exception ex)
+        {
+            log.warn("Could not obtain value for variable " + var, ex);
+        }
+
+        return result;
+    }
+
+    /**
+     * Clears the shared cache with the so far resolved constants.
+     */
+    public static void clear()
+    {
+        synchronized (constantCache)
+        {
+            constantCache.clear();
+        }
+    }
+
+    /**
+     * Determines the value of the specified constant member field of a class.
+     * This implementation will call {@code fetchClass()} to obtain the
+     * {@code java.lang.Class} object for the target class. Then it will
+     * use reflection to obtain the field's value. For this to work the field
+     * must be accessable.
+     *
+     * @param className the name of the class
+     * @param fieldName the name of the member field of that class to read
+     * @return the field's value
+     * @throws Exception if an error occurs
+     */
+    protected Object resolveField(String className, String fieldName)
+            throws Exception
+    {
+        Class<?> clazz = fetchClass(className);
+        Field field = clazz.getField(fieldName);
+        return field.get(null);
+    }
+
+    /**
+     * Loads the class with the specified name. If an application has special
+     * needs regarding the class loaders to be used, it can hook in here. This
+     * implementation delegates to the {@code getClass()} method of
+     * Commons Lang's
+     * <code><a href="http://commons.apache.org/lang/api-release/org/apache/commons/lang/ClassUtils.html">
+     * ClassUtils</a></code>.
+     *
+     * @param className the name of the class to be loaded
+     * @return the corresponding class object
+     * @throws ClassNotFoundException if the class cannot be loaded
+     */
+    protected Class<?> fetchClass(String className) throws ClassNotFoundException
+    {
+        return ClassUtils.getClass(className);
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/interpol/EnvironmentLookup.java b/src/main/java/org/apache/commons/configuration/interpol/EnvironmentLookup.java
new file mode 100644
index 0000000..85fcedc
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/interpol/EnvironmentLookup.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.interpol;
+
+import org.apache.commons.configuration.EnvironmentConfiguration;
+import org.apache.commons.lang.text.StrLookup;
+
+/**
+ * <p>
+ * A specialized lookup implementation that allows access to environment
+ * variables.
+ * </p>
+ * <p>
+ * This implementation relies on {@link EnvironmentConfiguration} to resolve
+ * environment variables. It can be used for referencing environment variables
+ * in configuration files in an easy way, for instance:
+ *
+ * <pre>
+ * java.home = ${env:JAVA_HOME}
+ * </pre>
+ *
+ * </p>
+ * <p>
+ * {@code EnvironmentLookup} is one of the standard lookups that is
+ * registered per default for each configuration.
+ * </p>
+ *
+ * @author <a
+ *         href="http://commons.apache.org/configuration/team-list.html">Commons
+ *         Configuration team</a>
+ * @since 1.7
+ * @version $Id: EnvironmentLookup.java 1210620 2011-12-05 20:57:31Z oheger $
+ */
+public class EnvironmentLookup extends StrLookup
+{
+    /** Stores the underlying {@code EnvironmentConfiguration}. */
+    private final EnvironmentConfiguration environmentConfig = new EnvironmentConfiguration();
+
+    /**
+     * Performs a lookup for the specified variable. This implementation
+     * directly delegates to a {@code EnvironmentConfiguration}.
+     *
+     * @param key the key to lookup
+     * @return the value of this key or <b>null</b> if it cannot be resolved
+     */
+    @Override
+    public String lookup(String key)
+    {
+        return environmentConfig.getString(key);
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/interpol/ExprLookup.java b/src/main/java/org/apache/commons/configuration/interpol/ExprLookup.java
new file mode 100644
index 0000000..88e4dbd
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/interpol/ExprLookup.java
@@ -0,0 +1,334 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.interpol;
+
+import java.util.ArrayList;
+
+import org.apache.commons.configuration.AbstractConfiguration;
+import org.apache.commons.configuration.ConfigurationRuntimeException;
+import org.apache.commons.jexl2.Expression;
+import org.apache.commons.jexl2.JexlContext;
+import org.apache.commons.jexl2.JexlEngine;
+import org.apache.commons.jexl2.MapContext;
+import org.apache.commons.lang.ClassUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.text.StrLookup;
+import org.apache.commons.lang.text.StrSubstitutor;
+
+/**
+ * Lookup that allows expressions to be evaluated.
+ *
+ * <pre>
+ *     ExprLookup.Variables vars = new ExprLookup.Variables();
+ *     vars.add(new ExprLookup.Variable("String", org.apache.commons.lang.StringUtils.class));
+ *     vars.add(new ExprLookup.Variable("Util", new Utility("Hello")));
+ *     vars.add(new ExprLookup.Variable("System", "Class:java.lang.System"));
+ *     XMLConfiguration config = new XMLConfiguration(TEST_FILE);
+ *     config.setLogger(log);
+ *     ExprLookup lookup = new ExprLookup(vars);
+ *     lookup.setConfiguration(config);
+ *     String str = lookup.lookup("'$[element] ' + String.trimToEmpty('$[space.description]')");
+ * </pre>
+ *
+ * In the example above TEST_FILE contains xml that looks like:
+ * <pre>
+ * <configuration>
+ *   <element>value</element>
+ *   <space xml:space="preserve">
+ *     <description xml:space="default">     Some text      </description>
+ *   </space>
+ * </configuration>
+ * </pre>
+ *
+ * The result will be "value Some text".
+ *
+ * This lookup uses Apache Commons Jexl and requires that the dependency be added to any
+ * projects which use this.
+ *
+ * @since 1.7
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a>
+ * @version $Id: ExprLookup.java 1234539 2012-01-22 16:19:15Z oheger $
+ */
+public class ExprLookup extends StrLookup
+{
+    /** Prefix to identify a Java Class object */
+    private static final String CLASS = "Class:";
+
+    /** The default prefix for subordinate lookup expressions */
+    private static final String DEFAULT_PREFIX = "$[";
+
+    /** The default suffix for subordinate lookup expressions */
+    private static final String DEFAULT_SUFFIX = "]";
+
+    /** Configuration being operated on */
+    private AbstractConfiguration configuration;
+
+    /** The engine. */
+    private final JexlEngine engine = new JexlEngine();
+
+    /** The variables maintained by this object. */
+    private Variables variables;
+
+    /** The String to use to start subordinate lookup expressions */
+    private String prefixMatcher = DEFAULT_PREFIX;
+
+    /** The String to use to terminate subordinate lookup expressions */
+    private String suffixMatcher = DEFAULT_SUFFIX;
+
+    /**
+     * The default constructor. Will get used when the Lookup is constructed via
+     * configuration.
+     */
+    public ExprLookup()
+    {
+    }
+
+    /**
+     * Constructor for use by applications.
+     * @param list The list of objects to be accessible in expressions.
+     */
+    public ExprLookup(Variables list)
+    {
+        setVariables(list);
+    }
+
+    /**
+     * Constructor for use by applications.
+     * @param list The list of objects to be accessible in expressions.
+     * @param prefix The prefix to use for subordinate lookups.
+     * @param suffix The suffix to use for subordinate lookups.
+     */
+    public ExprLookup(Variables list, String prefix, String suffix)
+    {
+        this(list);
+        setVariablePrefixMatcher(prefix);
+        setVariableSuffixMatcher(suffix);
+    }
+
+    /**
+     * Set the prefix to use to identify subordinate expressions. This cannot be the
+     * same as the prefix used for the primary expression.
+     * @param prefix The String identifying the beginning of the expression.
+     */
+    public void setVariablePrefixMatcher(String prefix)
+    {
+        prefixMatcher = prefix;
+    }
+
+
+    /**
+     * Set the suffix to use to identify subordinate expressions. This cannot be the
+     * same as the suffix used for the primary expression.
+     * @param suffix The String identifying the end of the expression.
+     */
+    public void setVariableSuffixMatcher(String suffix)
+    {
+        suffixMatcher = suffix;
+    }
+
+    /**
+     * Add the Variables that will be accessible within expressions.
+     * @param list The list of Variables.
+     */
+    public void setVariables(Variables list)
+    {
+        variables = new Variables(list);
+    }
+
+    /**
+     * Returns the list of Variables that are accessible within expressions.
+     * @return the List of Variables that are accessible within expressions.
+     */
+    public Variables getVariables()
+    {
+        return null;
+    }
+
+    /**
+     * Set the configuration to be used to interpolate subordinate expressions.
+     * @param config The Configuration.
+     */
+    public void setConfiguration(AbstractConfiguration config)
+    {
+        this.configuration = config;
+    }
+
+    /**
+     * Evaluates the expression.
+     * @param var The expression.
+     * @return The String result of the expression.
+     */
+    @Override
+    public String lookup(String var)
+    {
+        ConfigurationInterpolator interp = configuration.getInterpolator();
+        StrSubstitutor subst = new StrSubstitutor(interp, prefixMatcher, suffixMatcher,
+                StrSubstitutor.DEFAULT_ESCAPE);
+
+        String result = subst.replace(var);
+
+        try
+        {
+            Expression exp = engine.createExpression(result);
+            result = (String) exp.evaluate(createContext());
+        }
+        catch (Exception e)
+        {
+            configuration.getLogger().debug("Error encountered evaluating " + result, e);
+        }
+
+        return result;
+    }
+
+    /**
+     * Creates a new {@code JexlContext} and initializes it with the variables
+     * managed by this Lookup object.
+     *
+     * @return the newly created context
+     */
+    private JexlContext createContext()
+    {
+        JexlContext ctx = new MapContext();
+        initializeContext(ctx);
+        return ctx;
+    }
+
+    /**
+     * Initializes the specified context with the variables managed by this
+     * Lookup object.
+     *
+     * @param ctx the context to be initialized
+     */
+    private void initializeContext(JexlContext ctx)
+    {
+        for (Variable var : variables)
+        {
+            ctx.set(var.getName(), var.getValue());
+        }
+    }
+
+    /**
+     * List wrapper used to allow the Variables list to be created as beans in
+     * DefaultConfigurationBuilder.
+     */
+    public static class Variables extends ArrayList<Variable>
+    {
+        /**
+         * The serial version UID.
+         */
+        private static final long serialVersionUID = 20111205L;
+
+        /**
+         * Creates a new empty instance of {@code Variables}.
+         */
+        public Variables()
+        {
+            super();
+        }
+
+        /**
+         * Creates a new instance of {@code Variables} and copies the content of
+         * the given object.
+         *
+         * @param vars the {@code Variables} object to be copied
+         */
+        public Variables(Variables vars)
+        {
+            super(vars);
+        }
+
+        public Variable getVariable()
+        {
+            if (size() > 0)
+            {
+                return get(size() - 1);
+            }
+            else
+            {
+                return null;
+            }
+        }
+
+    }
+
+    /**
+     * The key and corresponding object that will be made available to the
+     * JexlContext for use in expressions.
+     */
+    public static class Variable
+    {
+        /** The name to be used in expressions */
+        private String key;
+
+        /** The object to be accessed in expressions */
+        private Object value;
+
+        public Variable()
+        {
+        }
+
+        public Variable(String name, Object value)
+        {
+            setName(name);
+            setValue(value);
+        }
+
+        public String getName()
+        {
+            return key;
+        }
+
+        public void setName(String name)
+        {
+            this.key = name;
+        }
+
+        public Object getValue()
+        {
+            return value;
+        }
+
+        public void setValue(Object value) throws ConfigurationRuntimeException
+        {
+            try
+            {
+                if (!(value instanceof String))
+                {
+                    this.value = value;
+                    return;
+                }
+                String val = (String) value;
+                String name = StringUtils.removeStartIgnoreCase(val, CLASS);
+                Class<?> clazz = ClassUtils.getClass(name);
+                if (name.length() == val.length())
+                {
+                    this.value = clazz.newInstance();
+                }
+                else
+                {
+                    this.value = clazz;
+                }
+            }
+            catch (Exception e)
+            {
+                throw new ConfigurationRuntimeException("Unable to create " + value, e);
+            }
+
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/interpol/package.html b/src/main/java/org/apache/commons/configuration/interpol/package.html
new file mode 100644
index 0000000..d960e99
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/interpol/package.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<html>
+<head>
+</head>
+<body>
+
+<p>
+A package with helper classes used for interpolation (variable substitution).
+</p>
+<p>
+<font size="-2">$Id: package.html 952622 2010-06-08 11:56:30Z sebb $</font>
+</p>
+
+</body>
+</html>
diff --git a/src/main/java/org/apache/commons/configuration/package.html b/src/main/java/org/apache/commons/configuration/package.html
new file mode 100644
index 0000000..c385132
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/package.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<html>
+<head>
+</head>
+
+<body>
+<p>
+The Configuration main package. It contains the definition of the
+<code>Configuration</code> interface and frequently used implementations
+like <code>PropertiesConfiguration</code> (dealing with <code>.properties</code>
+files) or <code>XMLConfiguration</code> that can load XML documents.
+</p>
+<p>
+<font size="-2">$Id: package.html 439648 2006-09-02 20:42:10Z oheger $</font>
+</p>
+</body>
+</html>
diff --git a/src/main/java/org/apache/commons/configuration/plist/PropertyListConfiguration.java b/src/main/java/org/apache/commons/configuration/plist/PropertyListConfiguration.java
new file mode 100644
index 0000000..114dc29
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/plist/PropertyListConfiguration.java
@@ -0,0 +1,713 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.plist;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.configuration.AbstractHierarchicalFileConfiguration;
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.HierarchicalConfiguration;
+import org.apache.commons.configuration.MapConfiguration;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * NeXT / OpenStep style configuration. This configuration can read and write
+ * ASCII plist files. It supports the GNUStep extension to specify date objects.
+ * <p>
+ * References:
+ * <ul>
+ *   <li><a
+ * href="http://developer.apple.com/documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html">
+ * Apple Documentation - Old-Style ASCII Property Lists</a></li>
+ *   <li><a
+ * href="http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html">
+ * GNUStep Documentation</a></li>
+ * </ul>
+ *
+ * <p>Example:</p>
+ * <pre>
+ * {
+ *     foo = "bar";
+ *
+ *     array = ( value1, value2, value3 );
+ *
+ *     data = <4f3e0145ab>;
+ *
+ *     date = <*D2007-05-05 20:05:00 +0100>;
+ *
+ *     nested =
+ *     {
+ *         key1 = value1;
+ *         key2 = value;
+ *         nested =
+ *         {
+ *             foo = bar
+ *         }
+ *     }
+ * }
+ * </pre>
+ *
+ * @since 1.2
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: PropertyListConfiguration.java 1210637 2011-12-05 21:12:12Z oheger $
+ */
+public class PropertyListConfiguration extends AbstractHierarchicalFileConfiguration
+{
+    /** Constant for the separator parser for the date part. */
+    private static final DateComponentParser DATE_SEPARATOR_PARSER = new DateSeparatorParser(
+            "-");
+
+    /** Constant for the separator parser for the time part. */
+    private static final DateComponentParser TIME_SEPARATOR_PARSER = new DateSeparatorParser(
+            ":");
+
+    /** Constant for the separator parser for blanks between the parts. */
+    private static final DateComponentParser BLANK_SEPARATOR_PARSER = new DateSeparatorParser(
+            " ");
+
+    /** An array with the component parsers for dealing with dates. */
+    private static final DateComponentParser[] DATE_PARSERS =
+    {new DateSeparatorParser("<*D"), new DateFieldParser(Calendar.YEAR, 4),
+            DATE_SEPARATOR_PARSER, new DateFieldParser(Calendar.MONTH, 2, 1),
+            DATE_SEPARATOR_PARSER, new DateFieldParser(Calendar.DATE, 2),
+            BLANK_SEPARATOR_PARSER,
+            new DateFieldParser(Calendar.HOUR_OF_DAY, 2),
+            TIME_SEPARATOR_PARSER, new DateFieldParser(Calendar.MINUTE, 2),
+            TIME_SEPARATOR_PARSER, new DateFieldParser(Calendar.SECOND, 2),
+            BLANK_SEPARATOR_PARSER, new DateTimeZoneParser(),
+            new DateSeparatorParser(">")};
+
+    /** Constant for the ID prefix for GMT time zones. */
+    private static final String TIME_ZONE_PREFIX = "GMT";
+
+    /** The serial version UID. */
+    private static final long serialVersionUID = 3227248503779092127L;
+
+    /** Constant for the milliseconds of a minute.*/
+    private static final int MILLIS_PER_MINUTE = 1000 * 60;
+
+    /** Constant for the minutes per hour.*/
+    private static final int MINUTES_PER_HOUR = 60;
+
+    /** Size of the indentation for the generated file. */
+    private static final int INDENT_SIZE = 4;
+
+    /** Constant for the length of a time zone.*/
+    private static final int TIME_ZONE_LENGTH = 5;
+
+    /** Constant for the padding character in the date format.*/
+    private static final char PAD_CHAR = '0';
+
+    /**
+     * Creates an empty PropertyListConfiguration object which can be
+     * used to synthesize a new plist file by adding values and
+     * then saving().
+     */
+    public PropertyListConfiguration()
+    {
+    }
+
+    /**
+     * Creates a new instance of {@code PropertyListConfiguration} and
+     * copies the content of the specified configuration into this object.
+     *
+     * @param c the configuration to copy
+     * @since 1.4
+     */
+    public PropertyListConfiguration(HierarchicalConfiguration c)
+    {
+        super(c);
+    }
+
+    /**
+     * Creates and loads the property list from the specified file.
+     *
+     * @param fileName The name of the plist file to load.
+     * @throws ConfigurationException Error while loading the plist file
+     */
+    public PropertyListConfiguration(String fileName) throws ConfigurationException
+    {
+        super(fileName);
+    }
+
+    /**
+     * Creates and loads the property list from the specified file.
+     *
+     * @param file The plist file to load.
+     * @throws ConfigurationException Error while loading the plist file
+     */
+    public PropertyListConfiguration(File file) throws ConfigurationException
+    {
+        super(file);
+    }
+
+    /**
+     * Creates and loads the property list from the specified URL.
+     *
+     * @param url The location of the plist file to load.
+     * @throws ConfigurationException Error while loading the plist file
+     */
+    public PropertyListConfiguration(URL url) throws ConfigurationException
+    {
+        super(url);
+    }
+
+    @Override
+    public void setProperty(String key, Object value)
+    {
+        // special case for byte arrays, they must be stored as is in the configuration
+        if (value instanceof byte[])
+        {
+            fireEvent(EVENT_SET_PROPERTY, key, value, true);
+            setDetailEvents(false);
+            try
+            {
+                clearProperty(key);
+                addPropertyDirect(key, value);
+            }
+            finally
+            {
+                setDetailEvents(true);
+            }
+            fireEvent(EVENT_SET_PROPERTY, key, value, false);
+        }
+        else
+        {
+            super.setProperty(key, value);
+        }
+    }
+
+    @Override
+    public void addProperty(String key, Object value)
+    {
+        if (value instanceof byte[])
+        {
+            fireEvent(EVENT_ADD_PROPERTY, key, value, true);
+            addPropertyDirect(key, value);
+            fireEvent(EVENT_ADD_PROPERTY, key, value, false);
+        }
+        else
+        {
+            super.addProperty(key, value);
+        }
+    }
+
+    public void load(Reader in) throws ConfigurationException
+    {
+        PropertyListParser parser = new PropertyListParser(in);
+        try
+        {
+            HierarchicalConfiguration config = parser.parse();
+            setRoot(config.getRoot());
+        }
+        catch (ParseException e)
+        {
+            throw new ConfigurationException(e);
+        }
+    }
+
+    public void save(Writer out) throws ConfigurationException
+    {
+        PrintWriter writer = new PrintWriter(out);
+        printNode(writer, 0, getRoot());
+        writer.flush();
+    }
+
+    /**
+     * Append a node to the writer, indented according to a specific level.
+     */
+    private void printNode(PrintWriter out, int indentLevel, ConfigurationNode node)
+    {
+        String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
+
+        if (node.getName() != null)
+        {
+            out.print(padding + quoteString(node.getName()) + " = ");
+        }
+
+        List<ConfigurationNode> children = new ArrayList<ConfigurationNode>(node.getChildren());
+        if (!children.isEmpty())
+        {
+            // skip a line, except for the root dictionary
+            if (indentLevel > 0)
+            {
+                out.println();
+            }
+
+            out.println(padding + "{");
+
+            // display the children
+            Iterator<ConfigurationNode> it = children.iterator();
+            while (it.hasNext())
+            {
+                ConfigurationNode child = it.next();
+
+                printNode(out, indentLevel + 1, child);
+
+                // add a semi colon for elements that are not dictionaries
+                Object value = child.getValue();
+                if (value != null && !(value instanceof Map) && !(value instanceof Configuration))
+                {
+                    out.println(";");
+                }
+
+                // skip a line after arrays and dictionaries
+                if (it.hasNext() && (value == null || value instanceof List))
+                {
+                    out.println();
+                }
+            }
+
+            out.print(padding + "}");
+
+            // line feed if the dictionary is not in an array
+            if (node.getParentNode() != null)
+            {
+                out.println();
+            }
+        }
+        else if (node.getValue() == null)
+        {
+            out.println();
+            out.print(padding + "{ };");
+
+            // line feed if the dictionary is not in an array
+            if (node.getParentNode() != null)
+            {
+                out.println();
+            }
+        }
+        else
+        {
+            // display the leaf value
+            Object value = node.getValue();
+            printValue(out, indentLevel, value);
+        }
+    }
+
+    /**
+     * Append a value to the writer, indented according to a specific level.
+     */
+    private void printValue(PrintWriter out, int indentLevel, Object value)
+    {
+        String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
+
+        if (value instanceof List)
+        {
+            out.print("( ");
+            Iterator<?> it = ((List<?>) value).iterator();
+            while (it.hasNext())
+            {
+                printValue(out, indentLevel + 1, it.next());
+                if (it.hasNext())
+                {
+                    out.print(", ");
+                }
+            }
+            out.print(" )");
+        }
+        else if (value instanceof HierarchicalConfiguration)
+        {
+            printNode(out, indentLevel, ((HierarchicalConfiguration) value).getRoot());
+        }
+        else if (value instanceof Configuration)
+        {
+            // display a flat Configuration as a dictionary
+            out.println();
+            out.println(padding + "{");
+
+            Configuration config = (Configuration) value;
+            Iterator<String> it = config.getKeys();
+            while (it.hasNext())
+            {
+                String key = it.next();
+                Node node = new Node(key);
+                node.setValue(config.getProperty(key));
+
+                printNode(out, indentLevel + 1, node);
+                out.println(";");
+            }
+            out.println(padding + "}");
+        }
+        else if (value instanceof Map)
+        {
+            // display a Map as a dictionary
+            Map<String, Object> map = transformMap((Map<?, ?>) value);
+            printValue(out, indentLevel, new MapConfiguration(map));
+        }
+        else if (value instanceof byte[])
+        {
+            out.print("<" + new String(Hex.encodeHex((byte[]) value)) + ">");
+        }
+        else if (value instanceof Date)
+        {
+            out.print(formatDate((Date) value));
+        }
+        else if (value != null)
+        {
+            out.print(quoteString(String.valueOf(value)));
+        }
+    }
+
+    /**
+     * Quote the specified string if necessary, that's if the string contains:
+     * <ul>
+     *   <li>a space character (' ', '\t', '\r', '\n')</li>
+     *   <li>a quote '"'</li>
+     *   <li>special characters in plist files ('(', ')', '{', '}', '=', ';', ',')</li>
+     * </ul>
+     * Quotes within the string are escaped.
+     *
+     * <p>Examples:</p>
+     * <ul>
+     *   <li>abcd -> abcd</li>
+     *   <li>ab cd -> "ab cd"</li>
+     *   <li>foo"bar -> "foo\"bar"</li>
+     *   <li>foo;bar -> "foo;bar"</li>
+     * </ul>
+     */
+    String quoteString(String s)
+    {
+        if (s == null)
+        {
+            return null;
+        }
+
+        if (s.indexOf(' ') != -1
+                || s.indexOf('\t') != -1
+                || s.indexOf('\r') != -1
+                || s.indexOf('\n') != -1
+                || s.indexOf('"') != -1
+                || s.indexOf('(') != -1
+                || s.indexOf(')') != -1
+                || s.indexOf('{') != -1
+                || s.indexOf('}') != -1
+                || s.indexOf('=') != -1
+                || s.indexOf(',') != -1
+                || s.indexOf(';') != -1)
+        {
+            s = s.replaceAll("\"", "\\\\\\\"");
+            s = "\"" + s + "\"";
+        }
+
+        return s;
+    }
+
+    /**
+     * Parses a date in a format like
+     * {@code <*D2002-03-22 11:30:00 +0100>}.
+     *
+     * @param s the string with the date to be parsed
+     * @return the parsed date
+     * @throws ParseException if an error occurred while parsing the string
+     */
+    static Date parseDate(String s) throws ParseException
+    {
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        int index = 0;
+
+        for (DateComponentParser parser : DATE_PARSERS)
+        {
+            index += parser.parseComponent(s, index, cal);
+        }
+
+        return cal.getTime();
+    }
+
+    /**
+     * Returns a string representation for the date specified by the given
+     * calendar.
+     *
+     * @param cal the calendar with the initialized date
+     * @return a string for this date
+     */
+    static String formatDate(Calendar cal)
+    {
+        StringBuilder buf = new StringBuilder();
+
+        for (int i = 0; i < DATE_PARSERS.length; i++)
+        {
+            DATE_PARSERS[i].formatComponent(buf, cal);
+        }
+
+        return buf.toString();
+    }
+
+    /**
+     * Returns a string representation for the specified date.
+     *
+     * @param date the date
+     * @return a string for this date
+     */
+    static String formatDate(Date date)
+    {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(date);
+        return formatDate(cal);
+    }
+
+    /**
+     * Transform a map of arbitrary types into a map with string keys and object
+     * values. All keys of the source map which are not of type String are
+     * dropped.
+     *
+     * @param src the map to be converted
+     * @return the resulting map
+     */
+    private static Map<String, Object> transformMap(Map<?, ?> src)
+    {
+        Map<String, Object> dest = new HashMap<String, Object>();
+        for (Map.Entry<?, ?> e : src.entrySet())
+        {
+            if (e.getKey() instanceof String)
+            {
+                dest.put((String) e.getKey(), e.getValue());
+            }
+        }
+        return dest;
+    }
+
+    /**
+     * A helper class for parsing and formatting date literals. Usually we would
+     * use {@code SimpleDateFormat} for this purpose, but in Java 1.3 the
+     * functionality of this class is limited. So we have a hierarchy of parser
+     * classes instead that deal with the different components of a date
+     * literal.
+     */
+    private abstract static class DateComponentParser
+    {
+        /**
+         * Parses a component from the given input string.
+         *
+         * @param s the string to be parsed
+         * @param index the current parsing position
+         * @param cal the calendar where to store the result
+         * @return the length of the processed component
+         * @throws ParseException if the component cannot be extracted
+         */
+        public abstract int parseComponent(String s, int index, Calendar cal)
+                throws ParseException;
+
+        /**
+         * Formats a date component. This method is used for converting a date
+         * in its internal representation into a string literal.
+         *
+         * @param buf the target buffer
+         * @param cal the calendar with the current date
+         */
+        public abstract void formatComponent(StringBuilder buf, Calendar cal);
+
+        /**
+         * Checks whether the given string has at least {@code length}
+         * characters starting from the given parsing position. If this is not
+         * the case, an exception will be thrown.
+         *
+         * @param s the string to be tested
+         * @param index the current index
+         * @param length the minimum length after the index
+         * @throws ParseException if the string is too short
+         */
+        protected void checkLength(String s, int index, int length)
+                throws ParseException
+        {
+            int len = (s == null) ? 0 : s.length();
+            if (index + length > len)
+            {
+                throw new ParseException("Input string too short: " + s
+                        + ", index: " + index);
+            }
+        }
+
+        /**
+         * Adds a number to the given string buffer and adds leading '0'
+         * characters until the given length is reached.
+         *
+         * @param buf the target buffer
+         * @param num the number to add
+         * @param length the required length
+         */
+        protected void padNum(StringBuilder buf, int num, int length)
+        {
+            buf.append(StringUtils.leftPad(String.valueOf(num), length,
+                    PAD_CHAR));
+        }
+    }
+
+    /**
+     * A specialized date component parser implementation that deals with
+     * numeric calendar fields. The class is able to extract fields from a
+     * string literal and to format a literal from a calendar.
+     */
+    private static class DateFieldParser extends DateComponentParser
+    {
+        /** Stores the calendar field to be processed. */
+        private int calendarField;
+
+        /** Stores the length of this field. */
+        private int length;
+
+        /** An optional offset to add to the calendar field. */
+        private int offset;
+
+        /**
+         * Creates a new instance of {@code DateFieldParser}.
+         *
+         * @param calFld the calendar field code
+         * @param len the length of this field
+         */
+        public DateFieldParser(int calFld, int len)
+        {
+            this(calFld, len, 0);
+        }
+
+        /**
+         * Creates a new instance of {@code DateFieldParser} and fully
+         * initializes it.
+         *
+         * @param calFld the calendar field code
+         * @param len the length of this field
+         * @param ofs an offset to add to the calendar field
+         */
+        public DateFieldParser(int calFld, int len, int ofs)
+        {
+            calendarField = calFld;
+            length = len;
+            offset = ofs;
+        }
+
+        @Override
+        public void formatComponent(StringBuilder buf, Calendar cal)
+        {
+            padNum(buf, cal.get(calendarField) + offset, length);
+        }
+
+        @Override
+        public int parseComponent(String s, int index, Calendar cal)
+                throws ParseException
+        {
+            checkLength(s, index, length);
+            try
+            {
+                cal.set(calendarField, Integer.parseInt(s.substring(index,
+                        index + length))
+                        - offset);
+                return length;
+            }
+            catch (NumberFormatException nfex)
+            {
+                throw new ParseException("Invalid number: " + s + ", index "
+                        + index);
+            }
+        }
+    }
+
+    /**
+     * A specialized date component parser implementation that deals with
+     * separator characters.
+     */
+    private static class DateSeparatorParser extends DateComponentParser
+    {
+        /** Stores the separator. */
+        private String separator;
+
+        /**
+         * Creates a new instance of {@code DateSeparatorParser} and sets
+         * the separator string.
+         *
+         * @param sep the separator string
+         */
+        public DateSeparatorParser(String sep)
+        {
+            separator = sep;
+        }
+
+        @Override
+        public void formatComponent(StringBuilder buf, Calendar cal)
+        {
+            buf.append(separator);
+        }
+
+        @Override
+        public int parseComponent(String s, int index, Calendar cal)
+                throws ParseException
+        {
+            checkLength(s, index, separator.length());
+            if (!s.startsWith(separator, index))
+            {
+                throw new ParseException("Invalid input: " + s + ", index "
+                        + index + ", expected " + separator);
+            }
+            return separator.length();
+        }
+    }
+
+    /**
+     * A specialized date component parser implementation that deals with the
+     * time zone part of a date component.
+     */
+    private static class DateTimeZoneParser extends DateComponentParser
+    {
+        @Override
+        public void formatComponent(StringBuilder buf, Calendar cal)
+        {
+            TimeZone tz = cal.getTimeZone();
+            int ofs = tz.getRawOffset() / MILLIS_PER_MINUTE;
+            if (ofs < 0)
+            {
+                buf.append('-');
+                ofs = -ofs;
+            }
+            else
+            {
+                buf.append('+');
+            }
+            int hour = ofs / MINUTES_PER_HOUR;
+            int min = ofs % MINUTES_PER_HOUR;
+            padNum(buf, hour, 2);
+            padNum(buf, min, 2);
+        }
+
+        @Override
+        public int parseComponent(String s, int index, Calendar cal)
+                throws ParseException
+        {
+            checkLength(s, index, TIME_ZONE_LENGTH);
+            TimeZone tz = TimeZone.getTimeZone(TIME_ZONE_PREFIX
+                    + s.substring(index, index + TIME_ZONE_LENGTH));
+            cal.setTimeZone(tz);
+            return TIME_ZONE_LENGTH;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/plist/XMLPropertyListConfiguration.java b/src/main/java/org/apache/commons/configuration/plist/XMLPropertyListConfiguration.java
new file mode 100644
index 0000000..b3c397d
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/plist/XMLPropertyListConfiguration.java
@@ -0,0 +1,804 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.plist;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.URL;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.configuration.AbstractHierarchicalFileConfiguration;
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.HierarchicalConfiguration;
+import org.apache.commons.configuration.MapConfiguration;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.lang.StringEscapeUtils;
+import org.apache.commons.lang.StringUtils;
+import org.xml.sax.Attributes;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Property list file (plist) in XML FORMAT as used by Mac OS X (http://www.apple.com/DTDs/PropertyList-1.0.dtd).
+ * This configuration doesn't support the binary FORMAT used in OS X 10.4.
+ *
+ * <p>Example:</p>
+ * <pre>
+ * <?xml version="1.0"?>
+ * <!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
+ * <plist version="1.0">
+ *     <dict>
+ *         <key>string</key>
+ *         <string>value1</string>
+ *
+ *         <key>integer</key>
+ *         <integer>12345</integer>
+ *
+ *         <key>real</key>
+ *         <real>-123.45E-1</real>
+ *
+ *         <key>boolean</key>
+ *         <true/>
+ *
+ *         <key>date</key>
+ *         <date>2005-01-01T12:00:00Z</date>
+ *
+ *         <key>data</key>
+ *         <data>RHJhY28gRG9ybWllbnMgTnVucXVhbSBUaXRpbGxhbmR1cw==</data>
+ *
+ *         <key>array</key>
+ *         <array>
+ *             <string>value1</string>
+ *             <string>value2</string>
+ *             <string>value3</string>
+ *         </array>
+ *
+ *         <key>dictionnary</key>
+ *         <dict>
+ *             <key>key1</key>
+ *             <string>value1</string>
+ *             <key>key2</key>
+ *             <string>value2</string>
+ *             <key>key3</key>
+ *             <string>value3</string>
+ *         </dict>
+ *
+ *         <key>nested</key>
+ *         <dict>
+ *             <key>node1</key>
+ *             <dict>
+ *                 <key>node2</key>
+ *                 <dict>
+ *                     <key>node3</key>
+ *                     <string>value</string>
+ *                 </dict>
+ *             </dict>
+ *         </dict>
+ *
+ *     </dict>
+ * </plist>
+ * </pre>
+ *
+ * @since 1.2
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: XMLPropertyListConfiguration.java 1368665 2012-08-02 19:48:26Z oheger $
+ */
+public class XMLPropertyListConfiguration extends AbstractHierarchicalFileConfiguration
+{
+    /**
+     * The serial version UID.
+     */
+    private static final long serialVersionUID = -3162063751042475985L;
+
+    /** Size of the indentation for the generated file. */
+    private static final int INDENT_SIZE = 4;
+
+    /**
+     * Creates an empty XMLPropertyListConfiguration object which can be
+     * used to synthesize a new plist file by adding values and
+     * then saving().
+     */
+    public XMLPropertyListConfiguration()
+    {
+        initRoot();
+    }
+
+    /**
+     * Creates a new instance of {@code XMLPropertyListConfiguration} and
+     * copies the content of the specified configuration into this object.
+     *
+     * @param configuration the configuration to copy
+     * @since 1.4
+     */
+    public XMLPropertyListConfiguration(HierarchicalConfiguration configuration)
+    {
+        super(configuration);
+    }
+
+    /**
+     * Creates and loads the property list from the specified file.
+     *
+     * @param fileName The name of the plist file to load.
+     * @throws org.apache.commons.configuration.ConfigurationException Error
+     * while loading the plist file
+     */
+    public XMLPropertyListConfiguration(String fileName) throws ConfigurationException
+    {
+        super(fileName);
+    }
+
+    /**
+     * Creates and loads the property list from the specified file.
+     *
+     * @param file The plist file to load.
+     * @throws ConfigurationException Error while loading the plist file
+     */
+    public XMLPropertyListConfiguration(File file) throws ConfigurationException
+    {
+        super(file);
+    }
+
+    /**
+     * Creates and loads the property list from the specified URL.
+     *
+     * @param url The location of the plist file to load.
+     * @throws ConfigurationException Error while loading the plist file
+     */
+    public XMLPropertyListConfiguration(URL url) throws ConfigurationException
+    {
+        super(url);
+    }
+
+    @Override
+    public void setProperty(String key, Object value)
+    {
+        // special case for byte arrays, they must be stored as is in the configuration
+        if (value instanceof byte[])
+        {
+            fireEvent(EVENT_SET_PROPERTY, key, value, true);
+            setDetailEvents(false);
+            try
+            {
+                clearProperty(key);
+                addPropertyDirect(key, value);
+            }
+            finally
+            {
+                setDetailEvents(true);
+            }
+            fireEvent(EVENT_SET_PROPERTY, key, value, false);
+        }
+        else
+        {
+            super.setProperty(key, value);
+        }
+    }
+
+    @Override
+    public void addProperty(String key, Object value)
+    {
+        if (value instanceof byte[])
+        {
+            fireEvent(EVENT_ADD_PROPERTY, key, value, true);
+            addPropertyDirect(key, value);
+            fireEvent(EVENT_ADD_PROPERTY, key, value, false);
+        }
+        else
+        {
+            super.addProperty(key, value);
+        }
+    }
+
+    public void load(Reader in) throws ConfigurationException
+    {
+        // We have to make sure that the root node is actually a PListNode.
+        // If this object was not created using the standard constructor, the
+        // root node is a plain Node.
+        if (!(getRootNode() instanceof PListNode))
+        {
+            initRoot();
+        }
+
+        // set up the DTD validation
+        EntityResolver resolver = new EntityResolver()
+        {
+            public InputSource resolveEntity(String publicId, String systemId)
+            {
+                return new InputSource(getClass().getClassLoader().getResourceAsStream("PropertyList-1.0.dtd"));
+            }
+        };
+
+        // parse the file
+        XMLPropertyListHandler handler = new XMLPropertyListHandler(getRoot());
+        try
+        {
+            SAXParserFactory factory = SAXParserFactory.newInstance();
+            factory.setValidating(true);
+
+            SAXParser parser = factory.newSAXParser();
+            parser.getXMLReader().setEntityResolver(resolver);
+            parser.getXMLReader().setContentHandler(handler);
+            parser.getXMLReader().parse(new InputSource(in));
+        }
+        catch (Exception e)
+        {
+            throw new ConfigurationException("Unable to parse the configuration file", e);
+        }
+    }
+
+    public void save(Writer out) throws ConfigurationException
+    {
+        PrintWriter writer = new PrintWriter(out);
+
+        if (getEncoding() != null)
+        {
+            writer.println("<?xml version=\"1.0\" encoding=\"" + getEncoding() + "\"?>");
+        }
+        else
+        {
+            writer.println("<?xml version=\"1.0\"?>");
+        }
+
+        writer.println("<!DOCTYPE plist SYSTEM \"file://localhost/System/Library/DTDs/PropertyList.dtd\">");
+        writer.println("<plist version=\"1.0\">");
+
+        printNode(writer, 1, getRoot());
+
+        writer.println("</plist>");
+        writer.flush();
+    }
+
+    /**
+     * Append a node to the writer, indented according to a specific level.
+     */
+    private void printNode(PrintWriter out, int indentLevel, ConfigurationNode node)
+    {
+        String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
+
+        if (node.getName() != null)
+        {
+            out.println(padding + "<key>" + StringEscapeUtils.escapeXml(node.getName()) + "</key>");
+        }
+
+        List<ConfigurationNode> children = node.getChildren();
+        if (!children.isEmpty())
+        {
+            out.println(padding + "<dict>");
+
+            Iterator<ConfigurationNode> it = children.iterator();
+            while (it.hasNext())
+            {
+                ConfigurationNode child = it.next();
+                printNode(out, indentLevel + 1, child);
+
+                if (it.hasNext())
+                {
+                    out.println();
+                }
+            }
+
+            out.println(padding + "</dict>");
+        }
+        else if (node.getValue() == null)
+        {
+            out.println(padding + "<dict/>");
+        }
+        else
+        {
+            Object value = node.getValue();
+            printValue(out, indentLevel, value);
+        }
+    }
+
+    /**
+     * Append a value to the writer, indented according to a specific level.
+     */
+    private void printValue(PrintWriter out, int indentLevel, Object value)
+    {
+        String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);
+
+        if (value instanceof Date)
+        {
+            synchronized (PListNode.FORMAT)
+            {
+                out.println(padding + "<date>" + PListNode.FORMAT.format((Date) value) + "</date>");
+            }
+        }
+        else if (value instanceof Calendar)
+        {
+            printValue(out, indentLevel, ((Calendar) value).getTime());
+        }
+        else if (value instanceof Number)
+        {
+            if (value instanceof Double || value instanceof Float || value instanceof BigDecimal)
+            {
+                out.println(padding + "<real>" + value.toString() + "</real>");
+            }
+            else
+            {
+                out.println(padding + "<integer>" + value.toString() + "</integer>");
+            }
+        }
+        else if (value instanceof Boolean)
+        {
+            if (((Boolean) value).booleanValue())
+            {
+                out.println(padding + "<true/>");
+            }
+            else
+            {
+                out.println(padding + "<false/>");
+            }
+        }
+        else if (value instanceof List)
+        {
+            out.println(padding + "<array>");
+            Iterator<?> it = ((List<?>) value).iterator();
+            while (it.hasNext())
+            {
+                printValue(out, indentLevel + 1, it.next());
+            }
+            out.println(padding + "</array>");
+        }
+        else if (value instanceof HierarchicalConfiguration)
+        {
+            printNode(out, indentLevel, ((HierarchicalConfiguration) value).getRoot());
+        }
+        else if (value instanceof Configuration)
+        {
+            // display a flat Configuration as a dictionary
+            out.println(padding + "<dict>");
+
+            Configuration config = (Configuration) value;
+            Iterator<String> it = config.getKeys();
+            while (it.hasNext())
+            {
+                // create a node for each property
+                String key = it.next();
+                Node node = new Node(key);
+                node.setValue(config.getProperty(key));
+
+                // print the node
+                printNode(out, indentLevel + 1, node);
+
+                if (it.hasNext())
+                {
+                    out.println();
+                }
+            }
+            out.println(padding + "</dict>");
+        }
+        else if (value instanceof Map)
+        {
+            // display a Map as a dictionary
+            Map<String, Object> map = transformMap((Map<?, ?>) value);
+            printValue(out, indentLevel, new MapConfiguration(map));
+        }
+        else if (value instanceof byte[])
+        {
+            String base64 = new String(Base64.encodeBase64((byte[]) value));
+            out.println(padding + "<data>" + StringEscapeUtils.escapeXml(base64) + "</data>");
+        }
+        else if (value != null)
+        {
+            out.println(padding + "<string>" + StringEscapeUtils.escapeXml(String.valueOf(value)) + "</string>");
+        }
+        else
+        {
+            out.println(padding + "<string/>");
+        }
+    }
+
+    /**
+     * Helper method for initializing the configuration's root node.
+     */
+    private void initRoot()
+    {
+        setRootNode(new PListNode());
+    }
+
+    /**
+     * Transform a map of arbitrary types into a map with string keys and object
+     * values. All keys of the source map which are not of type String are
+     * dropped.
+     *
+     * @param src the map to be converted
+     * @return the resulting map
+     */
+    private static Map<String, Object> transformMap(Map<?, ?> src)
+    {
+        Map<String, Object> dest = new HashMap<String, Object>();
+        for (Map.Entry<?, ?> e : src.entrySet())
+        {
+            if (e.getKey() instanceof String)
+            {
+                dest.put((String) e.getKey(), e.getValue());
+            }
+        }
+        return dest;
+    }
+
+    /**
+     * SAX Handler to build the configuration nodes while the document is being parsed.
+     */
+    private class XMLPropertyListHandler extends DefaultHandler
+    {
+        /** The buffer containing the text node being read */
+        private StringBuilder buffer = new StringBuilder();
+
+        /** The stack of configuration nodes */
+        private List<Node> stack = new ArrayList<Node>();
+
+        public XMLPropertyListHandler(Node root)
+        {
+            push(root);
+        }
+
+        /**
+         * Return the node on the top of the stack.
+         */
+        private Node peek()
+        {
+            if (!stack.isEmpty())
+            {
+                return stack.get(stack.size() - 1);
+            }
+            else
+            {
+                return null;
+            }
+        }
+
+        /**
+         * Remove and return the node on the top of the stack.
+         */
+        private Node pop()
+        {
+            if (!stack.isEmpty())
+            {
+                return stack.remove(stack.size() - 1);
+            }
+            else
+            {
+                return null;
+            }
+        }
+
+        /**
+         * Put a node on the top of the stack.
+         */
+        private void push(Node node)
+        {
+            stack.add(node);
+        }
+
+        @Override
+        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
+        {
+            if ("array".equals(qName))
+            {
+                push(new ArrayNode());
+            }
+            else if ("dict".equals(qName))
+            {
+                if (peek() instanceof ArrayNode)
+                {
+                    // create the configuration
+                    XMLPropertyListConfiguration config = new XMLPropertyListConfiguration();
+
+                    // add it to the ArrayNode
+                    ArrayNode node = (ArrayNode) peek();
+                    node.addValue(config);
+
+                    // push the root on the stack
+                    push(config.getRoot());
+                }
+            }
+        }
+
+        @Override
+        public void endElement(String uri, String localName, String qName) throws SAXException
+        {
+            if ("key".equals(qName))
+            {
+                // create a new node, link it to its parent and push it on the stack
+                PListNode node = new PListNode();
+                node.setName(buffer.toString());
+                peek().addChild(node);
+                push(node);
+            }
+            else if ("dict".equals(qName))
+            {
+                // remove the root of the XMLPropertyListConfiguration previously pushed on the stack
+                pop();
+            }
+            else
+            {
+                if ("string".equals(qName))
+                {
+                    ((PListNode) peek()).addValue(buffer.toString());
+                }
+                else if ("integer".equals(qName))
+                {
+                    ((PListNode) peek()).addIntegerValue(buffer.toString());
+                }
+                else if ("real".equals(qName))
+                {
+                    ((PListNode) peek()).addRealValue(buffer.toString());
+                }
+                else if ("true".equals(qName))
+                {
+                    ((PListNode) peek()).addTrueValue();
+                }
+                else if ("false".equals(qName))
+                {
+                    ((PListNode) peek()).addFalseValue();
+                }
+                else if ("data".equals(qName))
+                {
+                    ((PListNode) peek()).addDataValue(buffer.toString());
+                }
+                else if ("date".equals(qName))
+                {
+                    try
+                    {
+                        ((PListNode) peek()).addDateValue(buffer.toString());
+                    }
+                    catch (IllegalArgumentException iex)
+                    {
+                        getLogger().warn(
+                                "Ignoring invalid date property " + buffer);
+                    }
+                }
+                else if ("array".equals(qName))
+                {
+                    ArrayNode array = (ArrayNode) pop();
+                    ((PListNode) peek()).addList(array);
+                }
+
+                // remove the plist node on the stack once the value has been parsed,
+                // array nodes remains on the stack for the next values in the list
+                if (!(peek() instanceof ArrayNode))
+                {
+                    pop();
+                }
+            }
+
+            buffer.setLength(0);
+        }
+
+        @Override
+        public void characters(char[] ch, int start, int length) throws SAXException
+        {
+            buffer.append(ch, start, length);
+        }
+    }
+
+    /**
+     * Node extension with addXXX methods to parse the typed data passed by the SAX handler.
+     * <b>Do not use this class !</b> It is used internally by XMLPropertyConfiguration
+     * to parse the configuration file, it may be removed at any moment in the future.
+     */
+    public static class PListNode extends Node
+    {
+        /**
+         * The serial version UID.
+         */
+        private static final long serialVersionUID = -7614060264754798317L;
+
+        /**
+         * The MacOS FORMAT of dates in plist files. Note: Because
+         * {@code SimpleDateFormat} is not thread-safe, each access has to be
+         * synchronized.
+         */
+        private static final DateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+        static
+        {
+            FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
+        }
+
+        /**
+         * The GNUstep FORMAT of dates in plist files. Note: Because
+         * {@code SimpleDateFormat} is not thread-safe, each access has to be
+         * synchronized.
+         */
+        private static final DateFormat GNUSTEP_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
+
+        /**
+         * Update the value of the node. If the existing value is null, it's
+         * replaced with the new value. If the existing value is a list, the
+         * specified value is appended to the list. If the existing value is
+         * not null, a list with the two values is built.
+         *
+         * @param value the value to be added
+         */
+        public void addValue(Object value)
+        {
+            if (getValue() == null)
+            {
+                setValue(value);
+            }
+            else if (getValue() instanceof Collection)
+            {
+                // This is safe because we create the collections ourselves
+                @SuppressWarnings("unchecked")
+                Collection<Object> collection = (Collection<Object>) getValue();
+                collection.add(value);
+            }
+            else
+            {
+                List<Object> list = new ArrayList<Object>();
+                list.add(getValue());
+                list.add(value);
+                setValue(list);
+            }
+        }
+
+        /**
+         * Parse the specified string as a date and add it to the values of the node.
+         *
+         * @param value the value to be added
+         * @throws IllegalArgumentException if the date string cannot be parsed
+         */
+        public void addDateValue(String value)
+        {
+            try
+            {
+                if (value.indexOf(' ') != -1)
+                {
+                    // parse the date using the GNUstep FORMAT
+                    synchronized (GNUSTEP_FORMAT)
+                    {
+                        addValue(GNUSTEP_FORMAT.parse(value));
+                    }
+                }
+                else
+                {
+                    // parse the date using the MacOS X FORMAT
+                    synchronized (FORMAT)
+                    {
+                        addValue(FORMAT.parse(value));
+                    }
+                }
+            }
+            catch (ParseException e)
+            {
+                throw new IllegalArgumentException(String.format(
+                        "'%s' cannot be parsed to a date!", value), e);
+            }
+        }
+
+        /**
+         * Parse the specified string as a byte array in base 64 FORMAT
+         * and add it to the values of the node.
+         *
+         * @param value the value to be added
+         */
+        public void addDataValue(String value)
+        {
+            addValue(Base64.decodeBase64(value.getBytes()));
+        }
+
+        /**
+         * Parse the specified string as an Interger and add it to the values of the node.
+         *
+         * @param value the value to be added
+         */
+        public void addIntegerValue(String value)
+        {
+            addValue(new BigInteger(value));
+        }
+
+        /**
+         * Parse the specified string as a Double and add it to the values of the node.
+         *
+         * @param value the value to be added
+         */
+        public void addRealValue(String value)
+        {
+            addValue(new BigDecimal(value));
+        }
+
+        /**
+         * Add a boolean value 'true' to the values of the node.
+         */
+        public void addTrueValue()
+        {
+            addValue(Boolean.TRUE);
+        }
+
+        /**
+         * Add a boolean value 'false' to the values of the node.
+         */
+        public void addFalseValue()
+        {
+            addValue(Boolean.FALSE);
+        }
+
+        /**
+         * Add a sublist to the values of the node.
+         *
+         * @param node the node whose value will be added to the current node value
+         */
+        public void addList(ArrayNode node)
+        {
+            addValue(node.getValue());
+        }
+    }
+
+    /**
+     * Container for array elements. <b>Do not use this class !</b>
+     * It is used internally by XMLPropertyConfiguration to parse the
+     * configuration file, it may be removed at any moment in the future.
+     */
+    public static class ArrayNode extends PListNode
+    {
+        /**
+         * The serial version UID.
+         */
+        private static final long serialVersionUID = 5586544306664205835L;
+
+        /** The list of values in the array. */
+        private List<Object> list = new ArrayList<Object>();
+
+        /**
+         * Add an object to the array.
+         *
+         * @param value the value to be added
+         */
+        @Override
+        public void addValue(Object value)
+        {
+            list.add(value);
+        }
+
+        /**
+         * Return the list of values in the array.
+         *
+         * @return the {@link List} of values
+         */
+        @Override
+        public Object getValue()
+        {
+            return list;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/plist/package.html b/src/main/java/org/apache/commons/configuration/plist/package.html
new file mode 100644
index 0000000..5a4acb8
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/plist/package.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<html>
+<head>
+</head>
+<body>
+
+<p>
+Configuration classes supporting NeXT / OpenStep /GNUStep style configuration.
+</p>
+<p>
+<font size="-2">$Id: package.html 527436 2007-04-11 09:57:29Z ebourg $</font>
+</p>
+
+</body>
+</html>
diff --git a/src/main/java/org/apache/commons/configuration/reloading/FileChangedReloadingStrategy.java b/src/main/java/org/apache/commons/configuration/reloading/FileChangedReloadingStrategy.java
new file mode 100644
index 0000000..bcdda7a
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/reloading/FileChangedReloadingStrategy.java
@@ -0,0 +1,226 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.reloading;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.apache.commons.configuration.ConfigurationUtils;
+import org.apache.commons.configuration.FileConfiguration;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <p>A reloading strategy that will reload the configuration every time its
+ * underlying file is changed.</p>
+ * <p>This reloading strategy does not actively monitor a configuration file,
+ * but is triggered by its associated configuration whenever properties are
+ * accessed. It then checks the configuration file's last modification date
+ * and causes a reload if this has changed.</p>
+ * <p>To avoid permanent disc access on successive property lookups a refresh
+ * delay can be specified. This has the effect that the configuration file's
+ * last modification date is only checked once in this delay period. The default
+ * value for this refresh delay is 5 seconds.</p>
+ * <p>This strategy only works with FileConfiguration instances.</p>
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: FileChangedReloadingStrategy.java 1210646 2011-12-05 21:25:01Z oheger $
+ * @since 1.1
+ */
+public class FileChangedReloadingStrategy implements ReloadingStrategy
+{
+    /** Constant for the jar URL protocol.*/
+    private static final String JAR_PROTOCOL = "jar";
+
+    /** Constant for the default refresh delay.*/
+    private static final int DEFAULT_REFRESH_DELAY = 5000;
+
+    /** Stores a reference to the configuration to be monitored.*/
+    protected FileConfiguration configuration;
+
+    /** The last time the configuration file was modified. */
+    protected long lastModified;
+
+    /** The last time the file was checked for changes. */
+    protected long lastChecked;
+
+    /** The minimum delay in milliseconds between checks. */
+    protected long refreshDelay = DEFAULT_REFRESH_DELAY;
+
+    /** A flag whether a reload is required.*/
+    private boolean reloading;
+
+    /** The Log to use for diagnostic messages */
+    private Log logger = LogFactory.getLog(FileChangedReloadingStrategy.class);
+
+    public void setConfiguration(FileConfiguration configuration)
+    {
+        this.configuration = configuration;
+    }
+
+    public void init()
+    {
+        updateLastModified();
+    }
+
+    public boolean reloadingRequired()
+    {
+        if (!reloading)
+        {
+            long now = System.currentTimeMillis();
+
+            if (now > lastChecked + refreshDelay)
+            {
+                lastChecked = now;
+                if (hasChanged())
+                {
+                    if (logger.isDebugEnabled())
+                    {
+                        logger.debug("File change detected: " + getName());
+                    }
+                    reloading = true;
+                }
+            }
+        }
+
+        return reloading;
+    }
+
+    public void reloadingPerformed()
+    {
+        updateLastModified();
+    }
+
+    /**
+     * Return the minimal time in milliseconds between two reloadings.
+     *
+     * @return the refresh delay (in milliseconds)
+     */
+    public long getRefreshDelay()
+    {
+        return refreshDelay;
+    }
+
+    /**
+     * Set the minimal time between two reloadings.
+     *
+     * @param refreshDelay refresh delay in milliseconds
+     */
+    public void setRefreshDelay(long refreshDelay)
+    {
+        this.refreshDelay = refreshDelay;
+    }
+
+    /**
+     * Update the last modified time.
+     */
+    protected void updateLastModified()
+    {
+        File file = getFile();
+        if (file != null)
+        {
+            lastModified = file.lastModified();
+        }
+        reloading = false;
+    }
+
+    /**
+     * Check if the configuration has changed since the last time it was loaded.
+     *
+     * @return a flag whether the configuration has changed
+     */
+    protected boolean hasChanged()
+    {
+        File file = getFile();
+        if (file == null || !file.exists())
+        {
+            if (logger.isWarnEnabled() && lastModified != 0)
+            {
+                logger.warn("File was deleted: " + getName(file));
+                lastModified = 0;
+            }
+            return false;
+        }
+
+        return file.lastModified() > lastModified;
+    }
+
+    /**
+     * Returns the file that is monitored by this strategy. Note that the return
+     * value can be <b>null </b> under some circumstances.
+     *
+     * @return the monitored file
+     */
+    protected File getFile()
+    {
+        return (configuration.getURL() != null) ? fileFromURL(configuration
+                .getURL()) : configuration.getFile();
+    }
+
+    /**
+     * Helper method for transforming a URL into a file object. This method
+     * handles file: and jar: URLs.
+     *
+     * @param url the URL to be converted
+     * @return the resulting file or <b>null </b>
+     */
+    private File fileFromURL(URL url)
+    {
+        if (JAR_PROTOCOL.equals(url.getProtocol()))
+        {
+            String path = url.getPath();
+            try
+            {
+                return ConfigurationUtils.fileFromURL(new URL(path.substring(0,
+                        path.indexOf('!'))));
+            }
+            catch (MalformedURLException mex)
+            {
+                return null;
+            }
+        }
+        else
+        {
+            return ConfigurationUtils.fileFromURL(url);
+        }
+    }
+
+    private String getName()
+    {
+        return getName(getFile());
+    }
+
+    private String getName(File file)
+    {
+        String name = configuration.getURL().toString();
+        if (name == null)
+        {
+            if (file != null)
+            {
+                name = file.getAbsolutePath();
+            }
+            else
+            {
+                name = "base: " + configuration.getBasePath()
+                       + "file: " + configuration.getFileName();
+            }
+        }
+        return name;
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/reloading/InvariantReloadingStrategy.java b/src/main/java/org/apache/commons/configuration/reloading/InvariantReloadingStrategy.java
new file mode 100644
index 0000000..0553179
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/reloading/InvariantReloadingStrategy.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.reloading;
+
+import org.apache.commons.configuration.FileConfiguration;
+
+/**
+ * A strategy that never triggers a reloading.
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: InvariantReloadingStrategy.java 1210646 2011-12-05 21:25:01Z oheger $
+ * @since 1.1
+ */
+public class InvariantReloadingStrategy implements ReloadingStrategy
+{
+    public void setConfiguration(FileConfiguration configuration)
+    {
+    }
+
+    public void init()
+    {
+    }
+
+    public boolean reloadingRequired()
+    {
+        return false;
+    }
+
+    public void reloadingPerformed()
+    {
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/reloading/ManagedReloadingStrategy.java b/src/main/java/org/apache/commons/configuration/reloading/ManagedReloadingStrategy.java
new file mode 100644
index 0000000..2d0e351
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/reloading/ManagedReloadingStrategy.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.reloading;
+
+import org.apache.commons.configuration.FileConfiguration;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * A strategy to reload configuration based on management requests. Designed for
+ * JMX management.
+ *
+ * @author Nicolas De loof
+ * @version $Id: ManagedReloadingStrategy.java 1210646 2011-12-05 21:25:01Z oheger $
+ */
+public class ManagedReloadingStrategy implements ReloadingStrategy,
+        ManagedReloadingStrategyMBean
+{
+    /** The logger. */
+    private Log log = LogFactory.getLog(ManagedReloadingStrategy.class);
+
+    /** Stores a reference to the associated configuration. */
+    private FileConfiguration configuration;
+
+    /** A flag whether a reload is required. */
+    private boolean reloadingRequired;
+
+    /**
+     * @see org.apache.commons.configuration.reloading.ReloadingStrategy#init()
+     */
+    public void init()
+    {
+    }
+
+    /**
+     * @see org.apache.commons.configuration.reloading.ReloadingStrategy#reloadingPerformed()
+     */
+    public void reloadingPerformed()
+    {
+        reloadingRequired = false;
+    }
+
+    /**
+     * Checks whether reloading is required. This implementation checks whether
+     * the {@code refresh()} method has been invoked.
+     *
+     * @return a flag whether reloading is required
+     * @see org.apache.commons.configuration.reloading.ReloadingStrategy#reloadingRequired()
+     */
+    public boolean reloadingRequired()
+    {
+        return reloadingRequired;
+    }
+
+    /**
+     * Sets the associated configuration.
+     *
+     * @param configuration the associated configuration
+     */
+    public void setConfiguration(FileConfiguration configuration)
+    {
+        this.configuration = configuration;
+    }
+
+    /**
+     * Tells this strategy that the monitored configuration file should be
+     * refreshed. This method will typically be called from outside (through an
+     * exposed MBean) on behalf of an administrator.
+     *
+     * @see org.apache.commons.configuration.reloading.ManagedReloadingStrategyMBean#refresh()
+     */
+    public void refresh()
+    {
+        log.info("Reloading configuration.");
+        this.reloadingRequired = true;
+        // force reloading
+        configuration.isEmpty();
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/reloading/ManagedReloadingStrategyMBean.java b/src/main/java/org/apache/commons/configuration/reloading/ManagedReloadingStrategyMBean.java
new file mode 100644
index 0000000..205bbcb
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/reloading/ManagedReloadingStrategyMBean.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.reloading;
+
+/**
+ * MBean definition for managing configuration reload.
+ *
+ * @author Nicolas De Loof
+ * @version $Id: ManagedReloadingStrategyMBean.java 1210646 2011-12-05 21:25:01Z oheger $
+ */
+public interface ManagedReloadingStrategyMBean
+{
+    /**
+     * Management method to force configuration reload.
+     */
+    void refresh();
+}
diff --git a/src/main/java/org/apache/commons/configuration/reloading/Reloadable.java b/src/main/java/org/apache/commons/configuration/reloading/Reloadable.java
new file mode 100644
index 0000000..c3b037c
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/reloading/Reloadable.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.reloading;
+
+/**
+ * Interface that allows other objects to synchronize on a root lock.
+ *
+ * @author <a
+ *         href="http://commons.apache.org/configuration/team-list.html">Commons
+ *         Configuration team</a>
+ * @since 1.7
+ * @version $Id: Reloadable.java 1162387 2011-08-27 16:05:20Z oheger $
+ */
+public interface Reloadable
+{
+    Object getReloadLock();
+}
diff --git a/src/main/java/org/apache/commons/configuration/reloading/ReloadingStrategy.java b/src/main/java/org/apache/commons/configuration/reloading/ReloadingStrategy.java
new file mode 100644
index 0000000..32f91ee
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/reloading/ReloadingStrategy.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.reloading;
+
+import org.apache.commons.configuration.FileConfiguration;
+
+/**
+ * A strategy to decide if a configuration should be reloaded.
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: ReloadingStrategy.java 1210646 2011-12-05 21:25:01Z oheger $
+ * @since 1.1
+ */
+public interface ReloadingStrategy
+{
+    /**
+     * Set the configuration managed by this strategy.
+     *
+     * @param configuration the configuration to monitor
+     */
+    void setConfiguration(FileConfiguration configuration);
+
+    /**
+     * Initialize the strategy.
+     */
+    void init();
+
+    /**
+     * Tell if the evaluation of the strategy requires to reload the configuration.
+     *
+     * @return a flag whether a reload should be performed
+     */
+    boolean reloadingRequired();
+
+    /**
+     * Notify the strategy that the file has been reloaded.
+     */
+    void reloadingPerformed();
+
+}
diff --git a/src/main/java/org/apache/commons/configuration/reloading/VFSFileChangedReloadingStrategy.java b/src/main/java/org/apache/commons/configuration/reloading/VFSFileChangedReloadingStrategy.java
new file mode 100644
index 0000000..f7bc24f
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/reloading/VFSFileChangedReloadingStrategy.java
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.reloading;
+
+import org.apache.commons.configuration.ConfigurationRuntimeException;
+import org.apache.commons.configuration.FileConfiguration;
+import org.apache.commons.configuration.FileSystem;
+import org.apache.commons.configuration.FileSystemBased;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileSystemManager;
+import org.apache.commons.vfs2.VFS;
+
+/**
+ * <p>
+ * A file-based reloading strategy that uses <a
+ * href="http://commons.apache.org/vfs/">Commons VFS</a> to determine when a
+ * file was changed.
+ * </p>
+ * <p>
+ * This reloading strategy is very similar to
+ * {@link FileChangedReloadingStrategy}, except for the fact that it uses VFS
+ * and thus can deal with a variety of different configuration sources.
+ * </p>
+ * <p>
+ * This strategy only works with FileConfiguration instances.
+ * </p>
+ *
+ * @author <a
+ *         href="http://commons.apache.org/configuration/team-list.html">Commons
+ *         Configuration team</a>
+ * @version $Id: VFSFileChangedReloadingStrategy.java 1162383 2011-08-27 15:57:11Z oheger $
+ * @since 1.7
+ */
+public class VFSFileChangedReloadingStrategy implements ReloadingStrategy
+{
+    /** Constant for the default refresh delay.*/
+    private static final int DEFAULT_REFRESH_DELAY = 5000;
+
+    /** Stores a reference to the configuration to be monitored.*/
+    protected FileConfiguration configuration;
+
+    /** The last time the configuration file was modified. */
+    protected long lastModified;
+
+    /** The last time the file was checked for changes. */
+    protected long lastChecked;
+
+    /** The minimum delay in milliseconds between checks. */
+    protected long refreshDelay = DEFAULT_REFRESH_DELAY;
+
+    /** A flag whether a reload is required.*/
+    private boolean reloading;
+
+    /** Stores the logger.*/
+    private Log log = LogFactory.getLog(getClass());
+
+    public void setConfiguration(FileConfiguration configuration)
+    {
+        this.configuration = configuration;
+    }
+
+    public void init()
+    {
+        if (configuration.getURL() == null && configuration.getFileName() == null)
+        {
+            return;
+        }
+        if (this.configuration == null)
+        {
+            throw new IllegalStateException("No configuration has been set for this strategy");
+        }
+        updateLastModified();
+    }
+
+    public boolean reloadingRequired()
+    {
+        if (!reloading)
+        {
+            long now = System.currentTimeMillis();
+
+            if (now > lastChecked + refreshDelay)
+            {
+                lastChecked = now;
+                if (hasChanged())
+                {
+                    reloading = true;
+                }
+            }
+        }
+
+        return reloading;
+    }
+
+    public void reloadingPerformed()
+    {
+        updateLastModified();
+    }
+
+    /**
+     * Return the minimal time in milliseconds between two reloadings.
+     *
+     * @return the refresh delay (in milliseconds)
+     */
+    public long getRefreshDelay()
+    {
+        return refreshDelay;
+    }
+
+    /**
+     * Set the minimal time between two reloadings.
+     *
+     * @param refreshDelay refresh delay in milliseconds
+     */
+    public void setRefreshDelay(long refreshDelay)
+    {
+        this.refreshDelay = refreshDelay;
+    }
+
+    /**
+     * Update the last modified time.
+     */
+    protected void updateLastModified()
+    {
+        FileObject file = getFile();
+        if (file != null)
+        {
+            try
+            {
+                lastModified = file.getContent().getLastModifiedTime();
+            }
+            catch (FileSystemException fse)
+            {
+                log.error("Unable to get last modified time for" + file.getName().getURI());
+            }
+        }
+        reloading = false;
+    }
+
+    /**
+     * Check if the configuration has changed since the last time it was loaded.
+     *
+     * @return a flag whether the configuration has changed
+     */
+    protected boolean hasChanged()
+    {
+        FileObject file = getFile();
+        try
+        {
+            if (file == null || !file.exists())
+            {
+                return false;
+            }
+
+            return file.getContent().getLastModifiedTime() > lastModified;
+        }
+        catch (FileSystemException ex)
+        {
+            log.error("Unable to get last modified time for" + file.getName().getURI());
+            return false;
+        }
+    }
+
+    /**
+     * Returns the file that is monitored by this strategy. Note that the return
+     * value can be <b>null </b> under some circumstances.
+     *
+     * @return the monitored file
+     */
+    protected FileObject getFile()
+    {
+        try
+        {
+            FileSystemManager fsManager = VFS.getManager();
+            FileSystem fs = ((FileSystemBased) configuration).getFileSystem();
+            String uri = fs.getPath(null, configuration.getURL(), configuration.getBasePath(),
+                configuration.getFileName());
+            if (uri == null)
+            {
+                throw new ConfigurationRuntimeException("Unable to determine file to monitor");
+            }
+            return fsManager.resolveFile(uri);
+        }
+        catch (FileSystemException fse)
+        {
+            String msg = "Unable to monitor " + configuration.getURL().toString();
+            log.error(msg);
+            throw new ConfigurationRuntimeException(msg, fse);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/reloading/package.html b/src/main/java/org/apache/commons/configuration/reloading/package.html
new file mode 100644
index 0000000..b9f1ba8
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/reloading/package.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<html>
+<head>
+</head>
+<body>
+
+<p>
+The <code>reloading</code> package contains the definition of the
+<code>ReloadingStrategy</code> interface, which provides automatic reloading
+facilities for file based configurations. There are also some concrete
+implementations of this interface that can be used out of the box.
+</p>
+<p>
+<font size="-2">$Id: package.html 439648 2006-09-02 20:42:10Z oheger $</font>
+</p>
+
+</body>
+</html>
diff --git a/src/main/java/org/apache/commons/configuration/resolver/CatalogResolver.java b/src/main/java/org/apache/commons/configuration/resolver/CatalogResolver.java
new file mode 100644
index 0000000..4e4cf7a
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/resolver/CatalogResolver.java
@@ -0,0 +1,521 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.resolver;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.FileNameMap;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Vector;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.ConfigurationUtils;
+import org.apache.commons.configuration.FileSystem;
+import org.apache.commons.lang.text.StrSubstitutor;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.xml.resolver.CatalogException;
+import org.apache.xml.resolver.readers.CatalogReader;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * Thin wrapper around xml commons CatalogResolver to allow list of catalogs
+ * to be provided.
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @since 1.7
+ * @version $Id: CatalogResolver.java 1301991 2012-03-17 20:18:02Z sebb $
+ */
+public class CatalogResolver implements EntityResolver
+{
+    /**
+     * Debug everything.
+     */
+    private static final int DEBUG_ALL = 9;
+
+    /**
+     * Normal debug setting.
+     */
+    private static final int DEBUG_NORMAL = 4;
+
+    /**
+     * Debug nothing.
+     */
+    private static final int DEBUG_NONE = 0;
+
+    /**
+     * The CatalogManager
+     */
+    protected CatalogManager manager = new CatalogManager();
+
+    /**
+     * The FileSystem in use.
+     */
+    protected FileSystem fs = FileSystem.getDefaultFileSystem();
+
+    /**
+     * The CatalogResolver
+     */
+    private org.apache.xml.resolver.tools.CatalogResolver resolver;
+
+    /**
+     * Stores the logger.
+     */
+    private Log log;
+
+    /**
+     * Constructs the CatalogResolver
+     */
+    public CatalogResolver()
+    {
+        manager.setIgnoreMissingProperties(true);
+        manager.setUseStaticCatalog(false);
+        manager.setFileSystem(fs);
+        setLogger(null);
+    }
+
+    /**
+     * Set the list of catalog file names
+     *
+     * @param catalogs The delimited list of catalog files.
+     */
+    public void setCatalogFiles(String catalogs)
+    {
+        manager.setCatalogFiles(catalogs);
+    }
+
+    /**
+     * Set the FileSystem.
+     * @param fileSystem The FileSystem.
+     */
+    public void setFileSystem(FileSystem fileSystem)
+    {
+        this.fs = fileSystem;
+        manager.setFileSystem(fileSystem);
+    }
+
+    /**
+     * Set the base path.
+     * @param baseDir The base path String.
+     */
+    public void setBaseDir(String baseDir)
+    {
+        manager.setBaseDir(baseDir);
+    }
+
+    /**
+     * Set the StrSubstitutor.
+     * @param substitutor The StrSubstitutor.
+     */
+    public void setSubstitutor(StrSubstitutor substitutor)
+    {
+        manager.setSubstitutor(substitutor);
+    }
+
+    /**
+     * Enables debug logging of xml-commons Catalog processing.
+     * @param debug True if debugging should be enabled, false otherwise.
+     */
+    public void setDebug(boolean debug)
+    {
+        if (debug)
+        {
+            manager.setVerbosity(DEBUG_ALL);
+        }
+        else
+        {
+            manager.setVerbosity(DEBUG_NONE);
+        }
+    }
+
+    /**
+     * Implements the {@code resolveEntity} method
+     * for the SAX interface.
+     * <p/>
+     * <p>Presented with an optional public identifier and a system
+     * identifier, this function attempts to locate a mapping in the
+     * catalogs.</p>
+     * <p/>
+     * <p>If such a mapping is found, the resolver attempts to open
+     * the mapped value as an InputSource and return it. Exceptions are
+     * ignored and null is returned if the mapped value cannot be opened
+     * as an input source.</p>
+     * <p/>
+     * <p>If no mapping is found (or an error occurs attempting to open
+     * the mapped value as an input source), null is returned and the system
+     * will use the specified system identifier as if no entityResolver
+     * was specified.</p>
+     *
+     * @param publicId The public identifier for the entity in question.
+     *                 This may be null.
+     * @param systemId The system identifier for the entity in question.
+     *                 XML requires a system identifier on all external entities, so this
+     *                 value is always specified.
+     * @return An InputSource for the mapped identifier, or null.
+     * @throws SAXException if an error occurs.
+     */
+    public InputSource resolveEntity(String publicId, String systemId)
+            throws SAXException
+    {
+        String resolved = getResolver().getResolvedEntity(publicId, systemId);
+
+        if (resolved != null)
+        {
+            String badFilePrefix = "file://";
+            String correctFilePrefix = "file:///";
+
+            // Java 5 has a bug when constructing file URLS
+            if (resolved.startsWith(badFilePrefix) && !resolved.startsWith(correctFilePrefix))
+            {
+                resolved = correctFilePrefix + resolved.substring(badFilePrefix.length());
+            }
+
+            try
+            {
+                InputStream is = fs.getInputStream(null, resolved);
+                InputSource iSource = new InputSource(resolved);
+                iSource.setPublicId(publicId);
+                iSource.setByteStream(is);
+                return iSource;
+            }
+            catch (Exception e)
+            {
+                log.warn("Failed to create InputSource for " + resolved + " ("
+                                + e.toString() + ")");
+                return null;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns the logger used by this configuration object.
+     *
+     * @return the logger
+     */
+    public Log getLogger()
+    {
+        return log;
+    }
+
+    /**
+     * Allows to set the logger to be used by this configuration object. This
+     * method makes it possible for clients to exactly control logging behavior.
+     * Per default a logger is set that will ignore all log messages. Derived
+     * classes that want to enable logging should call this method during their
+     * initialization with the logger to be used.
+     *
+     * @param log the new logger
+     */
+    public void setLogger(Log log)
+    {
+        this.log = (log != null) ? log : LogFactory.getLog(CatalogResolver.class);
+    }
+
+    private synchronized org.apache.xml.resolver.tools.CatalogResolver getResolver()
+    {
+        if (resolver == null)
+        {
+            resolver = new org.apache.xml.resolver.tools.CatalogResolver(manager);
+        }
+        return resolver;
+    }
+
+    /**
+     * Extend the CatalogManager to make the FileSystem and base directory accessible.
+     */
+    public static class CatalogManager extends org.apache.xml.resolver.CatalogManager
+    {
+        /** The static catalog used by this manager. */
+        private static org.apache.xml.resolver.Catalog staticCatalog;
+
+        /** The FileSystem */
+        private FileSystem fs;
+
+        /** The base directory */
+        private String baseDir = System.getProperty("user.dir");
+
+        /** The String Substitutor */
+        private StrSubstitutor substitutor;
+
+        /**
+         * Set the FileSystem
+         * @param fileSystem The FileSystem in use.
+         */
+        public void setFileSystem(FileSystem fileSystem)
+        {
+            this.fs = fileSystem;
+        }
+
+        /**
+         * Retrieve the FileSystem.
+         * @return The FileSystem.
+         */
+        public FileSystem getFileSystem()
+        {
+            return this.fs;
+        }
+
+        /**
+         * Set the base directory.
+         * @param baseDir The base directory.
+         */
+        public void setBaseDir(String baseDir)
+        {
+            if (baseDir != null)
+            {
+                this.baseDir = baseDir;
+            }
+        }
+
+        /**
+         * Return the base directory.
+         * @return The base directory.
+         */
+        public String getBaseDir()
+        {
+            return this.baseDir;
+        }
+
+        public void setSubstitutor(StrSubstitutor substitutor)
+        {
+            this.substitutor = substitutor;
+        }
+
+        public StrSubstitutor getStrSubstitutor()
+        {
+            return this.substitutor;
+        }
+
+
+        /**
+         * Get a new catalog instance. This method is only overridden because xml-resolver
+         * might be in a parent ClassLoader and will be incapable of loading our Catalog
+         * implementation.
+         *
+         * This method always returns a new instance of the underlying catalog class.
+         * @return the Catalog.
+         */
+        @Override
+        public org.apache.xml.resolver.Catalog getPrivateCatalog()
+        {
+            org.apache.xml.resolver.Catalog catalog = staticCatalog;
+
+            if (catalog == null || !getUseStaticCatalog())
+            {
+                try
+                {
+                    catalog = new Catalog();
+                    catalog.setCatalogManager(this);
+                    catalog.setupReaders();
+                    catalog.loadSystemCatalogs();
+                }
+                catch (Exception ex)
+                {
+                    ex.printStackTrace();
+                }
+
+                if (getUseStaticCatalog())
+                {
+                    staticCatalog = catalog;
+                }
+            }
+
+            return catalog;
+        }
+
+        /**
+         * Get a catalog instance.
+         *
+         * If this manager uses static catalogs, the same static catalog will
+         * always be returned. Otherwise a new catalog will be returned.
+         * @return The Catalog.
+         */
+        @Override
+        public org.apache.xml.resolver.Catalog getCatalog()
+        {
+            return getPrivateCatalog();
+        }
+    }
+
+    /**
+     * Overrides the Catalog implementation to use the underlying FileSystem.
+     */
+    public static class Catalog extends org.apache.xml.resolver.Catalog
+    {
+        /** The FileSystem */
+        private FileSystem fs;
+
+        /** FileNameMap to determine the mime type */
+        private FileNameMap fileNameMap = URLConnection.getFileNameMap();
+
+        /**
+         * Load the catalogs.
+         * @throws IOException if an error occurs.
+         */
+        @Override
+        public void loadSystemCatalogs() throws IOException
+        {
+            fs = ((CatalogManager) catalogManager).getFileSystem();
+            String base = ((CatalogManager) catalogManager).getBaseDir();
+
+            // This is safe because the catalog manager returns a vector of strings.
+            @SuppressWarnings("unchecked")
+            Vector<String> catalogs = catalogManager.getCatalogFiles();
+            if (catalogs != null)
+            {
+                for (int count = 0; count < catalogs.size(); count++)
+                {
+                    String fileName = catalogs.elementAt(count);
+
+                    URL url = null;
+                    InputStream is = null;
+
+                    try
+                    {
+                        url = ConfigurationUtils.locate(fs, base, fileName);
+                        if (url != null)
+                        {
+                            is = fs.getInputStream(url);
+                        }
+                    }
+                    catch (ConfigurationException ce)
+                    {
+                        String name = (url == null) ? fileName : url.toString();
+                        // Ignore the exception.
+                        catalogManager.debug.message(DEBUG_ALL,
+                            "Unable to get input stream for " + name + ". " + ce.getMessage());
+                    }
+                    if (is != null)
+                    {
+                        String mimeType = fileNameMap.getContentTypeFor(fileName);
+                        try
+                        {
+                            if (mimeType != null)
+                            {
+                                parseCatalog(mimeType, is);
+                                continue;
+                            }
+                        }
+                        catch (Exception ex)
+                        {
+                            // Ignore the exception.
+                            catalogManager.debug.message(DEBUG_ALL,
+                                "Exception caught parsing input stream for " + fileName + ". "
+                                + ex.getMessage());
+                        }
+                        finally
+                        {
+                            is.close();
+                        }
+                    }
+                    parseCatalog(base, fileName);
+                }
+            }
+
+        }
+
+        /**
+         * Parse the specified catalog file.
+         * @param baseDir The base directory, if not included in the file name.
+         * @param fileName The catalog file. May be a full URI String.
+         * @throws IOException If an error occurs.
+         */
+        public void parseCatalog(String baseDir, String fileName) throws IOException
+        {
+            base = ConfigurationUtils.locate(fs, baseDir, fileName);
+            catalogCwd = base;
+            default_override = catalogManager.getPreferPublic();
+            catalogManager.debug.message(DEBUG_NORMAL, "Parse catalog: " + fileName);
+
+            boolean parsed = false;
+
+            for (int count = 0; !parsed && count < readerArr.size(); count++)
+            {
+                CatalogReader reader = (CatalogReader) readerArr.get(count);
+                InputStream inStream;
+
+                try
+                {
+                    inStream = fs.getInputStream(base);
+                }
+                catch (Exception ex)
+                {
+                    catalogManager.debug.message(DEBUG_NORMAL, "Unable to access " + base
+                        + ex.getMessage());
+                    break;
+                }
+
+                try
+                {
+                    reader.readCatalog(this, inStream);
+                    parsed = true;
+                }
+                catch (CatalogException ce)
+                {
+                    catalogManager.debug.message(DEBUG_NORMAL, "Parse failed for " + fileName
+                            + ce.getMessage());
+                    if (ce.getExceptionType() == CatalogException.PARSE_FAILED)
+                    {
+                        break;
+                    }
+                    else
+                    {
+                        // try again!
+                        continue;
+                    }
+                }
+                finally
+                {
+                    try
+                    {
+                        inStream.close();
+                    }
+                    catch (IOException ioe)
+                    {
+                        // Ignore the exception.
+                        inStream = null;
+                    }
+                }
+            }
+
+            if (parsed)
+            {
+                parsePendingCatalogs();
+            }
+        }
+
+        /**
+         * Perform character normalization on a URI reference.
+         *
+         * @param uriref The URI reference
+         * @return The normalized URI reference.
+         */
+        @Override
+        protected String normalizeURI(String uriref)
+        {
+            StrSubstitutor substitutor = ((CatalogManager) catalogManager).getStrSubstitutor();
+            String resolved = substitutor != null ? substitutor.replace(uriref) : uriref;
+            return super.normalizeURI(resolved);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/resolver/DefaultEntityResolver.java b/src/main/java/org/apache/commons/configuration/resolver/DefaultEntityResolver.java
new file mode 100644
index 0000000..46a192e
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/resolver/DefaultEntityResolver.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.resolver;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * The DefaultEntityResolver used by XML Configurations.
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @since 1.7
+ * @version $Id: DefaultEntityResolver.java 1301991 2012-03-17 20:18:02Z sebb $
+ */
+public class DefaultEntityResolver implements EntityResolver, EntityRegistry
+{
+    /** Stores a map with the registered public IDs.*/
+    private Map<String, URL> registeredEntities = new HashMap<String, URL>();
+
+    /**
+     * <p>
+     * Registers the specified URL for the specified public identifier.
+     * </p>
+     * <p>
+     * This implementation maps {@code PUBLICID}'s to URLs (from which
+     * the resource will be loaded). A common use case for this method is to
+     * register local URLs (possibly computed at runtime by a class loader) for
+     * DTDs and Schemas. This allows the performance advantage of using a local
+     * version without having to ensure every {@code SYSTEM} URI on every
+     * processed XML document is local. This implementation provides only basic
+     * functionality. If more sophisticated features are required, either calling
+     * {@code XMLConfiguration.setDocumentBuilder(DocumentBuilder)} to set a custom
+     * {@code DocumentBuilder} (which also can be initialized with a
+     * custom {@code EntityResolver}) or creating a custom entity resolver
+     * and registering it with the XMLConfiguration is recommended.
+     * </p>
+     *
+     * @param publicId Public identifier of the Entity to be resolved
+     * @param entityURL The URL to use for reading this Entity
+     * @throws IllegalArgumentException if the public ID is undefined
+     */
+    public void registerEntityId(String publicId, URL entityURL)
+    {
+        if (publicId == null)
+        {
+            throw new IllegalArgumentException("Public ID must not be null!");
+        }
+        getRegisteredEntities().put(publicId, entityURL);
+    }
+
+    /**
+     * Resolves the requested external entity. This is the default
+     * implementation of the {@code EntityResolver} interface. It checks
+     * the passed in public ID against the registered entity IDs and uses a
+     * local URL if possible.
+     *
+     * @param publicId the public identifier of the entity being referenced
+     * @param systemId the system identifier of the entity being referenced
+     * @return an input source for the specified entity
+     * @throws org.xml.sax.SAXException if a parsing exception occurs
+     */
+    public InputSource resolveEntity(String publicId, String systemId)
+            throws SAXException
+    {
+        // Has this system identifier been registered?
+        URL entityURL = null;
+        if (publicId != null)
+        {
+            entityURL = getRegisteredEntities().get(publicId);
+        }
+
+        if (entityURL != null)
+        {
+            // Obtain an InputSource for this URL. This code is based on the
+            // createInputSourceFromURL() method of Commons Digester.
+            try
+            {
+                URLConnection connection = entityURL.openConnection();
+                connection.setUseCaches(false);
+                InputStream stream = connection.getInputStream();
+                InputSource source = new InputSource(stream);
+                source.setSystemId(entityURL.toExternalForm());
+                return source;
+            }
+            catch (IOException e)
+            {
+                throw new SAXException(e);
+            }
+        }
+        else
+        {
+            // default processing behavior
+            return null;
+        }
+    }
+
+    /**
+     * Returns a map with the entity IDs that have been registered using the
+     * {@code registerEntityId()} method.
+     *
+     * @return a map with the registered entity IDs
+     */
+    public Map<String, URL> getRegisteredEntities()
+    {
+        return registeredEntities;
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/resolver/EntityRegistry.java b/src/main/java/org/apache/commons/configuration/resolver/EntityRegistry.java
new file mode 100644
index 0000000..8dc752f
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/resolver/EntityRegistry.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.resolver;
+
+import java.net.URL;
+import java.util.Map;
+
+/**
+ * Interface used for registering and retrieving PUBLICID to URL mappings.
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @since 1.7
+ * @version $Id: EntityRegistry.java 1206577 2011-11-26 20:25:52Z oheger $
+ */
+public interface EntityRegistry
+{
+    /**
+     * <p>
+     * Registers the specified URL for the specified public identifier.
+     * </p>
+     * <p>
+     * This implementation maps {@code PUBLICID}'s to URLs (from which
+     * the resource will be loaded). A common use case for this method is to
+     * register local URLs (possibly computed at runtime by a class loader) for
+     * DTDs and Schemas. This allows the performance advantage of using a local
+     * version without having to ensure every {@code SYSTEM} URI on every
+     * processed XML document is local. This implementation provides only basic
+     * functionality. If more sophisticated features are required, either calling
+     * {@code XMLConfiguration.setDocumentBuilder(DocumentBuilder)} to set a custom
+     * {@code DocumentBuilder} (which also can be initialized with a
+     * custom {@code EntityResolver}) or creating a custom entity resolver
+     * and registering it with the XMLConfiguration is recommended.
+     * </p>
+     *
+     * @param publicId Public identifier of the Entity to be resolved
+     * @param entityURL The URL to use for reading this Entity
+     * @throws IllegalArgumentException if the public ID is undefined
+     */
+    void registerEntityId(String publicId, URL entityURL);
+
+    /**
+     * Returns a map with the entity IDs that have been registered using the
+     * {@code registerEntityId()} method.
+     *
+     * @return a map with the registered entity IDs
+     */
+    Map<String, URL> getRegisteredEntities();
+}
diff --git a/src/main/java/org/apache/commons/configuration/resolver/EntityResolverSupport.java b/src/main/java/org/apache/commons/configuration/resolver/EntityResolverSupport.java
new file mode 100644
index 0000000..ff0318a
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/resolver/EntityResolverSupport.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.resolver;
+
+import org.xml.sax.EntityResolver;
+
+/**
+ * Interface that identifies the class as using an EntityResolver
+ * @author <a href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @since 1.7
+ * @version $Id: EntityResolverSupport.java 1206761 2011-11-27 16:35:34Z oheger $
+ */
+public interface EntityResolverSupport
+{
+    /**
+     * Return the EntityResolver associated with the class.
+     * @return The EntityResolver.
+     */
+    EntityResolver getEntityResolver();
+
+    /**
+     * Set the EntityResolver to associate with this class.
+     * @param resolver The EntityResolver
+     */
+    void setEntityResolver(EntityResolver resolver);
+}
diff --git a/src/main/java/org/apache/commons/configuration/resolver/package.html b/src/main/java/org/apache/commons/configuration/resolver/package.html
new file mode 100644
index 0000000..833f3fc
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/resolver/package.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<html>
+<head>
+</head>
+<body>
+
+<p>
+A package containing EntityResolvers.
+</p>
+<p>
+<font size="-2">$Id: package.html 1162380 2011-08-27 15:49:26Z oheger $</font>
+</p>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/configuration/tree/ConfigurationNode.java b/src/main/java/org/apache/commons/configuration/tree/ConfigurationNode.java
new file mode 100644
index 0000000..c8a3d88
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/tree/ConfigurationNode.java
@@ -0,0 +1,269 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import java.util.List;
+
+/**
+ * <p>
+ * Definition of an interface for the nodes of a hierarchical configuration.
+ * </p>
+ * <p>
+ * This interface defines a tree like structure for configuration data. A node
+ * has a value and can have an arbitrary number of children and attributes.
+ * </p>
+ *
+ * @since 1.3
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: ConfigurationNode.java 1234988 2012-01-23 21:12:15Z oheger $
+ */
+public interface ConfigurationNode
+{
+    /**
+     * Returns the name of this node.
+     *
+     * @return the node name
+     */
+    String getName();
+
+    /**
+     * Sets the name of this node.
+     *
+     * @param name the node name
+     */
+    void setName(String name);
+
+    /**
+     * Returns the value of this node.
+     *
+     * @return the node's value
+     */
+    Object getValue();
+
+    /**
+     * Sets the value of this node.
+     *
+     * @param val the node's value
+     */
+    void setValue(Object val);
+
+    /**
+     * Returns this node's reference.
+     *
+     * @return the reference
+     */
+    Object getReference();
+
+    /**
+     * Sets this node's reference. This reference can be used by concrete
+     * Configuration implementations to store data associated with each node. A
+     * XML based configuration for instance could here store a reference to the
+     * corresponding DOM element.
+     *
+     * @param ref the reference
+     */
+    void setReference(Object ref);
+
+    /**
+     * Returns this node's parent. Can be <b>null</b>, then this node is the
+     * top level node.
+     *
+     * @return the parent of this node
+     */
+    ConfigurationNode getParentNode();
+
+    /**
+     * Sets the parent of this node.
+     *
+     * @param parent the parent of this node
+     */
+    void setParentNode(ConfigurationNode parent);
+
+    /**
+     * Adds a child to this node.
+     *
+     * @param node the new child
+     */
+    void addChild(ConfigurationNode node);
+
+    /**
+     * Returns a list with the child nodes of this node. The nodes in this list
+     * should be in the order they were inserted into this node.
+     *
+     * @return a list with the children of this node (never <b>null</b>)
+     */
+    List<ConfigurationNode> getChildren();
+
+    /**
+     * Returns the number of this node's children.
+     *
+     * @return the number of the children of this node
+     */
+    int getChildrenCount();
+
+    /**
+     * Returns a list with all children of this node with the given name.
+     *
+     * @param name the name of the searched children
+     * @return a list with all child nodes with this name (never <b>null</b>)
+     */
+    List<ConfigurationNode> getChildren(String name);
+
+    /**
+     * Returns the number of children with the given name.
+     *
+     * @param name the name
+     * @return the number of children with this name
+     */
+    int getChildrenCount(String name);
+
+    /**
+     * Returns the child node with the given index. If the index does not
+     * exist, an exception will be thrown.
+     * @param index the index of the child node (0-based)
+     * @return the child node with this index
+     */
+    ConfigurationNode getChild(int index);
+
+    /**
+     * Removes the given node from this node's children.
+     *
+     * @param child the child node to be removed
+     * @return a flag if the node could be removed
+     */
+    boolean removeChild(ConfigurationNode child);
+
+    /**
+     * Removes all child nodes of this node with the given name.
+     *
+     * @param childName the name of the children to be removed
+     * @return a flag if at least one child was removed
+     */
+    boolean removeChild(String childName);
+
+    /**
+     * Removes all children from this node.
+     */
+    void removeChildren();
+
+    /**
+     * Returns a flag whether this node is an attribute.
+     *
+     * @return a flag whether this node is an attribute
+     */
+    boolean isAttribute();
+
+    /**
+     * Sets a flag whether this node is an attribute.
+     *
+     * @param f the attribute flag
+     */
+    void setAttribute(boolean f);
+
+    /**
+     * Returns a list with this node's attributes. Attributes are also modeled
+     * as {@code ConfigurationNode} objects.
+     *
+     * @return a list with the attributes
+     */
+    List<ConfigurationNode> getAttributes();
+
+    /**
+     * Returns the number of attributes of this node.
+     * @return the number of attributes
+     */
+    int getAttributeCount();
+
+    /**
+     * Returns a list with the attribute nodes with the given name. Attributes
+     * with same names can be added multiple times, so the return value of this
+     * method is a list.
+     *
+     * @param name the name of the attribute
+     * @return the attribute nodes with this name (never <b>null</b>)
+     */
+    List<ConfigurationNode> getAttributes(String name);
+
+    /**
+     * Returns the number of attributes with the given name.
+     *
+     * @param name the name of the attribute
+     * @return the number of attributes with this name
+     */
+    int getAttributeCount(String name);
+
+    /**
+     * Returns the attribute node with the given index. If no such index exists,
+     * an exception will be thrown.
+     * @param index the index
+     * @return the attribute node with this index
+     */
+    ConfigurationNode getAttribute(int index);
+
+    /**
+     * Removes the specified attribute from this node.
+     *
+     * @param node the attribute to remove
+     * @return a flag if the node could be removed
+     */
+    boolean removeAttribute(ConfigurationNode node);
+
+    /**
+     * Removes all attributes with the given name.
+     *
+     * @param name the name of the attributes to be removed
+     * @return a flag if at least one attribute was removed
+     */
+    boolean removeAttribute(String name);
+
+    /**
+     * Removes all attributes of this node.
+     */
+    void removeAttributes();
+
+    /**
+     * Adds the specified attribute to this node
+     *
+     * @param attr the attribute node
+     */
+    void addAttribute(ConfigurationNode attr);
+
+    /**
+     * Returns a flag if this node is defined. This means that the node contains
+     * some data.
+     *
+     * @return a flag whether this node is defined
+     */
+    boolean isDefined();
+
+    /**
+     * Visits this node and all its sub nodes. This method provides a simple
+     * means for going through a hierarchical structure of configuration nodes.
+     *
+     * @see ConfigurationNodeVisitor
+     * @param visitor the visitor
+     */
+    void visit(ConfigurationNodeVisitor visitor);
+
+    /**
+     * Returns a copy of this node.
+     * @return the copy
+     */
+    Object clone();
+}
diff --git a/src/main/java/org/apache/commons/configuration/tree/ConfigurationNodeVisitor.java b/src/main/java/org/apache/commons/configuration/tree/ConfigurationNodeVisitor.java
new file mode 100644
index 0000000..554f422
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/tree/ConfigurationNodeVisitor.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+/**
+ * <p>
+ * Definition of a <em>Visitor</em> interface for a configuration node
+ * structure.
+ * </p>
+ * <p>
+ * The {@code ConfigurationNode} interface defines a {@code visit()},
+ * which simplifies traversal of a complex node hierarchy. A configuration node
+ * implementation must provide a way of visiting all nodes in the current
+ * hierarchy. This is a typical application of the GoF <em>Visitor</em>
+ * pattern.
+ * </p>
+ *
+ * @since 1.3
+ * @see ConfigurationNode
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: ConfigurationNodeVisitor.java 1206463 2011-11-26 15:47:08Z oheger $
+ */
+public interface ConfigurationNodeVisitor
+{
+    /**
+     * Visits the specified node. This method is called before eventually
+     * existing children of this node are processed.
+     *
+     * @param node the node to be visited
+     */
+    void visitBeforeChildren(ConfigurationNode node);
+
+    /**
+     * Visits the specified node. This method is called after eventually
+     * existing children of this node have been processed.
+     *
+     * @param node the node to be visited
+     */
+    void visitAfterChildren(ConfigurationNode node);
+
+    /**
+     * Returns a flag whether the actual visit process should be aborted. This
+     * method allows a visitor implementation to state that it does not need any
+     * further data. It may be used e.g. by visitors that search for a certain
+     * node in the hierarchy. After that node was found, there is no need to
+     * process the remaining nodes, too.
+     *
+     * @return a flag if the visit process should be stopped
+     */
+    boolean terminate();
+}
diff --git a/src/main/java/org/apache/commons/configuration/tree/ConfigurationNodeVisitorAdapter.java b/src/main/java/org/apache/commons/configuration/tree/ConfigurationNodeVisitorAdapter.java
new file mode 100644
index 0000000..6e2d1f1
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/tree/ConfigurationNodeVisitorAdapter.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+/**
+ * <p>
+ * A simple adapter class that simplifies writing custom node visitor
+ * implementations.
+ * </p>
+ * <p>
+ * This class provides dummy implementations for the methods defined in the
+ * {@code ConfigurationNodeVisitor} interface. Derived classes only need
+ * to override the methods they really need.
+ * </p>
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: ConfigurationNodeVisitorAdapter.java 1206464 2011-11-26 15:49:10Z oheger $
+ */
+public class ConfigurationNodeVisitorAdapter implements
+        ConfigurationNodeVisitor
+{
+    /**
+     * Empty dummy implementation of this interface method.
+     *
+     * @param node the node
+     */
+    public void visitBeforeChildren(ConfigurationNode node)
+    {
+    }
+
+    /**
+     * Empty dummy implementation of this interface method.
+     *
+     * @param node the node
+     */
+    public void visitAfterChildren(ConfigurationNode node)
+    {
+    }
+
+    /**
+     * Dummy implementation of this interface method. Returns always <b>false</b>.
+     *
+     * @return the terminate flag
+     */
+    public boolean terminate()
+    {
+        return false;
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/tree/DefaultConfigurationKey.java b/src/main/java/org/apache/commons/configuration/tree/DefaultConfigurationKey.java
new file mode 100644
index 0000000..23575ed
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/tree/DefaultConfigurationKey.java
@@ -0,0 +1,876 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * <p>
+ * A simple class that supports creation of and iteration on configuration keys
+ * supported by a {@link DefaultExpressionEngine} object.
+ * </p>
+ * <p>
+ * For key creation the class works similar to a StringBuffer: There are several
+ * {@code appendXXXX()} methods with which single parts of a key can be
+ * constructed. All these methods return a reference to the actual object so
+ * they can be written in a chain. When using this methods the exact syntax for
+ * keys need not be known.
+ * </p>
+ * <p>
+ * This class also defines a specialized iterator for configuration keys. With
+ * such an iterator a key can be tokenized into its single parts. For each part
+ * it can be checked whether it has an associated index.
+ * </p>
+ * <p>
+ * Instances of this class are always associated with an instance of
+ * {@link DefaultExpressionEngine}, from which the current
+ * delimiters are obtained. So key creation and parsing is specific to this
+ * associated expression engine.
+ * </p>
+ *
+ * @since 1.3
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: DefaultConfigurationKey.java 1231724 2012-01-15 18:40:31Z oheger $
+ */
+public class DefaultConfigurationKey
+{
+    /** Constant for the initial StringBuffer size. */
+    private static final int INITIAL_SIZE = 32;
+
+    /** Stores a reference to the associated expression engine. */
+    private DefaultExpressionEngine expressionEngine;
+
+    /** Holds a buffer with the so far created key. */
+    private StringBuilder keyBuffer;
+
+    /**
+     * Creates a new instance of {@code DefaultConfigurationKey} and sets
+     * the associated expression engine.
+     *
+     * @param engine the expression engine
+     */
+    public DefaultConfigurationKey(DefaultExpressionEngine engine)
+    {
+        keyBuffer = new StringBuilder(INITIAL_SIZE);
+        setExpressionEngine(engine);
+    }
+
+    /**
+     * Creates a new instance of {@code DefaultConfigurationKey} and sets
+     * the associated expression engine and an initial key.
+     *
+     * @param engine the expression engine
+     * @param key the key to be wrapped
+     */
+    public DefaultConfigurationKey(DefaultExpressionEngine engine, String key)
+    {
+        setExpressionEngine(engine);
+        keyBuffer = new StringBuilder(trim(key));
+    }
+
+    /**
+     * Returns the associated default expression engine.
+     *
+     * @return the associated expression engine
+     */
+    public DefaultExpressionEngine getExpressionEngine()
+    {
+        return expressionEngine;
+    }
+
+    /**
+     * Sets the associated expression engine.
+     *
+     * @param expressionEngine the expression engine (must not be <b>null</b>)
+     */
+    public void setExpressionEngine(DefaultExpressionEngine expressionEngine)
+    {
+        if (expressionEngine == null)
+        {
+            throw new IllegalArgumentException(
+                    "Expression engine must not be null!");
+        }
+        this.expressionEngine = expressionEngine;
+    }
+
+    /**
+     * Appends the name of a property to this key. If necessary, a property
+     * delimiter will be added. If the boolean argument is set to <b>true</b>,
+     * property delimiters contained in the property name will be escaped.
+     *
+     * @param property the name of the property to be added
+     * @param escape a flag if property delimiters in the passed in property name
+     * should be escaped
+     * @return a reference to this object
+     */
+    public DefaultConfigurationKey append(String property, boolean escape)
+    {
+        String key;
+        if (escape && property != null)
+        {
+            key = escapeDelimiters(property);
+        }
+        else
+        {
+            key = property;
+        }
+        key = trim(key);
+
+        if (keyBuffer.length() > 0 && !isAttributeKey(property)
+                && key.length() > 0)
+        {
+            keyBuffer.append(getExpressionEngine().getPropertyDelimiter());
+        }
+
+        keyBuffer.append(key);
+        return this;
+    }
+
+    /**
+     * Appends the name of a property to this key. If necessary, a property
+     * delimiter will be added. Property delimiters in the given string will not
+     * be escaped.
+     *
+     * @param property the name of the property to be added
+     * @return a reference to this object
+     */
+    public DefaultConfigurationKey append(String property)
+    {
+        return append(property, false);
+    }
+
+    /**
+     * Appends an index to this configuration key.
+     *
+     * @param index the index to be appended
+     * @return a reference to this object
+     */
+    public DefaultConfigurationKey appendIndex(int index)
+    {
+        keyBuffer.append(getExpressionEngine().getIndexStart());
+        keyBuffer.append(index);
+        keyBuffer.append(getExpressionEngine().getIndexEnd());
+        return this;
+    }
+
+    /**
+     * Appends an attribute to this configuration key.
+     *
+     * @param attr the name of the attribute to be appended
+     * @return a reference to this object
+     */
+    public DefaultConfigurationKey appendAttribute(String attr)
+    {
+        keyBuffer.append(constructAttributeKey(attr));
+        return this;
+    }
+
+    /**
+     * Returns the actual length of this configuration key.
+     *
+     * @return the length of this key
+     */
+    public int length()
+    {
+        return keyBuffer.length();
+    }
+
+    /**
+     * Sets the new length of this configuration key. With this method it is
+     * possible to truncate the key, e.g. to return to a state prior calling
+     * some {@code append()} methods. The semantic is the same as the
+     * {@code setLength()} method of {@code StringBuilder}.
+     *
+     * @param len the new length of the key
+     */
+    public void setLength(int len)
+    {
+        keyBuffer.setLength(len);
+    }
+
+    /**
+     * Checks if two {@code ConfigurationKey} objects are equal. The
+     * method can be called with strings or other objects, too.
+     *
+     * @param c the object to compare
+     * @return a flag if both objects are equal
+     */
+    @Override
+    public boolean equals(Object c)
+    {
+        if (c == null)
+        {
+            return false;
+        }
+
+        return keyBuffer.toString().equals(c.toString());
+    }
+
+    /**
+     * Returns the hash code for this object.
+     *
+     * @return the hash code
+     */
+    @Override
+    public int hashCode()
+    {
+        return String.valueOf(keyBuffer).hashCode();
+    }
+
+    /**
+     * Returns a string representation of this object. This is the configuration
+     * key as a plain string.
+     *
+     * @return a string for this object
+     */
+    @Override
+    public String toString()
+    {
+        return keyBuffer.toString();
+    }
+
+    /**
+     * Tests if the specified key represents an attribute according to the
+     * current expression engine.
+     *
+     * @param key the key to be checked
+     * @return <b>true</b> if this is an attribute key, <b>false</b> otherwise
+     */
+    public boolean isAttributeKey(String key)
+    {
+        if (key == null)
+        {
+            return false;
+        }
+
+        return key.startsWith(getExpressionEngine().getAttributeStart())
+                && (getExpressionEngine().getAttributeEnd() == null || key
+                        .endsWith(getExpressionEngine().getAttributeEnd()));
+    }
+
+    /**
+     * Decorates the given key so that it represents an attribute. Adds special
+     * start and end markers. The passed in string will be modified only if does
+     * not already represent an attribute.
+     *
+     * @param key the key to be decorated
+     * @return the decorated attribute key
+     */
+    public String constructAttributeKey(String key)
+    {
+        if (key == null)
+        {
+            return StringUtils.EMPTY;
+        }
+        if (isAttributeKey(key))
+        {
+            return key;
+        }
+        else
+        {
+            StringBuilder buf = new StringBuilder();
+            buf.append(getExpressionEngine().getAttributeStart()).append(key);
+            if (getExpressionEngine().getAttributeEnd() != null)
+            {
+                buf.append(getExpressionEngine().getAttributeEnd());
+            }
+            return buf.toString();
+        }
+    }
+
+    /**
+     * Extracts the name of the attribute from the given attribute key. This
+     * method removes the attribute markers - if any - from the specified key.
+     *
+     * @param key the attribute key
+     * @return the name of the corresponding attribute
+     */
+    public String attributeName(String key)
+    {
+        return isAttributeKey(key) ? removeAttributeMarkers(key) : key;
+    }
+
+    /**
+     * Removes leading property delimiters from the specified key.
+     *
+     * @param key the key
+     * @return the key with removed leading property delimiters
+     */
+    public String trimLeft(String key)
+    {
+        if (key == null)
+        {
+            return StringUtils.EMPTY;
+        }
+        else
+        {
+            String result = key;
+            while (hasLeadingDelimiter(result))
+            {
+                result = result.substring(getExpressionEngine()
+                        .getPropertyDelimiter().length());
+            }
+            return result;
+        }
+    }
+
+    /**
+     * Removes trailing property delimiters from the specified key.
+     *
+     * @param key the key
+     * @return the key with removed trailing property delimiters
+     */
+    public String trimRight(String key)
+    {
+        if (key == null)
+        {
+            return StringUtils.EMPTY;
+        }
+        else
+        {
+            String result = key;
+            while (hasTrailingDelimiter(result))
+            {
+                result = result
+                        .substring(0, result.length()
+                                - getExpressionEngine().getPropertyDelimiter()
+                                        .length());
+            }
+            return result;
+        }
+    }
+
+    /**
+     * Removes delimiters at the beginning and the end of the specified key.
+     *
+     * @param key the key
+     * @return the key with removed property delimiters
+     */
+    public String trim(String key)
+    {
+        return trimRight(trimLeft(key));
+    }
+
+    /**
+     * Returns an iterator for iterating over the single components of this
+     * configuration key.
+     *
+     * @return an iterator for this key
+     */
+    public KeyIterator iterator()
+    {
+        return new KeyIterator();
+    }
+
+    /**
+     * Helper method that checks if the specified key ends with a property
+     * delimiter.
+     *
+     * @param key the key to check
+     * @return a flag if there is a trailing delimiter
+     */
+    private boolean hasTrailingDelimiter(String key)
+    {
+        return key.endsWith(getExpressionEngine().getPropertyDelimiter())
+                && (getExpressionEngine().getEscapedDelimiter() == null || !key
+                        .endsWith(getExpressionEngine().getEscapedDelimiter()));
+    }
+
+    /**
+     * Helper method that checks if the specified key starts with a property
+     * delimiter.
+     *
+     * @param key the key to check
+     * @return a flag if there is a leading delimiter
+     */
+    private boolean hasLeadingDelimiter(String key)
+    {
+        return key.startsWith(getExpressionEngine().getPropertyDelimiter())
+                && (getExpressionEngine().getEscapedDelimiter() == null || !key
+                        .startsWith(getExpressionEngine().getEscapedDelimiter()));
+    }
+
+    /**
+     * Helper method for removing attribute markers from a key.
+     *
+     * @param key the key
+     * @return the key with removed attribute markers
+     */
+    private String removeAttributeMarkers(String key)
+    {
+        return key
+                .substring(
+                        getExpressionEngine().getAttributeStart().length(),
+                        key.length()
+                                - ((getExpressionEngine().getAttributeEnd() != null) ? getExpressionEngine()
+                                        .getAttributeEnd().length()
+                                        : 0));
+    }
+
+    /**
+     * Unescapes the delimiters in the specified string.
+     *
+     * @param key the key to be unescaped
+     * @return the unescaped key
+     */
+    private String unescapeDelimiters(String key)
+    {
+        return (getExpressionEngine().getEscapedDelimiter() == null) ? key
+                : StringUtils.replace(key, getExpressionEngine()
+                        .getEscapedDelimiter(), getExpressionEngine()
+                        .getPropertyDelimiter());
+    }
+
+    /**
+     * Escapes the delimiters in the specified string.
+     *
+     * @param key the key to be escaped
+     * @return the escaped key
+     */
+    private String escapeDelimiters(String key)
+    {
+        return (getExpressionEngine().getEscapedDelimiter() == null || key
+                .indexOf(getExpressionEngine().getPropertyDelimiter()) < 0) ? key
+                : StringUtils.replace(key, getExpressionEngine()
+                        .getPropertyDelimiter(), getExpressionEngine()
+                        .getEscapedDelimiter());
+    }
+
+    /**
+     * A specialized iterator class for tokenizing a configuration key. This
+     * class implements the normal iterator interface. In addition it provides
+     * some specific methods for configuration keys.
+     */
+    public class KeyIterator implements Iterator<Object>, Cloneable
+    {
+        /** Stores the current key name. */
+        private String current;
+
+        /** Stores the start index of the actual token. */
+        private int startIndex;
+
+        /** Stores the end index of the actual token. */
+        private int endIndex;
+
+        /** Stores the index of the actual property if there is one. */
+        private int indexValue;
+
+        /** Stores a flag if the actual property has an index. */
+        private boolean hasIndex;
+
+        /** Stores a flag if the actual property is an attribute. */
+        private boolean attribute;
+
+        /**
+         * Returns the next key part of this configuration key. This is a short
+         * form of {@code nextKey(false)}.
+         *
+         * @return the next key part
+         */
+        public String nextKey()
+        {
+            return nextKey(false);
+        }
+
+        /**
+         * Returns the next key part of this configuration key. The boolean
+         * parameter indicates wheter a decorated key should be returned. This
+         * affects only attribute keys: if the parameter is <b>false</b>, the
+         * attribute markers are stripped from the key; if it is <b>true</b>,
+         * they remain.
+         *
+         * @param decorated a flag if the decorated key is to be returned
+         * @return the next key part
+         */
+        public String nextKey(boolean decorated)
+        {
+            if (!hasNext())
+            {
+                throw new NoSuchElementException("No more key parts!");
+            }
+
+            hasIndex = false;
+            indexValue = -1;
+            String key = findNextIndices();
+
+            current = key;
+            hasIndex = checkIndex(key);
+            attribute = checkAttribute(current);
+
+            return currentKey(decorated);
+        }
+
+        /**
+         * Checks if there is a next element.
+         *
+         * @return a flag if there is a next element
+         */
+        public boolean hasNext()
+        {
+            return endIndex < keyBuffer.length();
+        }
+
+        /**
+         * Returns the next object in the iteration.
+         *
+         * @return the next object
+         */
+        public Object next()
+        {
+            return nextKey();
+        }
+
+        /**
+         * Removes the current object in the iteration. This method is not
+         * supported by this iterator type, so an exception is thrown.
+         */
+        public void remove()
+        {
+            throw new UnsupportedOperationException("Remove not supported!");
+        }
+
+        /**
+         * Returns the current key of the iteration (without skipping to the
+         * next element). This is the same key the previous {@code next()}
+         * call had returned. (Short form of {@code currentKey(false)}.
+         *
+         * @return the current key
+         */
+        public String currentKey()
+        {
+            return currentKey(false);
+        }
+
+        /**
+         * Returns the current key of the iteration (without skipping to the
+         * next element). The boolean parameter indicates wheter a decorated key
+         * should be returned. This affects only attribute keys: if the
+         * parameter is <b>false</b>, the attribute markers are stripped from
+         * the key; if it is <b>true</b>, they remain.
+         *
+         * @param decorated a flag if the decorated key is to be returned
+         * @return the current key
+         */
+        public String currentKey(boolean decorated)
+        {
+            return (decorated && !isPropertyKey()) ? constructAttributeKey(current)
+                    : current;
+        }
+
+        /**
+         * Returns a flag if the current key is an attribute. This method can be
+         * called after {@code next()}.
+         *
+         * @return a flag if the current key is an attribute
+         */
+        public boolean isAttribute()
+        {
+            // if attribute emulation mode is active, the last part of a key is
+            // always an attribute key, too
+            return attribute || (isAttributeEmulatingMode() && !hasNext());
+        }
+
+        /**
+         * Returns a flag whether the current key refers to a property (i.e. is
+         * no special attribute key). Usually this method will return the
+         * opposite of {@code isAttribute()}, but if the delimiters for
+         * normal properties and attributes are set to the same string, it is
+         * possible that both methods return <b>true</b>.
+         *
+         * @return a flag if the current key is a property key
+         * @see #isAttribute()
+         */
+        public boolean isPropertyKey()
+        {
+            return !attribute;
+        }
+
+        /**
+         * Returns the index value of the current key. If the current key does
+         * not have an index, return value is -1. This method can be called
+         * after {@code next()}.
+         *
+         * @return the index value of the current key
+         */
+        public int getIndex()
+        {
+            return indexValue;
+        }
+
+        /**
+         * Returns a flag if the current key has an associated index. This
+         * method can be called after {@code next()}.
+         *
+         * @return a flag if the current key has an index
+         */
+        public boolean hasIndex()
+        {
+            return hasIndex;
+        }
+
+        /**
+         * Creates a clone of this object.
+         *
+         * @return a clone of this object
+         */
+        @Override
+        public Object clone()
+        {
+            try
+            {
+                return super.clone();
+            }
+            catch (CloneNotSupportedException cex)
+            {
+                // should not happen
+                return null;
+            }
+        }
+
+        /**
+         * Helper method for determining the next indices.
+         *
+         * @return the next key part
+         */
+        private String findNextIndices()
+        {
+            startIndex = endIndex;
+            // skip empty names
+            while (startIndex < length()
+                    && hasLeadingDelimiter(keyBuffer.substring(startIndex)))
+            {
+                startIndex += getExpressionEngine().getPropertyDelimiter()
+                        .length();
+            }
+
+            // Key ends with a delimiter?
+            if (startIndex >= length())
+            {
+                endIndex = length();
+                startIndex = endIndex - 1;
+                return keyBuffer.substring(startIndex, endIndex);
+            }
+            else
+            {
+                return nextKeyPart();
+            }
+        }
+
+        /**
+         * Helper method for extracting the next key part. Takes escaping of
+         * delimiter characters into account.
+         *
+         * @return the next key part
+         */
+        private String nextKeyPart()
+        {
+            int attrIdx = keyBuffer.toString().indexOf(
+                    getExpressionEngine().getAttributeStart(), startIndex);
+            if (attrIdx < 0 || attrIdx == startIndex)
+            {
+                attrIdx = length();
+            }
+
+            int delIdx = nextDelimiterPos(keyBuffer.toString(), startIndex,
+                    attrIdx);
+            if (delIdx < 0)
+            {
+                delIdx = attrIdx;
+            }
+
+            endIndex = Math.min(attrIdx, delIdx);
+            return unescapeDelimiters(keyBuffer.substring(startIndex, endIndex));
+        }
+
+        /**
+         * Searches the next unescaped delimiter from the given position.
+         *
+         * @param key the key
+         * @param pos the start position
+         * @param endPos the end position
+         * @return the position of the next delimiter or -1 if there is none
+         */
+        private int nextDelimiterPos(String key, int pos, int endPos)
+        {
+            int delimiterPos = pos;
+            boolean found = false;
+
+            do
+            {
+                delimiterPos = key.indexOf(getExpressionEngine()
+                        .getPropertyDelimiter(), delimiterPos);
+                if (delimiterPos < 0 || delimiterPos >= endPos)
+                {
+                    return -1;
+                }
+                int escapePos = escapedPosition(key, delimiterPos);
+                if (escapePos < 0)
+                {
+                    found = true;
+                }
+                else
+                {
+                    delimiterPos = escapePos;
+                }
+            }
+            while (!found);
+
+            return delimiterPos;
+        }
+
+        /**
+         * Checks if a delimiter at the specified position is escaped. If this
+         * is the case, the next valid search position will be returned.
+         * Otherwise the return value is -1.
+         *
+         * @param key the key to check
+         * @param pos the position where a delimiter was found
+         * @return information about escaped delimiters
+         */
+        private int escapedPosition(String key, int pos)
+        {
+            if (getExpressionEngine().getEscapedDelimiter() == null)
+            {
+                // nothing to escape
+                return -1;
+            }
+            int escapeOffset = escapeOffset();
+            if (escapeOffset < 0 || escapeOffset > pos)
+            {
+                // No escaping possible at this position
+                return -1;
+            }
+
+            int escapePos = key.indexOf(getExpressionEngine()
+                    .getEscapedDelimiter(), pos - escapeOffset);
+            if (escapePos <= pos && escapePos >= 0)
+            {
+                // The found delimiter is escaped. Next valid search position
+                // is behind the escaped delimiter.
+                return escapePos
+                        + getExpressionEngine().getEscapedDelimiter().length();
+            }
+            else
+            {
+                return -1;
+            }
+        }
+
+        /**
+         * Determines the relative offset of an escaped delimiter in relation to
+         * a delimiter. Depending on the used delimiter and escaped delimiter
+         * tokens the position where to search for an escaped delimiter is
+         * different. If, for instance, the dot character (".") is
+         * used as delimiter, and a doubled dot ("..") as escaped
+         * delimiter, the escaped delimiter starts at the same position as the
+         * delimiter. If the token "\." was used, it would start one
+         * character before the delimiter because the delimiter character
+         * "." is the second character in the escaped delimiter
+         * string. This relation will be determined by this method. For this to
+         * work the delimiter string must be contained in the escaped delimiter
+         * string.
+         *
+         * @return the relative offset of the escaped delimiter in relation to a
+         * delimiter
+         */
+        private int escapeOffset()
+        {
+            return getExpressionEngine().getEscapedDelimiter().indexOf(
+                    getExpressionEngine().getPropertyDelimiter());
+        }
+
+        /**
+         * Helper method for checking if the passed key is an attribute. If this
+         * is the case, the internal fields will be set.
+         *
+         * @param key the key to be checked
+         * @return a flag if the key is an attribute
+         */
+        private boolean checkAttribute(String key)
+        {
+            if (isAttributeKey(key))
+            {
+                current = removeAttributeMarkers(key);
+                return true;
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        /**
+         * Helper method for checking if the passed key contains an index. If
+         * this is the case, internal fields will be set.
+         *
+         * @param key the key to be checked
+         * @return a flag if an index is defined
+         */
+        private boolean checkIndex(String key)
+        {
+            boolean result = false;
+
+            try
+            {
+                int idx = key.lastIndexOf(getExpressionEngine().getIndexStart());
+                if (idx > 0)
+                {
+                    int endidx = key.indexOf(getExpressionEngine().getIndexEnd(),
+                            idx);
+
+                    if (endidx > idx + 1)
+                    {
+                        indexValue = Integer.parseInt(key.substring(idx + 1, endidx));
+                        current = key.substring(0, idx);
+                        result = true;
+                    }
+                }
+            }
+            catch (NumberFormatException nfe)
+            {
+                result = false;
+            }
+
+            return result;
+        }
+
+        /**
+         * Returns a flag whether attributes are marked the same way as normal
+         * property keys. We call this the "attribute emulating mode".
+         * When navigating through node hierarchies it might be convenient to
+         * treat attributes the same way than other child nodes, so an
+         * expression engine supports to set the attribute markers to the same
+         * value than the property delimiter. If this is the case, some special
+         * checks have to be performed.
+         *
+         * @return a flag if attributes and normal property keys are treated the
+         * same way
+         */
+        private boolean isAttributeEmulatingMode()
+        {
+            return getExpressionEngine().getAttributeEnd() == null
+                    && StringUtils.equals(getExpressionEngine()
+                            .getPropertyDelimiter(), getExpressionEngine()
+                            .getAttributeStart());
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/tree/DefaultConfigurationNode.java b/src/main/java/org/apache/commons/configuration/tree/DefaultConfigurationNode.java
new file mode 100644
index 0000000..cbd0b24
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/tree/DefaultConfigurationNode.java
@@ -0,0 +1,717 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.configuration.ConfigurationRuntimeException;
+
+/**
+ * <p>
+ * A default implementation of the {@code ConfigurationNode} interface.
+ * </p>
+ *
+ * @since 1.3
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: DefaultConfigurationNode.java 1301991 2012-03-17 20:18:02Z sebb $
+ */
+public class DefaultConfigurationNode implements ConfigurationNode, Cloneable
+{
+    /** Stores the children of this node. */
+    private SubNodes children;
+
+    /** Stores the attributes of this node. */
+    private SubNodes attributes;
+
+    /** Stores a reference to this node's parent. */
+    private ConfigurationNode parent;
+
+    /** Stores the value of this node. */
+    private Object value;
+
+    /** Stores the reference. */
+    private Object reference;
+
+    /** Stores the name of this node. */
+    private String name;
+
+    /** Stores a flag if this is an attribute. */
+    private boolean attribute;
+
+    /**
+     * Creates a new uninitialized instance of {@code DefaultConfigurationNode}.
+     */
+    public DefaultConfigurationNode()
+    {
+        this(null);
+    }
+
+    /**
+     * Creates a new instance of {@code DefaultConfigurationNode} and
+     * initializes it with the node name.
+     *
+     * @param name the name of this node
+     */
+    public DefaultConfigurationNode(String name)
+    {
+        this(name, null);
+    }
+
+    /**
+     * Creates a new instance of {@code DefaultConfigurationNode} and
+     * initializes it with the name and a value.
+     *
+     * @param name the node's name
+     * @param value the node's value
+     */
+    public DefaultConfigurationNode(String name, Object value)
+    {
+        setName(name);
+        setValue(value);
+        initSubNodes();
+    }
+
+    /**
+     * Returns the name of this node.
+     *
+     * @return the name of this node
+     */
+    public String getName()
+    {
+        return name;
+    }
+
+    /**
+     * Sets the name of this node.
+     *
+     * @param name the new name
+     */
+    public void setName(String name)
+    {
+        checkState();
+        this.name = name;
+    }
+
+    /**
+     * Returns the value of this node.
+     *
+     * @return the value of this node
+     */
+    public Object getValue()
+    {
+        return value;
+    }
+
+    /**
+     * Sets the value of this node.
+     *
+     * @param val the value of this node
+     */
+    public void setValue(Object val)
+    {
+        value = val;
+    }
+
+    /**
+     * Returns the reference.
+     *
+     * @return the reference
+     */
+    public Object getReference()
+    {
+        return reference;
+    }
+
+    /**
+     * Sets the reference.
+     *
+     * @param reference the reference object
+     */
+    public void setReference(Object reference)
+    {
+        this.reference = reference;
+    }
+
+    /**
+     * Returns a reference to this node's parent.
+     *
+     * @return the parent node or <b>null </b> if this is the root
+     */
+    public ConfigurationNode getParentNode()
+    {
+        return parent;
+    }
+
+    /**
+     * Sets the parent of this node.
+     *
+     * @param parent the parent of this node
+     */
+    public void setParentNode(ConfigurationNode parent)
+    {
+        this.parent = parent;
+    }
+
+    /**
+     * Adds a new child to this node.
+     *
+     * @param child the new child
+     */
+    public void addChild(ConfigurationNode child)
+    {
+        children.addNode(child);
+        child.setAttribute(false);
+        child.setParentNode(this);
+    }
+
+    /**
+     * Returns a list with all children of this node.
+     *
+     * @return a list with all child nodes
+     */
+    public List<ConfigurationNode> getChildren()
+    {
+        return children.getSubNodes();
+    }
+
+    /**
+     * Returns the number of all children of this node.
+     *
+     * @return the number of all children
+     */
+    public int getChildrenCount()
+    {
+        return children.getSubNodes().size();
+    }
+
+    /**
+     * Returns a list of all children with the given name.
+     *
+     * @param name the name; can be <b>null </b>, then all children are returned
+     * @return a list of all children with the given name
+     */
+    public List<ConfigurationNode> getChildren(String name)
+    {
+        return children.getSubNodes(name);
+    }
+
+    /**
+     * Returns the number of children with the given name.
+     *
+     * @param name the name; can be <b>null </b>, then the number of all
+     * children is returned
+     * @return the number of child nodes with this name
+     */
+    public int getChildrenCount(String name)
+    {
+        return children.getSubNodes(name).size();
+    }
+
+    /**
+     * Returns the child node with the given index.
+     *
+     * @param index the index (0-based)
+     * @return the child with this index
+     */
+    public ConfigurationNode getChild(int index)
+    {
+        return children.getNode(index);
+    }
+
+    /**
+     * Removes the specified child node from this node.
+     *
+     * @param child the node to be removed
+     * @return a flag if a node was removed
+     */
+    public boolean removeChild(ConfigurationNode child)
+    {
+        return children.removeNode(child);
+    }
+
+    /**
+     * Removes all children with the given name.
+     *
+     * @param childName the name of the children to be removed
+     * @return a flag if at least one child node was removed
+     */
+    public boolean removeChild(String childName)
+    {
+        return children.removeNodes(childName);
+    }
+
+    /**
+     * Removes all child nodes of this node.
+     */
+    public void removeChildren()
+    {
+        children.clear();
+    }
+
+    /**
+     * Checks if this node is an attribute node.
+     *
+     * @return a flag if this is an attribute node
+     */
+    public boolean isAttribute()
+    {
+        return attribute;
+    }
+
+    /**
+     * Sets the attribute flag. Note: this method can only be called if the node
+     * is not already part of a node hierarchy.
+     *
+     * @param f the attribute flag
+     */
+    public void setAttribute(boolean f)
+    {
+        checkState();
+        attribute = f;
+    }
+
+    /**
+     * Adds the specified attribute to this node.
+     *
+     * @param attr the attribute to be added
+     */
+    public void addAttribute(ConfigurationNode attr)
+    {
+        attributes.addNode(attr);
+        attr.setAttribute(true);
+        attr.setParentNode(this);
+    }
+
+    /**
+     * Returns a list with the attributes of this node. This list contains
+     * {@code DefaultConfigurationNode} objects, too.
+     *
+     * @return the attribute list, never <b>null </b>
+     */
+    public List<ConfigurationNode> getAttributes()
+    {
+        return attributes.getSubNodes();
+    }
+
+    /**
+     * Returns the number of attributes contained in this node.
+     *
+     * @return the number of attributes
+     */
+    public int getAttributeCount()
+    {
+        return attributes.getSubNodes().size();
+    }
+
+    /**
+     * Returns a list with all attributes of this node with the given name.
+     *
+     * @param name the attribute's name
+     * @return all attributes with this name
+     */
+    public List<ConfigurationNode> getAttributes(String name)
+    {
+        return attributes.getSubNodes(name);
+    }
+
+    /**
+     * Returns the number of attributes of this node with the given name.
+     *
+     * @param name the name
+     * @return the number of attributes with this name
+     */
+    public int getAttributeCount(String name)
+    {
+        return getAttributes(name).size();
+    }
+
+    /**
+     * Removes the specified attribute.
+     *
+     * @param node the attribute node to be removed
+     * @return a flag if the attribute could be removed
+     */
+    public boolean removeAttribute(ConfigurationNode node)
+    {
+        return attributes.removeNode(node);
+    }
+
+    /**
+     * Removes all attributes with the specified name.
+     *
+     * @param name the name
+     * @return a flag if at least one attribute was removed
+     */
+    public boolean removeAttribute(String name)
+    {
+        return attributes.removeNodes(name);
+    }
+
+    /**
+     * Returns the attribute with the given index.
+     *
+     * @param index the index (0-based)
+     * @return the attribute with this index
+     */
+    public ConfigurationNode getAttribute(int index)
+    {
+        return attributes.getNode(index);
+    }
+
+    /**
+     * Removes all attributes of this node.
+     */
+    public void removeAttributes()
+    {
+        attributes.clear();
+    }
+
+    /**
+     * Returns a flag if this node is defined. This means that the node contains
+     * some data.
+     *
+     * @return a flag whether this node is defined
+     */
+    public boolean isDefined()
+    {
+        return getValue() != null || getChildrenCount() > 0
+                || getAttributeCount() > 0;
+    }
+
+    /**
+     * Visits this node and all its sub nodes.
+     *
+     * @param visitor the visitor
+     */
+    public void visit(ConfigurationNodeVisitor visitor)
+    {
+        if (visitor == null)
+        {
+            throw new IllegalArgumentException("Visitor must not be null!");
+        }
+
+        if (!visitor.terminate())
+        {
+            visitor.visitBeforeChildren(this);
+            children.visit(visitor);
+            attributes.visit(visitor);
+            visitor.visitAfterChildren(this);
+        }
+    }
+
+    /**
+     * Creates a copy of this object. This is not a deep copy, the children are
+     * not cloned.
+     *
+     * @return a copy of this object
+     */
+    @Override
+    public Object clone()
+    {
+        try
+        {
+            DefaultConfigurationNode copy = (DefaultConfigurationNode) super
+                    .clone();
+            copy.initSubNodes();
+            return copy;
+        }
+        catch (CloneNotSupportedException cex)
+        {
+            // should not happen
+            throw new ConfigurationRuntimeException("Cannot clone " + getClass());
+        }
+    }
+
+    /**
+     * Checks if a modification of this node is allowed. Some properties of a
+     * node must not be changed when the node has a parent. This method checks
+     * this and throws a runtime exception if necessary.
+     */
+    protected void checkState()
+    {
+        if (getParentNode() != null)
+        {
+            throw new IllegalStateException(
+                    "Node cannot be modified when added to a parent!");
+        }
+    }
+
+    /**
+     * Creates a {@code SubNodes} instance that is used for storing
+     * either this node's children or attributes.
+     *
+     * @param attributes <b>true</b> if the returned instance is used for
+     * storing attributes, <b>false</b> for storing child nodes
+     * @return the {@code SubNodes} object to use
+     */
+    protected SubNodes createSubNodes(boolean attributes)
+    {
+        return new SubNodes();
+    }
+
+    /**
+     * Deals with the reference when a node is removed. This method is called
+     * for each removed child node or attribute. It can be overloaded in sub
+     * classes, for which the reference has a concrete meaning and remove
+     * operations need some update actions. This default implementation is
+     * empty.
+     */
+    protected void removeReference()
+    {
+    }
+
+    /**
+     * Helper method for initializing the sub nodes objects.
+     */
+    private void initSubNodes()
+    {
+        children = createSubNodes(false);
+        attributes = createSubNodes(true);
+    }
+
+    /**
+     * An internally used helper class for managing a collection of sub nodes.
+     */
+    protected static class SubNodes
+    {
+        /** Stores a list for the sub nodes. */
+        private List<ConfigurationNode> nodes;
+
+        /** Stores a map for accessing subnodes by name. */
+        private Map<String, List<ConfigurationNode>> namedNodes;
+
+        /**
+         * Adds a new sub node.
+         *
+         * @param node the node to add
+         */
+        public void addNode(ConfigurationNode node)
+        {
+            if (node == null || node.getName() == null)
+            {
+                throw new IllegalArgumentException(
+                        "Node to add must have a defined name!");
+            }
+            node.setParentNode(null);  // reset, will later be set
+
+            if (nodes == null)
+            {
+                nodes = new ArrayList<ConfigurationNode>();
+                namedNodes = new HashMap<String, List<ConfigurationNode>>();
+            }
+
+            nodes.add(node);
+            List<ConfigurationNode> lst = namedNodes.get(node.getName());
+            if (lst == null)
+            {
+                lst = new LinkedList<ConfigurationNode>();
+                namedNodes.put(node.getName(), lst);
+            }
+            lst.add(node);
+        }
+
+        /**
+         * Removes a sub node.
+         *
+         * @param node the node to remove
+         * @return a flag if the node could be removed
+         */
+        public boolean removeNode(ConfigurationNode node)
+        {
+            if (nodes != null && node != null && nodes.contains(node))
+            {
+                detachNode(node);
+                nodes.remove(node);
+
+                List<ConfigurationNode> lst = namedNodes.get(node.getName());
+                if (lst != null)
+                {
+                    lst.remove(node);
+                    if (lst.isEmpty())
+                    {
+                        namedNodes.remove(node.getName());
+                    }
+                }
+                return true;
+            }
+
+            else
+            {
+                return false;
+            }
+        }
+
+        /**
+         * Removes all sub nodes with the given name.
+         *
+         * @param name the name
+         * @return a flag if at least on sub node was removed
+         */
+        public boolean removeNodes(String name)
+        {
+            if (nodes != null && name != null)
+            {
+                List<ConfigurationNode> lst = namedNodes.remove(name);
+                if (lst != null)
+                {
+                    detachNodes(lst);
+                    nodes.removeAll(lst);
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Removes all sub nodes.
+         */
+        public void clear()
+        {
+            if (nodes != null)
+            {
+                detachNodes(nodes);
+                nodes = null;
+                namedNodes = null;
+            }
+        }
+
+        /**
+         * Returns the node with the given index. If this index cannot be found,
+         * an {@code IndexOutOfBoundException} exception will be thrown.
+         *
+         * @param index the index (0-based)
+         * @return the sub node at the specified index
+         */
+        public ConfigurationNode getNode(int index)
+        {
+            if (nodes == null)
+            {
+                throw new IndexOutOfBoundsException("No sub nodes available!");
+            }
+            return nodes.get(index);
+        }
+
+        /**
+         * Returns a list with all stored sub nodes. The return value is never
+         * <b>null</b>.
+         *
+         * @return a list with the sub nodes
+         */
+        public List<ConfigurationNode> getSubNodes()
+        {
+            if (nodes == null)
+            {
+                return Collections.emptyList();
+            }
+            else
+            {
+                return Collections.unmodifiableList(nodes);
+            }
+        }
+
+        /**
+         * Returns a list of the sub nodes with the given name. The return value
+         * is never <b>null</b>.
+         *
+         * @param name the name; if <b>null</b> is passed, all sub nodes will
+         * be returned
+         * @return all sub nodes with this name
+         */
+        public List<ConfigurationNode> getSubNodes(String name)
+        {
+            if (name == null)
+            {
+                return getSubNodes();
+            }
+
+            List<ConfigurationNode> result;
+            if (nodes == null)
+            {
+                result = null;
+            }
+            else
+            {
+                result = namedNodes.get(name);
+            }
+
+            if (result == null)
+            {
+                return Collections.emptyList();
+            }
+            else
+            {
+                return Collections.unmodifiableList(result);
+            }
+        }
+
+        /**
+         * Let the passed in visitor visit all sub nodes.
+         *
+         * @param visitor the visitor
+         */
+        public void visit(ConfigurationNodeVisitor visitor)
+        {
+            if (nodes != null)
+            {
+                for (Iterator<ConfigurationNode> it = nodes.iterator(); it.hasNext()
+                        && !visitor.terminate();)
+                {
+                    it.next().visit(visitor);
+                }
+            }
+        }
+
+        /**
+         * This method is called whenever a sub node is removed from this
+         * object. It ensures that the removed node's parent is reset and its
+         * {@code removeReference()} method gets called.
+         *
+         * @param subNode the node to be removed
+         */
+        protected void detachNode(ConfigurationNode subNode)
+        {
+            subNode.setParentNode(null);
+            if (subNode instanceof DefaultConfigurationNode)
+            {
+                ((DefaultConfigurationNode) subNode).removeReference();
+            }
+        }
+
+        /**
+         * Detaches a list of sub nodes. This method calls
+         * {@code detachNode()} for each node contained in the list.
+         *
+         * @param subNodes the list with nodes to be detached
+         */
+        protected void detachNodes(Collection<? extends ConfigurationNode> subNodes)
+        {
+            for (ConfigurationNode nd : subNodes)
+            {
+                detachNode(nd);
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/tree/DefaultExpressionEngine.java b/src/main/java/org/apache/commons/configuration/tree/DefaultExpressionEngine.java
new file mode 100644
index 0000000..e4a8a8c
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/tree/DefaultExpressionEngine.java
@@ -0,0 +1,545 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * <p>
+ * A default implementation of the {@code ExpressionEngine} interface
+ * providing the "native"e; expression language for hierarchical
+ * configurations.
+ * </p>
+ * <p>
+ * This class implements a rather simple expression language for navigating
+ * through a hierarchy of configuration nodes. It supports the following
+ * operations:
+ * </p>
+ * <p>
+ * <ul>
+ * <li>Navigating from a node to one of its children using the child node
+ * delimiter, which is by the default a dot (".").</li>
+ * <li>Navigating from a node to one of its attributes using the attribute node
+ * delimiter, which by default follows the XPATH like syntax
+ * <code>[@<attributeName>]</code>.</li>
+ * <li>If there are multiple child or attribute nodes with the same name, a
+ * specific node can be selected using a numerical index. By default indices are
+ * written in parenthesis.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * As an example consider the following XML document:
+ * </p>
+ *
+ * <pre>
+ *  <database>
+ *    <tables>
+ *      <table type="system">
+ *        <name>users</name>
+ *        <fields>
+ *          <field>
+ *            <name>lid</name>
+ *            <type>long</name>
+ *          </field>
+ *          <field>
+ *            <name>usrName</name>
+ *            <type>java.lang.String</type>
+ *          </field>
+ *         ...
+ *        </fields>
+ *      </table>
+ *      <table>
+ *        <name>documents</name>
+ *        <fields>
+ *          <field>
+ *            <name>docid</name>
+ *            <type>long</type>
+ *          </field>
+ *          ...
+ *        </fields>
+ *      </table>
+ *      ...
+ *    </tables>
+ *  </database>
+ * </pre>
+ *
+ * </p>
+ * <p>
+ * If this document is parsed and stored in a hierarchical configuration object,
+ * for instance the key {@code tables.table(0).name} can be used to find
+ * out the name of the first table. In opposite {@code tables.table.name}
+ * would return a collection with the names of all available tables. Similarly
+ * the key {@code tables.table(1).fields.field.name} returns a collection
+ * with the names of all fields of the second table. If another index is added
+ * after the {@code field} element, a single field can be accessed:
+ * {@code tables.table(1).fields.field(0).name}. The key
+ * {@code tables.table(0)[@type]} would select the type attribute of the
+ * first table.
+ * </p>
+ * <p>
+ * This example works with the default values for delimiters and index markers.
+ * It is also possible to set custom values for these properties so that you can
+ * adapt a {@code DefaultExpressionEngine} to your personal needs.
+ * </p>
+ *
+ * @since 1.3
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: DefaultExpressionEngine.java 1301991 2012-03-17 20:18:02Z sebb $
+ */
+public class DefaultExpressionEngine implements ExpressionEngine
+{
+    /** Constant for the default property delimiter. */
+    public static final String DEFAULT_PROPERTY_DELIMITER = ".";
+
+    /** Constant for the default escaped property delimiter. */
+    public static final String DEFAULT_ESCAPED_DELIMITER = DEFAULT_PROPERTY_DELIMITER
+            + DEFAULT_PROPERTY_DELIMITER;
+
+    /** Constant for the default attribute start marker. */
+    public static final String DEFAULT_ATTRIBUTE_START = "[@";
+
+    /** Constant for the default attribute end marker. */
+    public static final String DEFAULT_ATTRIBUTE_END = "]";
+
+    /** Constant for the default index start marker. */
+    public static final String DEFAULT_INDEX_START = "(";
+
+    /** Constant for the default index end marker. */
+    public static final String DEFAULT_INDEX_END = ")";
+
+    /** Stores the property delimiter. */
+    private String propertyDelimiter = DEFAULT_PROPERTY_DELIMITER;
+
+    /** Stores the escaped property delimiter. */
+    private String escapedDelimiter = DEFAULT_ESCAPED_DELIMITER;
+
+    /** Stores the attribute start marker. */
+    private String attributeStart = DEFAULT_ATTRIBUTE_START;
+
+    /** Stores the attribute end marker. */
+    private String attributeEnd = DEFAULT_ATTRIBUTE_END;
+
+    /** Stores the index start marker. */
+    private String indexStart = DEFAULT_INDEX_START;
+
+    /** stores the index end marker. */
+    private String indexEnd = DEFAULT_INDEX_END;
+
+    /**
+     * Sets the attribute end marker.
+     *
+     * @return the attribute end marker
+     */
+    public String getAttributeEnd()
+    {
+        return attributeEnd;
+    }
+
+    /**
+     * Sets the attribute end marker.
+     *
+     * @param attributeEnd the attribute end marker; can be <b>null</b> if no
+     * end marker is needed
+     */
+    public void setAttributeEnd(String attributeEnd)
+    {
+        this.attributeEnd = attributeEnd;
+    }
+
+    /**
+     * Returns the attribute start marker.
+     *
+     * @return the attribute start marker
+     */
+    public String getAttributeStart()
+    {
+        return attributeStart;
+    }
+
+    /**
+     * Sets the attribute start marker. Attribute start and end marker are used
+     * together to detect attributes in a property key.
+     *
+     * @param attributeStart the attribute start marker
+     */
+    public void setAttributeStart(String attributeStart)
+    {
+        this.attributeStart = attributeStart;
+    }
+
+    /**
+     * Returns the escaped property delimiter string.
+     *
+     * @return the escaped property delimiter
+     */
+    public String getEscapedDelimiter()
+    {
+        return escapedDelimiter;
+    }
+
+    /**
+     * Sets the escaped property delimiter string. With this string a delimiter
+     * that belongs to the key of a property can be escaped. If for instance
+     * "." is used as property delimiter, you can set the escaped
+     * delimiter to "\." and can then escape the delimiter with a back
+     * slash.
+     *
+     * @param escapedDelimiter the escaped delimiter string
+     */
+    public void setEscapedDelimiter(String escapedDelimiter)
+    {
+        this.escapedDelimiter = escapedDelimiter;
+    }
+
+    /**
+     * Returns the index end marker.
+     *
+     * @return the index end marker
+     */
+    public String getIndexEnd()
+    {
+        return indexEnd;
+    }
+
+    /**
+     * Sets the index end marker.
+     *
+     * @param indexEnd the index end marker
+     */
+    public void setIndexEnd(String indexEnd)
+    {
+        this.indexEnd = indexEnd;
+    }
+
+    /**
+     * Returns the index start marker.
+     *
+     * @return the index start marker
+     */
+    public String getIndexStart()
+    {
+        return indexStart;
+    }
+
+    /**
+     * Sets the index start marker. Index start and end marker are used together
+     * to detect indices in a property key.
+     *
+     * @param indexStart the index start marker
+     */
+    public void setIndexStart(String indexStart)
+    {
+        this.indexStart = indexStart;
+    }
+
+    /**
+     * Returns the property delimiter.
+     *
+     * @return the property delimiter
+     */
+    public String getPropertyDelimiter()
+    {
+        return propertyDelimiter;
+    }
+
+    /**
+     * Sets the property delimiter. This string is used to split the parts of a
+     * property key.
+     *
+     * @param propertyDelimiter the property delimiter
+     */
+    public void setPropertyDelimiter(String propertyDelimiter)
+    {
+        this.propertyDelimiter = propertyDelimiter;
+    }
+
+    /**
+     * Evaluates the given key and returns all matching nodes. This method
+     * supports the syntax as described in the class comment.
+     *
+     * @param root the root node
+     * @param key the key
+     * @return a list with the matching nodes
+     */
+    public List<ConfigurationNode> query(ConfigurationNode root, String key)
+    {
+        List<ConfigurationNode> nodes = new LinkedList<ConfigurationNode>();
+        findNodesForKey(new DefaultConfigurationKey(this, key).iterator(),
+                root, nodes);
+        return nodes;
+    }
+
+    /**
+     * Determines the key of the passed in node. This implementation takes the
+     * given parent key, adds a property delimiter, and then adds the node's
+     * name. (For attribute nodes the attribute delimiters are used instead.)
+     * The name of the root node is a blanc string. Note that no indices will be
+     * returned.
+     *
+     * @param node the node whose key is to be determined
+     * @param parentKey the key of this node's parent
+     * @return the key for the given node
+     */
+    public String nodeKey(ConfigurationNode node, String parentKey)
+    {
+        if (parentKey == null)
+        {
+            // this is the root node
+            return StringUtils.EMPTY;
+        }
+
+        else
+        {
+            DefaultConfigurationKey key = new DefaultConfigurationKey(this,
+                    parentKey);
+            if (node.isAttribute())
+            {
+                key.appendAttribute(node.getName());
+            }
+            else
+            {
+                key.append(node.getName(), true);
+            }
+            return key.toString();
+        }
+    }
+
+    /**
+     * <p>
+     * Prepares Adding the property with the specified key.
+     * </p>
+     * <p>
+     * To be able to deal with the structure supported by hierarchical
+     * configuration implementations the passed in key is of importance,
+     * especially the indices it might contain. The following example should
+     * clarify this: Suppose the actual node structure looks like the
+     * following:
+     * </p>
+     * <p>
+     * <pre>
+     *  tables
+     *     +-- table
+     *             +-- name = user
+     *             +-- fields
+     *                     +-- field
+     *                             +-- name = uid
+     *                     +-- field
+     *                             +-- name = firstName
+     *                     ...
+     *     +-- table
+     *             +-- name = documents
+     *             +-- fields
+     *                    ...
+     * </pre>
+     * </p>
+     * <p>
+     * In this example a database structure is defined, e.g. all fields of the
+     * first table could be accessed using the key
+     * {@code tables.table(0).fields.field.name}. If now properties are
+     * to be added, it must be exactly specified at which position in the
+     * hierarchy the new property is to be inserted. So to add a new field name
+     * to a table it is not enough to say just
+     * </p>
+     * <p>
+     * <pre>
+     * config.addProperty("tables.table.fields.field.name", "newField");
+     * </pre>
+     * </p>
+     * <p>
+     * The statement given above contains some ambiguity. For instance it is not
+     * clear, to which table the new field should be added. If this method finds
+     * such an ambiguity, it is resolved by following the last valid path. Here
+     * this would be the last table. The same is true for the {@code field};
+     * because there are multiple fields and no explicit index is provided, a
+     * new {@code name} property would be added to the last field - which
+     * is probably not what was desired.
+     * </p>
+     * <p>
+     * To make things clear explicit indices should be provided whenever
+     * possible. In the example above the exact table could be specified by
+     * providing an index for the {@code table} element as in
+     * {@code tables.table(1).fields}. By specifying an index it can
+     * also be expressed that at a given position in the configuration tree a
+     * new branch should be added. In the example above we did not want to add
+     * an additional {@code name} element to the last field of the table,
+     * but we want a complete new {@code field} element. This can be
+     * achieved by specifying an invalid index (like -1) after the element where
+     * a new branch should be created. Given this our example would run:
+     * </p>
+     * <p>
+     * <pre>
+     * config.addProperty("tables.table(1).fields.field(-1).name", "newField");
+     * </pre>
+     * </p>
+     * <p>
+     * With this notation it is possible to add new branches everywhere. We
+     * could for instance create a new {@code table} element by
+     * specifying
+     * </p>
+     * <p>
+     * <pre>
+     * config.addProperty("tables.table(-1).fields.field.name", "newField2");
+     * </pre>
+     * </p>
+     * <p>
+     * (Note that because after the {@code table} element a new branch is
+     * created indices in following elements are not relevant; the branch is new
+     * so there cannot be any ambiguities.)
+     * </p>
+     *
+     * @param root the root node of the nodes hierarchy
+     * @param key the key of the new property
+     * @return a data object with information needed for the add operation
+     */
+    public NodeAddData prepareAdd(ConfigurationNode root, String key)
+    {
+        DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
+                this, key).iterator();
+        if (!it.hasNext())
+        {
+            throw new IllegalArgumentException(
+                    "Key for add operation must be defined!");
+        }
+
+        NodeAddData result = new NodeAddData();
+        result.setParent(findLastPathNode(it, root));
+
+        while (it.hasNext())
+        {
+            if (!it.isPropertyKey())
+            {
+                throw new IllegalArgumentException(
+                        "Invalid key for add operation: " + key
+                                + " (Attribute key in the middle.)");
+            }
+            result.addPathNode(it.currentKey());
+            it.next();
+        }
+
+        result.setNewNodeName(it.currentKey());
+        result.setAttribute(!it.isPropertyKey());
+        return result;
+    }
+
+    /**
+     * Recursive helper method for evaluating a key. This method processes all
+     * facets of a configuration key, traverses the tree of properties and
+     * fetches the the nodes of all matching properties.
+     *
+     * @param keyPart the configuration key iterator
+     * @param node the actual node
+     * @param nodes here the found nodes are stored
+     */
+    protected void findNodesForKey(DefaultConfigurationKey.KeyIterator keyPart,
+            ConfigurationNode node, Collection<ConfigurationNode> nodes)
+    {
+        if (!keyPart.hasNext())
+        {
+            nodes.add(node);
+        }
+
+        else
+        {
+            String key = keyPart.nextKey(false);
+            if (keyPart.isPropertyKey())
+            {
+                processSubNodes(keyPart, node.getChildren(key), nodes);
+            }
+            if (keyPart.isAttribute())
+            {
+                processSubNodes(keyPart, node.getAttributes(key), nodes);
+            }
+        }
+    }
+
+    /**
+     * Finds the last existing node for an add operation. This method traverses
+     * the configuration node tree along the specified key. The last existing
+     * node on this path is returned.
+     *
+     * @param keyIt the key iterator
+     * @param node the actual node
+     * @return the last existing node on the given path
+     */
+    protected ConfigurationNode findLastPathNode(
+            DefaultConfigurationKey.KeyIterator keyIt, ConfigurationNode node)
+    {
+        String keyPart = keyIt.nextKey(false);
+
+        if (keyIt.hasNext())
+        {
+            if (!keyIt.isPropertyKey())
+            {
+                // Attribute keys can only appear as last elements of the path
+                throw new IllegalArgumentException(
+                        "Invalid path for add operation: "
+                                + "Attribute key in the middle!");
+            }
+            int idx = keyIt.hasIndex() ? keyIt.getIndex() : node
+                    .getChildrenCount(keyPart) - 1;
+            if (idx < 0 || idx >= node.getChildrenCount(keyPart))
+            {
+                return node;
+            }
+            else
+            {
+                return findLastPathNode(keyIt, node.getChildren(keyPart).get(idx));
+            }
+        }
+
+        else
+        {
+            return node;
+        }
+    }
+
+    /**
+     * Called by {@code findNodesForKey()} to process the sub nodes of
+     * the current node depending on the type of the current key part (children,
+     * attributes, or both).
+     *
+     * @param keyPart the key part
+     * @param subNodes a list with the sub nodes to process
+     * @param nodes the target collection
+     */
+    private void processSubNodes(DefaultConfigurationKey.KeyIterator keyPart,
+            List<ConfigurationNode> subNodes, Collection<ConfigurationNode> nodes)
+    {
+        if (keyPart.hasIndex())
+        {
+            if (keyPart.getIndex() >= 0 && keyPart.getIndex() < subNodes.size())
+            {
+                findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart
+                        .clone(), subNodes.get(keyPart.getIndex()), nodes);
+            }
+        }
+        else
+        {
+            for (ConfigurationNode node : subNodes)
+            {
+                findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart
+                        .clone(), node, nodes);
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/tree/ExpressionEngine.java b/src/main/java/org/apache/commons/configuration/tree/ExpressionEngine.java
new file mode 100644
index 0000000..7ab266e
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/tree/ExpressionEngine.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import java.util.List;
+
+/**
+ * <p>
+ * Definition of an interface for evaluating keys for hierarchical
+ * configurations.
+ * </p>
+ * <p>
+ * An <em>expression engine</em> knows how to map a key for a configuration's
+ * property to a single or a set of configuration nodes. Thus it defines the way
+ * how properties are addressed in this configuration. Methods of a
+ * configuration that have to handle property key (e.g.
+ * {@code getProperty()} or {@code addProperty()} do not interpret
+ * the passed in keys on their own, but delegate this task to an associated
+ * expression engine. This expression engine will then find out, which
+ * configuration nodes are addressed by the key.
+ * </p>
+ * <p>
+ * Separating the task of evaluating property keys from the configuration object
+ * has the advantage that many different expression languages (i.e. ways for
+ * querying or setting properties) can be supported. Just set a suitable
+ * implementation of this interface as the configuration's expression engine,
+ * and you can use the syntax provided by this implementation.
+ * </p>
+ *
+ * @since 1.3
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: ExpressionEngine.java 1206474 2011-11-26 16:14:09Z oheger $
+ */
+public interface ExpressionEngine
+{
+    /**
+     * Finds the node(s) that is (are) matched by the specified key. This is the
+     * main method for interpreting property keys. An implementation must
+     * traverse the given root node and its children to find all nodes that are
+     * matched by the given key. If the key is not correct in the syntax
+     * provided by that implementation, it is free to throw a (runtime)
+     * exception indicating this error condition.
+     *
+     * @param root the root node of a hierarchy of configuration nodes
+     * @param key the key to be evaluated
+     * @return a list with the nodes that are matched by the key (should never
+     * be <b>null</b>)
+     */
+    List<ConfigurationNode> query(ConfigurationNode root, String key);
+
+    /**
+     * Returns the key for the specified node in the expression language
+     * supported by an implementation. This method is called whenever a property
+     * key for a node has to be constructed, e.g. by the
+     * {@link org.apache.commons.configuration.Configuration#getKeys() getKeys()}
+     * method.
+     *
+     * @param node the node, for which the key must be constructed
+     * @param parentKey the key of this node's parent (can be <b>null</b> for
+     * the root node)
+     * @return this node's key
+     */
+    String nodeKey(ConfigurationNode node, String parentKey);
+
+    /**
+     * Returns information needed for an add operation. This method gets called
+     * when new properties are to be added to a configuration. An implementation
+     * has to interpret the specified key, find the parent node for the new
+     * elements, and provide all information about new nodes to be added.
+     *
+     * @param root the root node
+     * @param key the key for the new property
+     * @return an object with all information needed for the add operation
+     */
+    NodeAddData prepareAdd(ConfigurationNode root, String key);
+}
diff --git a/src/main/java/org/apache/commons/configuration/tree/MergeCombiner.java b/src/main/java/org/apache/commons/configuration/tree/MergeCombiner.java
new file mode 100644
index 0000000..72a77b0
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/tree/MergeCombiner.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * <p>
+ * A specialized implementation of the {@code NodeCombiner} interface
+ * that performs a merge from two passed in node hierarchies.
+ * </p>
+ * <p>
+ * This combiner performs the merge using a few rules:
+ * <ol>
+ * <li>Nodes can be merged when attributes that appear in both have the same value.</li>
+ * <li>Only a single node in the second file is considered a match to the node in the first file.</li>
+ * <li>Attributes in nodes that match are merged.
+ * <li>Nodes in both files that do not match are added to the result.</li>
+ * </ol>
+ * </p>
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: MergeCombiner.java 1301991 2012-03-17 20:18:02Z sebb $
+ * @since 1.7
+ */
+public class MergeCombiner extends NodeCombiner
+{
+    /**
+     * Combines the given nodes to a new union node.
+     *
+     * @param node1 the first source node
+     * @param node2 the second source node
+     * @return the union node
+     */
+
+    @Override
+    public ConfigurationNode combine(ConfigurationNode node1, ConfigurationNode node2)
+    {
+        ViewNode result = createViewNode();
+        result.setName(node1.getName());
+        result.setValue(node1.getValue());
+        addAttributes(result, node1, node2);
+
+        // Check if nodes can be combined
+        List<ConfigurationNode> children2 = new LinkedList<ConfigurationNode>(node2.getChildren());
+        for (ConfigurationNode child1 : node1.getChildren())
+        {
+            ConfigurationNode child2 = canCombine(node1, node2, child1, children2);
+            if (child2 != null)
+            {
+                result.addChild(combine(child1, child2));
+                children2.remove(child2);
+            }
+            else
+            {
+                result.addChild(child1);
+            }
+        }
+
+        // Add remaining children of node 2
+        for (ConfigurationNode c : children2)
+        {
+            result.addChild(c);
+        }
+        return result;
+    }
+
+    /**
+     * Handles the attributes during a combination process. First all attributes
+     * of the first node will be added to the result. Then all attributes of the
+     * second node, which are not contained in the first node, will also be
+     * added.
+     *
+     * @param result the resulting node
+     * @param node1 the first node
+     * @param node2 the second node
+     */
+    protected void addAttributes(ViewNode result, ConfigurationNode node1,
+            ConfigurationNode node2)
+    {
+        result.appendAttributes(node1);
+        for (ConfigurationNode attr : node2.getAttributes())
+        {
+            if (node1.getAttributeCount(attr.getName()) == 0)
+            {
+                result.addAttribute(attr);
+            }
+        }
+    }
+
+    /**
+     * Tests if the first node can be combined with the second node. A node can
+     * only be combined if its attributes are all present in the second node and
+     * they all have the same value.
+     *
+     * @param node1 the first node
+     * @param node2 the second node
+     * @param child the child node (of the first node)
+     * @return a child of the second node, with which a combination is possible
+     */
+    protected ConfigurationNode canCombine(ConfigurationNode node1,
+            ConfigurationNode node2, ConfigurationNode child, List<ConfigurationNode> children2)
+    {
+        List<ConfigurationNode> attrs1 = child.getAttributes();
+        List<ConfigurationNode> nodes = new ArrayList<ConfigurationNode>();
+
+        List<ConfigurationNode> children = node2.getChildren(child.getName());
+        Iterator<ConfigurationNode> it = children.iterator();
+        while (it.hasNext())
+        {
+            ConfigurationNode node = it.next();
+            Iterator<ConfigurationNode> iter = attrs1.iterator();
+            while (iter.hasNext())
+            {
+                ConfigurationNode attr1 = iter.next();
+                List<ConfigurationNode> list2 = node.getAttributes(attr1.getName());
+                if (list2.size() == 1
+                    && !attr1.getValue().equals(list2.get(0).getValue()))
+                {
+                    node = null;
+                    break;
+                }
+            }
+            if (node != null)
+            {
+                nodes.add(node);
+            }
+        }
+
+        if (nodes.size() == 1)
+        {
+            return nodes.get(0);
+        }
+        if (nodes.size() > 1 && !isListNode(child))
+        {
+            Iterator<ConfigurationNode> iter = nodes.iterator();
+            while (iter.hasNext())
+            {
+                children2.remove(iter.next());
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/tree/NodeAddData.java b/src/main/java/org/apache/commons/configuration/tree/NodeAddData.java
new file mode 100644
index 0000000..f65ea8b
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/tree/NodeAddData.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * <p>
+ * A simple data class used by {@link ExpressionEngine} to store
+ * the results of the {@code prepareAdd()} operation.
+ * </p>
+ * <p>
+ * If a new property is to be added to a configuration, the affected
+ * {@code Configuration} object must know, where in its hierarchy of
+ * configuration nodes new elements have to be added. This information is
+ * obtained by an {@code ExpressionEngine} object that interprets the key
+ * of the new property. This expression engine will pack all information
+ * necessary for the configuration to perform the add operation in an instance
+ * of this class.
+ * </p>
+ * <p>
+ * Information managed by this class contains:
+ * <ul>
+ * <li>the configuration node, to which new elements must be added</li>
+ * <li>the name of the new node</li>
+ * <li>whether the new node is a child node or an attribute node</li>
+ * <li>if a whole branch is to be added at once, the names of all nodes between
+ * the parent node (the target of the add operation) and the new node</li>
+ * </ul>
+ * </p>
+ *
+ * @since 1.3
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: NodeAddData.java 1234988 2012-01-23 21:12:15Z oheger $
+ */
+public class NodeAddData
+{
+    /** Stores the parent node of the add operation. */
+    private ConfigurationNode parent;
+
+    /**
+     * Stores a list with nodes that are on the path between the parent node and
+     * the new node.
+     */
+    private List<String> pathNodes;
+
+    /** Stores the name of the new node. */
+    private String newNodeName;
+
+    /** Stores the attribute flag. */
+    private boolean attribute;
+
+    /**
+     * Creates a new, uninitialized instance of {@code NodeAddData}.
+     */
+    public NodeAddData()
+    {
+        this(null, null);
+    }
+
+    /**
+     * Creates a new instance of {@code NodeAddData} and sets the most
+     * important data fields.
+     *
+     * @param parent the parent node
+     * @param nodeName the name of the new node
+     */
+    public NodeAddData(ConfigurationNode parent, String nodeName)
+    {
+        setParent(parent);
+        setNewNodeName(nodeName);
+    }
+
+    /**
+     * Returns a flag if the new node to be added is an attribute.
+     *
+     * @return <b>true</b> for an attribute node, <b>false</b> for a child
+     * node
+     */
+    public boolean isAttribute()
+    {
+        return attribute;
+    }
+
+    /**
+     * Sets the attribute flag. This flag determines whether an attribute or a
+     * child node will be added.
+     *
+     * @param attribute the attribute flag
+     */
+    public void setAttribute(boolean attribute)
+    {
+        this.attribute = attribute;
+    }
+
+    /**
+     * Returns the name of the new node.
+     *
+     * @return the new node's name
+     */
+    public String getNewNodeName()
+    {
+        return newNodeName;
+    }
+
+    /**
+     * Sets the name of the new node. A node with this name will be added to the
+     * configuration's node hierarchy.
+     *
+     * @param newNodeName the name of the new node
+     */
+    public void setNewNodeName(String newNodeName)
+    {
+        this.newNodeName = newNodeName;
+    }
+
+    /**
+     * Returns the parent node.
+     *
+     * @return the parent node
+     */
+    public ConfigurationNode getParent()
+    {
+        return parent;
+    }
+
+    /**
+     * Sets the parent node. New nodes will be added to this node.
+     *
+     * @param parent the parent node
+     */
+    public void setParent(ConfigurationNode parent)
+    {
+        this.parent = parent;
+    }
+
+    /**
+     * Returns a list with further nodes that must be added. This is needed if a
+     * complete branch is to be added at once. For instance imagine that there
+     * exists only a node {@code database}. Now the key
+     * {@code database.connection.settings.username} (assuming the syntax
+     * of the default expression engine) is to be added. Then
+     * {@code username} is the name of the new node, but the nodes
+     * {@code connection} and {@code settings} must be added to
+     * the parent node first. In this example these names would be returned by
+     * this method.
+     *
+     * @return a list with the names of nodes that must be added as parents of
+     * the new node (never <b>null</b>)
+     */
+    public List<String> getPathNodes()
+    {
+        if (pathNodes != null)
+        {
+            return Collections.unmodifiableList(pathNodes);
+        }
+        else
+        {
+            return Collections.emptyList();
+        }
+    }
+
+    /**
+     * Adds the name of a path node. With this method an additional node to be
+     * added can be defined.
+     *
+     * @param nodeName the name of the node
+     * @see #getPathNodes()
+     */
+    public void addPathNode(String nodeName)
+    {
+        if (pathNodes == null)
+        {
+            pathNodes = new LinkedList<String>();
+        }
+        pathNodes.add(nodeName);
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/tree/NodeCombiner.java b/src/main/java/org/apache/commons/configuration/tree/NodeCombiner.java
new file mode 100644
index 0000000..f4526f6
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/tree/NodeCombiner.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * <p>
+ * A base class for node combiner implementations.
+ * </p>
+ * <p>
+ * A <em>node combiner</em> is an object that knows how two hierarchical node
+ * structures can be combined into a single one. Of course, there are many
+ * possible ways of implementing such a combination, e.g. constructing a union,
+ * an intersection, or an "override" structure (were nodes in the first
+ * hierarchy take precedence over nodes in the second hierarchy). This abstract
+ * base class only provides some helper methods and defines the common interface
+ * for node combiners. Concrete sub classes will implement the diverse
+ * combination algorithms.
+ * </p>
+ * <p>
+ * For some concrete combiner implementations it is important to distinguish
+ * whether a node is a single node or whether it belongs to a list structure.
+ * Alone from the input structures, the combiner will not always be able to make
+ * this decision. So sometimes it may be necessary for the developer to
+ * configure the combiner and tell it, which nodes should be treated as list
+ * nodes. For this purpose the {@code addListNode()} method exists. It
+ * can be passed the name of a node, which should be considered a list node.
+ * </p>
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: NodeCombiner.java 1206476 2011-11-26 16:19:53Z oheger $
+ * @since 1.3
+ */
+public abstract class NodeCombiner
+{
+    /** Stores a list with node names that are known to be list nodes. */
+    protected Set<String> listNodes;
+
+    /**
+     * Creates a new instance of {@code NodeCombiner}.
+     */
+    public NodeCombiner()
+    {
+        listNodes = new HashSet<String>();
+    }
+
+    /**
+     * Adds the name of a node to the list of known list nodes. This means that
+     * nodes with this name will never be combined.
+     *
+     * @param nodeName the name to be added
+     */
+    public void addListNode(String nodeName)
+    {
+        listNodes.add(nodeName);
+    }
+
+    /**
+     * Returns a set with the names of nodes that are known to be list nodes.
+     *
+     * @return a set with the names of list nodes
+     */
+    public Set<String> getListNodes()
+    {
+        return Collections.unmodifiableSet(listNodes);
+    }
+
+    /**
+     * Checks if a node is a list node. This implementation tests if the given
+     * node name is contained in the set of known list nodes. Derived classes
+     * which use different criteria may overload this method.
+     *
+     * @param node the node to be tested
+     * @return a flag whether this is a list node
+     */
+    public boolean isListNode(ConfigurationNode node)
+    {
+        return listNodes.contains(node.getName());
+    }
+
+    /**
+     * Combines the hierarchies represented by the given root nodes. This method
+     * must be defined in concrete sub classes with the implementation of a
+     * specific combination algorithm.
+     *
+     * @param node1 the first root node
+     * @param node2 the second root node
+     * @return the resulting combined node structure
+     */
+    public abstract ConfigurationNode combine(ConfigurationNode node1,
+            ConfigurationNode node2);
+
+    /**
+     * Creates a new view node. This method will be called whenever a new view
+     * node is to be created. It can be overridden to create special view nodes.
+     * This base implementation returns a new instance of
+     * {@link ViewNode}.
+     *
+     * @return the new view node
+     */
+    protected ViewNode createViewNode()
+    {
+        return new ViewNode();
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/tree/OverrideCombiner.java b/src/main/java/org/apache/commons/configuration/tree/OverrideCombiner.java
new file mode 100644
index 0000000..64d5aec
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/tree/OverrideCombiner.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+
+/**
+ * <p>
+ * A concrete combiner implementation that is able to construct an override
+ * combination.
+ * </p>
+ * <p>
+ * An <em>override combination</em> means that nodes in the first node
+ * structure take precedence over nodes in the second, or - in other words -
+ * nodes of the second structure are only added to the resulting structure if
+ * they do not occur in the first one. This is especially suitable for dealing
+ * with the properties of configurations that are defined in an
+ * {@code override} section of a configuration definition file (hence the
+ * name).
+ * </p>
+ * <p>
+ * This combiner will iterate over the second node hierarchy and find all nodes
+ * that are not contained in the first hierarchy; these are added to the result.
+ * If a node can be found in both structures, it is checked whether a
+ * combination (in a recursive way) can be constructed for the two, which will
+ * then be added. Per default, nodes are combined, which occur only once in both
+ * structures. This test is implemented in the {@code canCombine()}
+ * method.
+ * </p>
+ * <p>
+ * As is true for the {@link UnionCombiner}, for this combiner
+ * list nodes are important. The {@code addListNode()} can be called to
+ * declare certain nodes as list nodes. This has the effect that these nodes
+ * will never be combined.
+ * </p>
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: OverrideCombiner.java 1301991 2012-03-17 20:18:02Z sebb $
+ * @since 1.3
+ */
+public class OverrideCombiner extends NodeCombiner
+{
+    /**
+     * Constructs an override combination for the passed in node structures.
+     *
+     * @param node1 the first node
+     * @param node2 the second node
+     * @return the resulting combined node structure
+     */
+    @Override
+    public ConfigurationNode combine(ConfigurationNode node1,
+            ConfigurationNode node2)
+    {
+        ViewNode result = createViewNode();
+        result.setName(node1.getName());
+
+        // Process nodes from the first structure, which override the second
+        for (ConfigurationNode child : node1.getChildren())
+        {
+            ConfigurationNode child2 = canCombine(node1, node2, child);
+            if (child2 != null)
+            {
+                result.addChild(combine(child, child2));
+            }
+            else
+            {
+                result.addChild(child);
+            }
+        }
+
+        // Process nodes from the second structure, which are not contained
+        // in the first structure
+        for (ConfigurationNode child : node2.getChildren())
+        {
+            if (node1.getChildrenCount(child.getName()) < 1)
+            {
+                result.addChild(child);
+            }
+        }
+
+        // Handle attributes and value
+        addAttributes(result, node1, node2);
+        result.setValue((node1.getValue() != null) ? node1.getValue() : node2
+                .getValue());
+
+        return result;
+    }
+
+    /**
+     * Handles the attributes during a combination process. First all attributes
+     * of the first node will be added to the result. Then all attributes of the
+     * second node, which are not contained in the first node, will also be
+     * added.
+     *
+     * @param result the resulting node
+     * @param node1 the first node
+     * @param node2 the second node
+     */
+    protected void addAttributes(ViewNode result, ConfigurationNode node1,
+            ConfigurationNode node2)
+    {
+        result.appendAttributes(node1);
+        for (ConfigurationNode attr : node2.getAttributes())
+        {
+            if (node1.getAttributeCount(attr.getName()) == 0)
+            {
+                result.addAttribute(attr);
+            }
+        }
+    }
+
+    /**
+     * Tests if a child node of the second node can be combined with the given
+     * child node of the first node. If this is the case, the corresponding node
+     * will be returned, otherwise <b>null</b>. This implementation checks
+     * whether the child node occurs only once in both hierarchies and is no
+     * known list node.
+     *
+     * @param node1 the first node
+     * @param node2 the second node
+     * @param child the child node (of the first node)
+     * @return a child of the second node, with which a combination is possible
+     */
+    protected ConfigurationNode canCombine(ConfigurationNode node1,
+            ConfigurationNode node2, ConfigurationNode child)
+    {
+        if (node2.getChildrenCount(child.getName()) == 1
+                && node1.getChildrenCount(child.getName()) == 1
+                && !isListNode(child))
+        {
+            return node2.getChildren(child.getName()).get(0);
+        }
+        else
+        {
+            return null;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/tree/TreeUtils.java b/src/main/java/org/apache/commons/configuration/tree/TreeUtils.java
new file mode 100644
index 0000000..7817870
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/tree/TreeUtils.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import java.io.PrintStream;
+import java.util.Iterator;
+
+/**
+ * Utility methods.
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: TreeUtils.java 1301991 2012-03-17 20:18:02Z sebb $
+ * @since 1.7
+ */
+public final class TreeUtils
+{
+    /** Prevent creating this class. */
+    private TreeUtils()
+    {
+    }
+
+    /**
+     * Print out the data in the configuration.
+     * @param stream The OutputStream.
+     * @param result The root node of the tree.
+     */
+    public static void printTree(PrintStream stream, ConfigurationNode result)
+    {
+        if (stream != null)
+        {
+            printTree(stream, "", result);
+        }
+    }
+
+    private static void printTree(PrintStream stream, String indent, ConfigurationNode result)
+    {
+        StringBuffer buffer = new StringBuffer(indent).append("<").append(result.getName());
+        Iterator<ConfigurationNode> iter = result.getAttributes().iterator();
+        while (iter.hasNext())
+        {
+            ConfigurationNode node = iter.next();
+            buffer.append(" ").append(node.getName()).append("='").append(node.getValue()).append("'");
+        }
+        buffer.append(">");
+        stream.print(buffer.toString());
+        if (result.getValue() != null)
+        {
+            stream.print(result.getValue());
+        }
+        boolean newline = false;
+        if (result.getChildrenCount() > 0)
+        {
+            stream.print("\n");
+            iter = result.getChildren().iterator();
+            while (iter.hasNext())
+            {
+                printTree(stream, indent + "  ", iter.next());
+            }
+            newline = true;
+        }
+        if (newline)
+        {
+            stream.print(indent);
+        }
+        stream.println("</" + result.getName() + ">");
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/tree/UnionCombiner.java b/src/main/java/org/apache/commons/configuration/tree/UnionCombiner.java
new file mode 100644
index 0000000..39fee6c
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/tree/UnionCombiner.java
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * <p>
+ * A specialized implementation of the {@code NodeCombiner} interface
+ * that constructs a union from two passed in node hierarchies.
+ * </p>
+ * <p>
+ * The given source hierarchies are traversed and their nodes are added to the
+ * resulting structure. Under some circumstances two nodes can be combined
+ * rather than adding both. This is the case if both nodes are single children
+ * (no lists) of their parents and do not have values. The corresponding check
+ * is implemented in the {@code findCombineNode()} method.
+ * </p>
+ * <p>
+ * Sometimes it is not possible for this combiner to detect whether two nodes
+ * can be combined or not. Consider the following two node hierarchies:
+ * </p>
+ * <p>
+ *
+ * <pre>
+ * Hierarchy 1:
+ *
+ * Database
+ *   +--Tables
+ *        +--Table
+ *             +--name [users]
+ *             +--fields
+ *                   +--field
+ *                   |    +--name [uid]
+ *                   +--field
+ *                   |    +--name [usrname]
+ *                     ...
+ * </pre>
+ *
+ * </p>
+ * <p>
+ *
+ * <pre>
+ * Hierarchy 2:
+ *
+ * Database
+ *   +--Tables
+ *        +--Table
+ *             +--name [documents]
+ *             +--fields
+ *                   +--field
+ *                   |    +--name [docid]
+ *                   +--field
+ *                   |    +--name [docname]
+ *                     ...
+ * </pre>
+ *
+ * </p>
+ * <p>
+ * Both hierarchies contain data about database tables. Each describes a single
+ * table. If these hierarchies are to be combined, the result should probably
+ * look like the following:
+ * <p>
+ *
+ * <pre>
+ * Database
+ *   +--Tables
+ *        +--Table
+ *        |    +--name [users]
+ *        |    +--fields
+ *        |          +--field
+ *        |          |    +--name [uid]
+ *        |            ...
+ *        +--Table
+ *             +--name [documents]
+ *             +--fields
+ *                   +--field
+ *                   |    +--name [docid]
+ *                     ...
+ * </pre>
+ *
+ * </p>
+ * <p>
+ * i.e. the {@code Tables} nodes should be combined, while the
+ * {@code Table} nodes should both be added to the resulting tree. From
+ * the combiner's point of view there is no difference between the
+ * {@code Tables} and the {@code Table} nodes in the source trees,
+ * so the developer has to help out and give a hint that the {@code Table}
+ * nodes belong to a list structure. This can be done using the
+ * {@code addListNode()} method; this method expects the name of a node,
+ * which should be treated as a list node. So if
+ * {@code addListNode("Table");} was called, the combiner knows that it
+ * must not combine the {@code Table} nodes, but add it both to the
+ * resulting tree.
+ * </p>
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: UnionCombiner.java 1206486 2011-11-26 16:41:12Z oheger $
+ * @since 1.3
+ */
+public class UnionCombiner extends NodeCombiner
+{
+    /**
+     * Combines the given nodes to a new union node.
+     *
+     * @param node1 the first source node
+     * @param node2 the second source node
+     * @return the union node
+     */
+    @Override
+    public ConfigurationNode combine(ConfigurationNode node1,
+            ConfigurationNode node2)
+    {
+        ViewNode result = createViewNode();
+        result.setName(node1.getName());
+        result.appendAttributes(node1);
+        result.appendAttributes(node2);
+
+        // Check if nodes can be combined
+        List<ConfigurationNode> children2 = new LinkedList<ConfigurationNode>(node2.getChildren());
+        for (ConfigurationNode child1 : node1.getChildren())
+        {
+            ConfigurationNode child2 = findCombineNode(node1, node2, child1,
+                    children2);
+            if (child2 != null)
+            {
+                result.addChild(combine(child1, child2));
+                children2.remove(child2);
+            }
+            else
+            {
+                result.addChild(child1);
+            }
+        }
+
+        // Add remaining children of node 2
+        for (ConfigurationNode c : children2)
+        {
+            result.addChild(c);
+        }
+
+        return result;
+    }
+
+    /**
+     * <p>
+     * Tries to find a child node of the second source node, with which a child
+     * of the first source node can be combined. During combining of the source
+     * nodes an iteration over the first source node's children is performed.
+     * For each child node it is checked whether a corresponding child node in
+     * the second source node exists. If this is the case, these corresponding
+     * child nodes are recursively combined and the result is added to the
+     * combined node. This method implements the checks whether such a recursive
+     * combination is possible. The actual implementation tests the following
+     * conditions:
+     * </p>
+     * <p>
+     * <ul>
+     * <li>In both the first and the second source node there is only one child
+     * node with the given name (no list structures).</li>
+     * <li>The given name is not in the list of known list nodes, i.e. it was
+     * not passed to the {@code addListNode()} method.</li>
+     * <li>None of these matching child nodes has a value.</li>
+     * </ul>
+     * </p>
+     * <p>
+     * If all of these tests are successful, the matching child node of the
+     * second source node is returned. Otherwise the result is <b>null</b>.
+     * </p>
+     *
+     * @param node1 the first source node
+     * @param node2 the second source node
+     * @param child the child node of the first source node to be checked
+     * @param children a list with all children of the second source node
+     * @return the matching child node of the second source node or <b>null</b>
+     * if there is none
+     */
+    protected ConfigurationNode findCombineNode(ConfigurationNode node1,
+            ConfigurationNode node2, ConfigurationNode child, List<ConfigurationNode> children)
+    {
+        if (child.getValue() == null && !isListNode(child)
+                && node1.getChildrenCount(child.getName()) == 1
+                && node2.getChildrenCount(child.getName()) == 1)
+        {
+            ConfigurationNode child2 = node2.getChildren(
+                    child.getName()).iterator().next();
+            if (child2.getValue() == null)
+            {
+                return child2;
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/tree/ViewNode.java b/src/main/java/org/apache/commons/configuration/tree/ViewNode.java
new file mode 100644
index 0000000..720f6e2
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/tree/ViewNode.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+
+/**
+ * <p>
+ * A specialized node implementation to be used in view configurations.
+ * </p>
+ * <p>
+ * Some configurations provide a logical view on the nodes of other
+ * configurations. These configurations construct their own hierarchy of nodes
+ * based on the node trees of their source configurations. This special node
+ * class can be used for this purpose. It allows child nodes and attributes to
+ * be added without changing their parent node. So a node can belong to a
+ * hierarchy of nodes of a source configuration, but be also contained in a view
+ * configuration.
+ * </p>
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: ViewNode.java 1206488 2011-11-26 16:42:41Z oheger $
+ * @since 1.3
+ */
+public class ViewNode extends DefaultConfigurationNode
+{
+    /**
+     * Adds an attribute to this view node. The new attribute's parent node will
+     * be saved.
+     *
+     * @param attr the attribute node to be added
+     */
+    @Override
+    public void addAttribute(ConfigurationNode attr)
+    {
+        ConfigurationNode parent = null;
+
+        if (attr != null)
+        {
+            parent = attr.getParentNode();
+            super.addAttribute(attr);
+            attr.setParentNode(parent);
+        }
+        else
+        {
+            throw new IllegalArgumentException("Attribute node must not be null!");
+        }
+    }
+
+    /**
+     * Adds a child node to this view node. The new child's parent node will be
+     * saved.
+     *
+     * @param child the child node to be added
+     */
+    @Override
+    public void addChild(ConfigurationNode child)
+    {
+        ConfigurationNode parent = null;
+
+        if (child != null)
+        {
+            parent = child.getParentNode();
+            super.addChild(child);
+            child.setParentNode(parent);
+        }
+        else
+        {
+            throw new IllegalArgumentException("Child node must not be null!");
+        }
+    }
+
+    /**
+     * Adds all attribute nodes of the given source node to this view node.
+     *
+     * @param source the source node
+     */
+    public void appendAttributes(ConfigurationNode source)
+    {
+        if (source != null)
+        {
+            for (ConfigurationNode attr : source.getAttributes())
+            {
+                addAttribute(attr);
+            }
+        }
+    }
+
+    /**
+     * Adds all child nodes of the given source node to this view node.
+     *
+     * @param source the source node
+     */
+    public void appendChildren(ConfigurationNode source)
+    {
+        if (source != null)
+        {
+            for (ConfigurationNode child : source.getChildren())
+            {
+                addChild(child);
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/tree/package.html b/src/main/java/org/apache/commons/configuration/tree/package.html
new file mode 100644
index 0000000..9008f09
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/tree/package.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<html>
+<head>
+</head>
+<body>
+
+<p>
+A package with helper and utility classes used by hierarchical configurations.
+</p>
+<p>
+<font size="-2">$Id: package.html 439648 2006-09-02 20:42:10Z oheger $</font>
+</p>
+
+</body>
+</html>
diff --git a/src/main/java/org/apache/commons/configuration/tree/xpath/ConfigurationNodeIteratorAttribute.java b/src/main/java/org/apache/commons/configuration/tree/xpath/ConfigurationNodeIteratorAttribute.java
new file mode 100644
index 0000000..c22efdf
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/tree/xpath/ConfigurationNodeIteratorAttribute.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree.xpath;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.jxpath.ri.QName;
+import org.apache.commons.jxpath.ri.model.NodePointer;
+
+/**
+ * A specialized node iterator implementation that deals with attribute nodes.
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: ConfigurationNodeIteratorAttribute.java 1206492 2011-11-26 16:52:16Z oheger $
+ */
+class ConfigurationNodeIteratorAttribute extends
+        ConfigurationNodeIteratorBase
+{
+    /** Constant for the wildcard node name.*/
+    private static final String WILDCARD = "*";
+
+    /**
+     * Creates a new instance of {@code ConfigurationNodeIteratorAttribute}.
+     * @param parent the parent node pointer
+     * @param name the name of the selected attribute
+     */
+    public ConfigurationNodeIteratorAttribute(NodePointer parent, QName name)
+    {
+        super(parent, false);
+        initSubNodeList(createSubNodeList((ConfigurationNode) parent.getNode(),
+                name));
+    }
+
+    /**
+     * Determines which attributes are selected based on the passed in node
+     * name.
+     * @param node the current node
+     * @param name the name of the selected attribute
+     * @return a list with the selected attributes
+     */
+    protected List<ConfigurationNode> createSubNodeList(ConfigurationNode node, QName name)
+    {
+        if (name.getPrefix() != null)
+        {
+            // namespace prefixes are not supported
+            return Collections.emptyList();
+        }
+
+        List<ConfigurationNode> result = new ArrayList<ConfigurationNode>();
+        if (!WILDCARD.equals(name.getName()))
+        {
+            result.addAll(node.getAttributes(name.getName()));
+        }
+        else
+        {
+            result.addAll(node.getAttributes());
+        }
+
+        return result;
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/tree/xpath/ConfigurationNodeIteratorBase.java b/src/main/java/org/apache/commons/configuration/tree/xpath/ConfigurationNodeIteratorBase.java
new file mode 100644
index 0000000..6b5e88b
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/tree/xpath/ConfigurationNodeIteratorBase.java
@@ -0,0 +1,197 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree.xpath;
+
+import java.util.List;
+
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.jxpath.ri.model.NodeIterator;
+import org.apache.commons.jxpath.ri.model.NodePointer;
+
+/**
+ * <p>
+ * A base class for implementing iterators over configuration nodes.
+ * </p>
+ * <p>
+ * This class already provides common functionality for implementing the
+ * iteration process. Derived classes will implement specific behavior based on
+ * the concrete node type (child node or attribute node).
+ * </p>
+ *
+ * @since 1.3
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: ConfigurationNodeIteratorBase.java 1206491 2011-11-26 16:51:50Z oheger $
+ */
+abstract class ConfigurationNodeIteratorBase implements NodeIterator
+{
+    /** Stores the parent node pointer. */
+    private NodePointer parent;
+
+    /** Stores the list with the sub nodes. */
+    private List<ConfigurationNode> subNodes;
+
+    /** Stores the current position. */
+    private int position;
+
+    /** Stores the start offset of the iterator. */
+    private int startOffset;
+
+    /** Stores the reverse flag. */
+    private boolean reverse;
+
+    /**
+     * Creates a new instance of {@code ConfigurationNodeIteratorBase}
+     * and initializes it.
+     *
+     * @param parent the parent pointer
+     * @param reverse the reverse flag
+     */
+    protected ConfigurationNodeIteratorBase(NodePointer parent, boolean reverse)
+    {
+        this.parent = parent;
+        this.reverse = reverse;
+    }
+
+    /**
+     * Returns the position of the iteration.
+     *
+     * @return the position
+     */
+    public int getPosition()
+    {
+        return position;
+    }
+
+    /**
+     * Sets the position of the iteration.
+     *
+     * @param pos the new position
+     * @return a flag if this is a valid position
+     */
+    public boolean setPosition(int pos)
+    {
+        position = pos;
+        return pos >= 1 && pos <= getMaxPosition();
+    }
+
+    /**
+     * Returns the current node pointer.
+     *
+     * @return the current pointer in this iteration
+     */
+    public NodePointer getNodePointer()
+    {
+        if (getPosition() < 1 && !setPosition(1))
+        {
+            return null;
+        }
+
+        return createNodePointer(subNodes.get(positionToIndex(getPosition())));
+    }
+
+    /**
+     * Returns the parent node pointer.
+     *
+     * @return the parent node pointer
+     */
+    protected NodePointer getParent()
+    {
+        return parent;
+    }
+
+    /**
+     * Returns the start offset of the iteration.
+     *
+     * @return the start offset
+     */
+    protected int getStartOffset()
+    {
+        return startOffset;
+    }
+
+    /**
+     * Sets the start offset of the iteration. This is used when a start element
+     * was set.
+     *
+     * @param startOffset the start offset
+     */
+    protected void setStartOffset(int startOffset)
+    {
+        this.startOffset = startOffset;
+        if (reverse)
+        {
+            this.startOffset--;
+        }
+        else
+        {
+            this.startOffset++;
+        }
+    }
+
+    /**
+     * Initializes the list of sub nodes for the iteration. This method must be
+     * called during initialization phase.
+     *
+     * @param nodes the list with the sub nodes
+     */
+    protected void initSubNodeList(List<ConfigurationNode> nodes)
+    {
+        subNodes = nodes;
+        if (reverse)
+        {
+            setStartOffset(subNodes.size());
+        }
+    }
+
+    /**
+     * Returns the maximum position for this iterator.
+     *
+     * @return the maximum allowed position
+     */
+    protected int getMaxPosition()
+    {
+        return reverse ? getStartOffset() + 1 : subNodes.size()
+                - getStartOffset();
+    }
+
+    /**
+     * Creates the configuration node pointer for the current position. This
+     * method is called by {@code getNodePointer()}. Derived classes
+     * must create the correct pointer object.
+     *
+     * @param node the current configuration node
+     * @return the node pointer
+     */
+    protected NodePointer createNodePointer(ConfigurationNode node)
+    {
+        return new ConfigurationNodePointer(getParent(), node);
+    }
+
+    /**
+     * Returns the index in the data list for the given position. This method
+     * also checks the reverse flag.
+     *
+     * @param pos the position (1-based)
+     * @return the corresponding list index
+     */
+    protected int positionToIndex(int pos)
+    {
+        return (reverse ? 1 - pos : pos - 1) + getStartOffset();
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/tree/xpath/ConfigurationNodeIteratorChildren.java b/src/main/java/org/apache/commons/configuration/tree/xpath/ConfigurationNodeIteratorChildren.java
new file mode 100644
index 0000000..9a39a39
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/tree/xpath/ConfigurationNodeIteratorChildren.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree.xpath;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.jxpath.ri.Compiler;
+import org.apache.commons.jxpath.ri.QName;
+import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
+import org.apache.commons.jxpath.ri.compiler.NodeTest;
+import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
+import org.apache.commons.jxpath.ri.model.NodePointer;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * A specialized iterator implementation for the child nodes of a configuration
+ * node.
+ *
+ * @since 1.3
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: ConfigurationNodeIteratorChildren.java 1206493 2011-11-26 16:56:42Z oheger $
+ */
+class ConfigurationNodeIteratorChildren extends ConfigurationNodeIteratorBase
+{
+    /**
+     * Creates a new instance of {@code ConfigurationNodeIteratorChildren}
+     * and initializes it.
+     *
+     * @param parent the parent pointer
+     * @param nodeTest the test selecting the sub nodes
+     * @param reverse the reverse flag
+     * @param startsWith the first element of the iteration
+     */
+    public ConfigurationNodeIteratorChildren(NodePointer parent,
+            NodeTest nodeTest, boolean reverse, NodePointer startsWith)
+    {
+        super(parent, reverse);
+        ConfigurationNode root = (ConfigurationNode) parent.getNode();
+        List<ConfigurationNode> childNodes = createSubNodeList(root, nodeTest);
+        initSubNodeList(childNodes);
+        if (startsWith != null)
+        {
+            setStartOffset(findStartIndex(root,
+                    (ConfigurationNode) startsWith.getNode()));
+        }
+    }
+
+    /**
+     * Creates the list with sub nodes. This method gets called during
+     * initialization phase. It finds out, based on the given test, which nodes
+     * must be iterated over.
+     *
+     * @param node the current node
+     * @param test the test object
+     * @return a list with the matching nodes
+     */
+    protected List<ConfigurationNode> createSubNodeList(ConfigurationNode node, NodeTest test)
+    {
+        List<ConfigurationNode> children = node.getChildren();
+
+        if (test == null)
+        {
+            return children;
+        }
+        else
+        {
+            if (test instanceof NodeNameTest)
+            {
+                NodeNameTest nameTest = (NodeNameTest) test;
+                QName name = nameTest.getNodeName();
+                if (name.getPrefix() == null)
+                {
+                    if (nameTest.isWildcard())
+                    {
+                        return children;
+                    }
+
+                    List<ConfigurationNode> result = new ArrayList<ConfigurationNode>();
+                    for (ConfigurationNode child : children)
+                    {
+                        if (StringUtils.equals(name.getName(), child.getName()))
+                        {
+                            result.add(child);
+                        }
+                    }
+                    return result;
+                }
+            }
+
+            else if (test instanceof NodeTypeTest)
+            {
+                NodeTypeTest typeTest = (NodeTypeTest) test;
+                if (typeTest.getNodeType() == Compiler.NODE_TYPE_NODE
+                        || typeTest.getNodeType() == Compiler.NODE_TYPE_TEXT)
+                {
+                    return children;
+                }
+            }
+        }
+
+        return Collections.emptyList();
+    }
+
+    /**
+     * Determines the start position of the iteration. Finds the index of the
+     * given start node in the children of the root node.
+     *
+     * @param node the root node
+     * @param startNode the start node
+     * @return the start node's index
+     */
+    protected int findStartIndex(ConfigurationNode node,
+            ConfigurationNode startNode)
+    {
+        for (int index = 0; index < node.getChildrenCount(); index++)
+        {
+            if (node.getChild(index) == startNode)
+            {
+                return index;
+            }
+        }
+
+        return -1;
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/tree/xpath/ConfigurationNodePointer.java b/src/main/java/org/apache/commons/configuration/tree/xpath/ConfigurationNodePointer.java
new file mode 100644
index 0000000..8a08f66
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/tree/xpath/ConfigurationNodePointer.java
@@ -0,0 +1,270 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree.xpath;
+
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.jxpath.ri.Compiler;
+import org.apache.commons.jxpath.ri.QName;
+import org.apache.commons.jxpath.ri.compiler.NodeTest;
+import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
+import org.apache.commons.jxpath.ri.model.NodeIterator;
+import org.apache.commons.jxpath.ri.model.NodePointer;
+
+/**
+ * <p>
+ * A specific {@code NodePointer} implementation for configuration nodes.
+ * </p>
+ * <p>
+ * This is needed for queries using JXPath.
+ * </p>
+ *
+ * @since 1.3
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: ConfigurationNodePointer.java 1206496 2011-11-26 17:01:14Z oheger $
+ */
+class ConfigurationNodePointer extends NodePointer
+{
+    /**
+     * The serial version UID.
+     */
+    private static final long serialVersionUID = -1087475639680007713L;
+
+    /** Stores the associated configuration node. */
+    private ConfigurationNode node;
+
+    /**
+     * Creates a new instance of {@code ConfigurationNodePointer}.
+     *
+     * @param node the node
+     * @param locale the locale
+     */
+    public ConfigurationNodePointer(ConfigurationNode node, Locale locale)
+    {
+        super(null, locale);
+        this.node = node;
+    }
+
+    /**
+     * Creates a new instance of {@code ConfigurationNodePointer} and
+     * initializes it with its parent pointer.
+     *
+     * @param parent the parent pointer
+     * @param node the associated node
+     */
+    public ConfigurationNodePointer(NodePointer parent, ConfigurationNode node)
+    {
+        super(parent);
+        this.node = node;
+    }
+
+    /**
+     * Returns a flag whether this node is a leaf. This is the case if there are
+     * no child nodes.
+     *
+     * @return a flag if this node is a leaf
+     */
+    @Override
+    public boolean isLeaf()
+    {
+        return node.getChildrenCount() < 1;
+    }
+
+    /**
+     * Returns a flag if this node is a collection. This is not the case.
+     *
+     * @return the collection flag
+     */
+    @Override
+    public boolean isCollection()
+    {
+        return false;
+    }
+
+    /**
+     * Returns this node's length. This is always 1.
+     *
+     * @return the node's length
+     */
+    @Override
+    public int getLength()
+    {
+        return 1;
+    }
+
+    /**
+     * Checks whether this node pointer refers to an attribute node. This method
+     * checks the attribute flag of the associated configuration node.
+     *
+     * @return the attribute flag
+     */
+    @Override
+    public boolean isAttribute()
+    {
+        return node.isAttribute();
+    }
+
+    /**
+     * Returns this node's name.
+     *
+     * @return the name
+     */
+    @Override
+    public QName getName()
+    {
+        return new QName(null, node.getName());
+    }
+
+    /**
+     * Returns this node's base value. This is the associated configuration
+     * node.
+     *
+     * @return the base value
+     */
+    @Override
+    public Object getBaseValue()
+    {
+        return node;
+    }
+
+    /**
+     * Returns the immediate node. This is the associated configuration node.
+     *
+     * @return the immediate node
+     */
+    @Override
+    public Object getImmediateNode()
+    {
+        return node;
+    }
+
+    /**
+     * Returns the value of this node.
+     *
+     * @return the represented node's value
+     */
+    @Override
+    public Object getValue()
+    {
+        return node.getValue();
+    }
+
+    /**
+     * Sets the value of this node.
+     *
+     * @param value the new value
+     */
+    @Override
+    public void setValue(Object value)
+    {
+        node.setValue(value);
+    }
+
+    /**
+     * Compares two child node pointers.
+     *
+     * @param pointer1 one pointer
+     * @param pointer2 another pointer
+     * @return a flag, which pointer should be sorted first
+     */
+    @Override
+    public int compareChildNodePointers(NodePointer pointer1,
+            NodePointer pointer2)
+    {
+        ConfigurationNode node1 = (ConfigurationNode) pointer1.getBaseValue();
+        ConfigurationNode node2 = (ConfigurationNode) pointer2.getBaseValue();
+
+        // attributes will be sorted before child nodes
+        if (node1.isAttribute() && !node2.isAttribute())
+        {
+            return -1;
+        }
+        else if (node2.isAttribute() && !node1.isAttribute())
+        {
+            return 1;
+        }
+
+        else
+        {
+            // sort based on the occurrence in the sub node list
+            List<ConfigurationNode> subNodes = node1.isAttribute() ? node.getAttributes() : node
+                    .getChildren();
+            for (ConfigurationNode child : subNodes)
+            {
+                if (child == node1)
+                {
+                    return -1;
+                }
+                else if (child == node2)
+                {
+                    return 1;
+                }
+            }
+            return 0; // should not happen
+        }
+    }
+
+    /**
+     * Returns an iterator for the attributes that match the given name.
+     *
+     * @param name the attribute name
+     * @return the iterator for the attributes
+     */
+    @Override
+    public NodeIterator attributeIterator(QName name)
+    {
+        return new ConfigurationNodeIteratorAttribute(this, name);
+    }
+
+    /**
+     * Returns an iterator for the children of this pointer that match the given
+     * test object.
+     *
+     * @param test the test object
+     * @param reverse the reverse flag
+     * @param startWith the start value of the iteration
+     */
+    @Override
+    public NodeIterator childIterator(NodeTest test, boolean reverse,
+            NodePointer startWith)
+    {
+        return new ConfigurationNodeIteratorChildren(this, test, reverse,
+                startWith);
+    }
+
+    /**
+     * Tests if this node matches the given test. Configuration nodes are text
+     * nodes, too because they can contain a value.
+     *
+     * @param test the test object
+     * @return a flag if this node corresponds to the test
+     */
+    @Override
+    public boolean testNode(NodeTest test)
+    {
+        if (test instanceof NodeTypeTest
+                && ((NodeTypeTest) test).getNodeType() == Compiler.NODE_TYPE_TEXT)
+        {
+            return true;
+        }
+        return super.testNode(test);
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/tree/xpath/ConfigurationNodePointerFactory.java b/src/main/java/org/apache/commons/configuration/tree/xpath/ConfigurationNodePointerFactory.java
new file mode 100644
index 0000000..b7d7da0
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/tree/xpath/ConfigurationNodePointerFactory.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree.xpath;
+
+import java.util.Locale;
+
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.jxpath.ri.QName;
+import org.apache.commons.jxpath.ri.model.NodePointer;
+import org.apache.commons.jxpath.ri.model.NodePointerFactory;
+
+/**
+ * Implementation of the {@code NodePointerFactory} interface for
+ * configuration nodes.
+ *
+ * @since 1.3
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: ConfigurationNodePointerFactory.java 1206498 2011-11-26 17:01:58Z oheger $
+ */
+public class ConfigurationNodePointerFactory implements NodePointerFactory
+{
+    /** Constant for the order of this factory. */
+    public static final int CONFIGURATION_NODE_POINTER_FACTORY_ORDER = 200;
+
+    /**
+     * Returns the order of this factory between other factories.
+     *
+     * @return this order's factory
+     */
+    public int getOrder()
+    {
+        return CONFIGURATION_NODE_POINTER_FACTORY_ORDER;
+    }
+
+    /**
+     * Creates a node pointer for the specified bean. If the bean is a
+     * configuration node, a corresponding pointer is returned.
+     *
+     * @param name the name of the node
+     * @param bean the bean
+     * @param locale the locale
+     * @return a pointer for a configuration node if the bean is such a node
+     */
+    public NodePointer createNodePointer(QName name, Object bean, Locale locale)
+    {
+        if (bean instanceof ConfigurationNode)
+        {
+            return new ConfigurationNodePointer((ConfigurationNode) bean,
+                    locale);
+        }
+        return null;
+    }
+
+    /**
+     * Creates a node pointer for the specified bean. If the bean is a
+     * configuration node, a corresponding pointer is returned.
+     *
+     * @param parent the parent node
+     * @param name the name
+     * @param bean the bean
+     * @return a pointer for a configuration node if the bean is such a node
+     */
+    public NodePointer createNodePointer(NodePointer parent, QName name,
+            Object bean)
+    {
+        if (bean instanceof ConfigurationNode)
+        {
+            return new ConfigurationNodePointer(parent,
+                    (ConfigurationNode) bean);
+        }
+        return null;
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/tree/xpath/XPathExpressionEngine.java b/src/main/java/org/apache/commons/configuration/tree/xpath/XPathExpressionEngine.java
new file mode 100644
index 0000000..a169a36
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/tree/xpath/XPathExpressionEngine.java
@@ -0,0 +1,426 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree.xpath;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.ExpressionEngine;
+import org.apache.commons.configuration.tree.NodeAddData;
+import org.apache.commons.jxpath.JXPathContext;
+import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * <p>
+ * A specialized implementation of the {@code ExpressionEngine} interface
+ * that is able to evaluate XPATH expressions.
+ * </p>
+ * <p>
+ * This class makes use of <a href="http://commons.apache.org/jxpath/"> Commons
+ * JXPath</a> for handling XPath expressions and mapping them to the nodes of a
+ * hierarchical configuration. This makes the rich and powerful XPATH syntax
+ * available for accessing properties from a configuration object.
+ * </p>
+ * <p>
+ * For selecting properties arbitrary XPATH expressions can be used, which
+ * select single or multiple configuration nodes. The associated
+ * {@code Configuration} instance will directly pass the specified property
+ * keys into this engine. If a key is not syntactically correct, an exception
+ * will be thrown.
+ * </p>
+ * <p>
+ * For adding new properties, this expression engine uses a specific syntax: the
+ * "key" of a new property must consist of two parts that are
+ * separated by whitespace:
+ * <ol>
+ * <li>An XPATH expression selecting a single node, to which the new element(s)
+ * are to be added. This can be an arbitrary complex expression, but it must
+ * select exactly one node, otherwise an exception will be thrown.</li>
+ * <li>The name of the new element(s) to be added below this parent node. Here
+ * either a single node name or a complete path of nodes (separated by the
+ * "/" character or "@" for an attribute) can be specified.</li>
+ * </ol>
+ * Some examples for valid keys that can be passed into the configuration's
+ * {@code addProperty()} method follow:
+ * </p>
+ * <p>
+ *
+ * <pre>
+ * "/tables/table[1] type"
+ * </pre>
+ *
+ * </p>
+ * <p>
+ * This will add a new {@code type} node as a child of the first
+ * {@code table} element.
+ * </p>
+ * <p>
+ *
+ * <pre>
+ * "/tables/table[1] @type"
+ * </pre>
+ *
+ * </p>
+ * <p>
+ * Similar to the example above, but this time a new attribute named
+ * {@code type} will be added to the first {@code table} element.
+ * </p>
+ * <p>
+ *
+ * <pre>
+ * "/tables table/fields/field/name"
+ * </pre>
+ *
+ * </p>
+ * <p>
+ * This example shows how a complex path can be added. Parent node is the
+ * {@code tables} element. Here a new branch consisting of the nodes
+ * {@code table}, {@code fields}, {@code field}, and
+ * {@code name} will be added.
+ * </p>
+ * <p>
+ *
+ * <pre>
+ * "/tables table/fields/field at type"
+ * </pre>
+ *
+ * </p>
+ * <p>
+ * This is similar to the last example, but in this case a complex path ending
+ * with an attribute is defined.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This extended syntax for adding properties only works
+ * with the {@code addProperty()} method. {@code setProperty()} does
+ * not support creating new nodes this way.
+ * </p>
+ * <p>
+ * From version 1.7 on, it is possible to use regular keys in calls to
+ * {@code addProperty()} (i.e. keys that do not have to contain a
+ * whitespace as delimiter). In this case the key is evaluated, and the biggest
+ * part pointing to an existing node is determined. The remaining part is then
+ * added as new path. As an example consider the key
+ *
+ * <pre>
+ * "tables/table[last()]/fields/field/name"
+ * </pre>
+ *
+ * If the key does not point to an existing node, the engine will check the
+ * paths {@code "tables/table[last()]/fields/field"},
+ * {@code "tables/table[last()]/fields"},
+ * {@code "tables/table[last()]"}, and so on, until a key is
+ * found which points to a node. Let's assume that the last key listed above can
+ * be resolved in this way. Then from this key the following key is derived:
+ * {@code "tables/table[last()] fields/field/name"} by appending
+ * the remaining part after a whitespace. This key can now be processed using
+ * the original algorithm. Keys of this form can also be used with the
+ * {@code setProperty()} method. However, it is still recommended to use
+ * the old format because it makes explicit at which position new nodes should
+ * be added. For keys without a whitespace delimiter there may be ambiguities.
+ * </p>
+ *
+ * @since 1.3
+ * @author <a
+ *         href="http://commons.apache.org/configuration/team-list.html">Commons
+ *         Configuration team</a>
+ * @version $Id: XPathExpressionEngine.java 1206563 2011-11-26 19:47:26Z oheger $
+ */
+public class XPathExpressionEngine implements ExpressionEngine
+{
+    /** Constant for the path delimiter. */
+    static final String PATH_DELIMITER = "/";
+
+    /** Constant for the attribute delimiter. */
+    static final String ATTR_DELIMITER = "@";
+
+    /** Constant for the delimiters for splitting node paths. */
+    private static final String NODE_PATH_DELIMITERS = PATH_DELIMITER
+            + ATTR_DELIMITER;
+
+    /**
+     * Constant for a space which is used as delimiter in keys for adding
+     * properties.
+     */
+    private static final String SPACE = " ";
+
+    /**
+     * Executes a query. The passed in property key is directly passed to a
+     * JXPath context.
+     *
+     * @param root the configuration root node
+     * @param key the query to be executed
+     * @return a list with the nodes that are selected by the query
+     */
+    public List<ConfigurationNode> query(ConfigurationNode root, String key)
+    {
+        if (StringUtils.isEmpty(key))
+        {
+            return Collections.singletonList(root);
+        }
+        else
+        {
+            JXPathContext context = createContext(root, key);
+            // This is safe because our node pointer implementations will return
+            // a list of configuration nodes.
+            @SuppressWarnings("unchecked")
+            List<ConfigurationNode> result = context.selectNodes(key);
+            if (result == null)
+            {
+                result = Collections.emptyList();
+            }
+            return result;
+        }
+    }
+
+    /**
+     * Returns a (canonical) key for the given node based on the parent's key.
+     * This implementation will create an XPATH expression that selects the
+     * given node (under the assumption that the passed in parent key is valid).
+     * As the {@code nodeKey()} implementation of
+     * {@link org.apache.commons.configuration.tree.DefaultExpressionEngine DefaultExpressionEngine}
+     * this method will not return indices for nodes. So all child nodes of a
+     * given parent with the same name will have the same key.
+     *
+     * @param node the node for which a key is to be constructed
+     * @param parentKey the key of the parent node
+     * @return the key for the given node
+     */
+    public String nodeKey(ConfigurationNode node, String parentKey)
+    {
+        if (parentKey == null)
+        {
+            // name of the root node
+            return StringUtils.EMPTY;
+        }
+        else if (node.getName() == null)
+        {
+            // paranoia check for undefined node names
+            return parentKey;
+        }
+
+        else
+        {
+            StringBuilder buf = new StringBuilder(parentKey.length()
+                    + node.getName().length() + PATH_DELIMITER.length());
+            if (parentKey.length() > 0)
+            {
+                buf.append(parentKey);
+                buf.append(PATH_DELIMITER);
+            }
+            if (node.isAttribute())
+            {
+                buf.append(ATTR_DELIMITER);
+            }
+            buf.append(node.getName());
+            return buf.toString();
+        }
+    }
+
+    /**
+     * Prepares an add operation for a configuration property. The expected
+     * format of the passed in key is explained in the class comment.
+     *
+     * @param root the configuration's root node
+     * @param key the key describing the target of the add operation and the
+     * path of the new node
+     * @return a data object to be evaluated by the calling configuration object
+     */
+    public NodeAddData prepareAdd(ConfigurationNode root, String key)
+    {
+        if (key == null)
+        {
+            throw new IllegalArgumentException(
+                    "prepareAdd: key must not be null!");
+        }
+
+        String addKey = key;
+        int index = findKeySeparator(addKey);
+        if (index < 0)
+        {
+            addKey = generateKeyForAdd(root, addKey);
+            index = findKeySeparator(addKey);
+        }
+
+        List<ConfigurationNode> nodes = query(root, addKey.substring(0, index).trim());
+        if (nodes.size() != 1)
+        {
+            throw new IllegalArgumentException(
+                    "prepareAdd: key must select exactly one target node!");
+        }
+
+        NodeAddData data = new NodeAddData();
+        data.setParent(nodes.get(0));
+        initNodeAddData(data, addKey.substring(index).trim());
+        return data;
+    }
+
+    /**
+     * Creates the {@code JXPathContext} used for executing a query. This
+     * method will create a new context and ensure that it is correctly
+     * initialized.
+     *
+     * @param root the configuration root node
+     * @param key the key to be queried
+     * @return the new context
+     */
+    protected JXPathContext createContext(ConfigurationNode root, String key)
+    {
+        JXPathContext context = JXPathContext.newContext(root);
+        context.setLenient(true);
+        return context;
+    }
+
+    /**
+     * Initializes most properties of a {@code NodeAddData} object. This
+     * method is called by {@code prepareAdd()} after the parent node has
+     * been found. Its task is to interpret the passed in path of the new node.
+     *
+     * @param data the data object to initialize
+     * @param path the path of the new node
+     */
+    protected void initNodeAddData(NodeAddData data, String path)
+    {
+        String lastComponent = null;
+        boolean attr = false;
+        boolean first = true;
+
+        StringTokenizer tok = new StringTokenizer(path, NODE_PATH_DELIMITERS,
+                true);
+        while (tok.hasMoreTokens())
+        {
+            String token = tok.nextToken();
+            if (PATH_DELIMITER.equals(token))
+            {
+                if (attr)
+                {
+                    invalidPath(path, " contains an attribute"
+                            + " delimiter at an unallowed position.");
+                }
+                if (lastComponent == null)
+                {
+                    invalidPath(path,
+                            " contains a '/' at an unallowed position.");
+                }
+                data.addPathNode(lastComponent);
+                lastComponent = null;
+            }
+
+            else if (ATTR_DELIMITER.equals(token))
+            {
+                if (attr)
+                {
+                    invalidPath(path,
+                            " contains multiple attribute delimiters.");
+                }
+                if (lastComponent == null && !first)
+                {
+                    invalidPath(path,
+                            " contains an attribute delimiter at an unallowed position.");
+                }
+                if (lastComponent != null)
+                {
+                    data.addPathNode(lastComponent);
+                }
+                attr = true;
+                lastComponent = null;
+            }
+
+            else
+            {
+                lastComponent = token;
+            }
+            first = false;
+        }
+
+        if (lastComponent == null)
+        {
+            invalidPath(path, "contains no components.");
+        }
+        data.setNewNodeName(lastComponent);
+        data.setAttribute(attr);
+    }
+
+    /**
+     * Tries to generate a key for adding a property. This method is called if a
+     * key was used for adding properties which does not contain a space
+     * character. It splits the key at its single components and searches for
+     * the last existing component. Then a key compatible for adding properties
+     * is generated.
+     *
+     * @param root the root node of the configuration
+     * @param key the key in question
+     * @return the key to be used for adding the property
+     */
+    private String generateKeyForAdd(ConfigurationNode root, String key)
+    {
+        int pos = key.lastIndexOf(PATH_DELIMITER, key.length());
+
+        while (pos >= 0)
+        {
+            String keyExisting = key.substring(0, pos);
+            if (!query(root, keyExisting).isEmpty())
+            {
+                StringBuilder buf = new StringBuilder(key.length() + 1);
+                buf.append(keyExisting).append(SPACE);
+                buf.append(key.substring(pos + 1));
+                return buf.toString();
+            }
+            pos = key.lastIndexOf(PATH_DELIMITER, pos - 1);
+        }
+
+        return SPACE + key;
+    }
+
+    /**
+     * Helper method for throwing an exception about an invalid path.
+     *
+     * @param path the invalid path
+     * @param msg the exception message
+     */
+    private void invalidPath(String path, String msg)
+    {
+        throw new IllegalArgumentException("Invalid node path: \"" + path
+                + "\" " + msg);
+    }
+
+    /**
+     * Determines the position of the separator in a key for adding new
+     * properties. If no delimiter is found, result is -1.
+     *
+     * @param key the key
+     * @return the position of the delimiter
+     */
+    private static int findKeySeparator(String key)
+    {
+        int index = key.length() - 1;
+        while (index >= 0 && !Character.isWhitespace(key.charAt(index)))
+        {
+            index--;
+        }
+        return index;
+    }
+
+    // static initializer: registers the configuration node pointer factory
+    static
+    {
+        JXPathContextReferenceImpl
+                .addNodePointerFactory(new ConfigurationNodePointerFactory());
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/tree/xpath/package.html b/src/main/java/org/apache/commons/configuration/tree/xpath/package.html
new file mode 100644
index 0000000..c9b63eb
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/tree/xpath/package.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<html>
+<head>
+</head>
+<body>
+
+<p>
+This package contains the implementation of the XPathExpressionEngine class, which
+enables XPATH support for querying configuration properties.
+</p>
+<p>
+<font size="-2">$Id: package.html 439648 2006-09-02 20:42:10Z oheger $</font>
+</p>
+
+</body>
+</html>
diff --git a/src/main/java/org/apache/commons/configuration/web/AppletConfiguration.java b/src/main/java/org/apache/commons/configuration/web/AppletConfiguration.java
new file mode 100644
index 0000000..637cb07
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/web/AppletConfiguration.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.web;
+
+import java.applet.Applet;
+import java.util.Arrays;
+import java.util.Iterator;
+
+/**
+ * A configuration wrapper to read applet parameters. This configuration is
+ * read only, adding or removing a property will throw an
+ * UnsupportedOperationException.
+ *
+ * @author <a href="mailto:ebourg at apache.org">Emmanuel Bourg</a>
+ * @version $Id: AppletConfiguration.java 1211124 2011-12-06 20:56:16Z oheger $
+ * @since 1.1
+ */
+public class AppletConfiguration extends BaseWebConfiguration
+{
+    /** Stores the wrapped applet.*/
+    protected Applet applet;
+
+    /**
+     * Create an AppletConfiguration using the initialization parameters of
+     * the specified Applet.
+     *
+     * @param applet the applet
+     */
+    public AppletConfiguration(Applet applet)
+    {
+        this.applet = applet;
+    }
+
+    public Object getProperty(String key)
+    {
+        return handleDelimiters(applet.getParameter(key));
+    }
+
+    public Iterator<String> getKeys()
+    {
+        String[][] paramsInfo = applet.getParameterInfo();
+        String[] keys = new String[paramsInfo != null ? paramsInfo.length : 0];
+        for (int i = 0; i < keys.length; i++)
+        {
+            keys[i] = paramsInfo[i][0];
+        }
+
+        return Arrays.asList(keys).iterator();
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/web/BaseWebConfiguration.java b/src/main/java/org/apache/commons/configuration/web/BaseWebConfiguration.java
new file mode 100644
index 0000000..49be510
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/web/BaseWebConfiguration.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.web;
+
+import java.util.List;
+
+import org.apache.commons.configuration.AbstractConfiguration;
+import org.apache.commons.configuration.PropertyConverter;
+
+/**
+ * <p>
+ * An abstract base class for all web configurations.
+ * </p>
+ * <p>
+ * This class implements common functionality used by all web based
+ * configurations. E.g. some methods are not supported by configurations of this
+ * type, so they throw a {@code UnsupportedOperationException} exception.
+ * </p>
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: BaseWebConfiguration.java 1211122 2011-12-06 20:55:45Z oheger $
+ * @since 1.2
+ */
+abstract class BaseWebConfiguration extends AbstractConfiguration
+{
+    /**
+     * Checks if this configuration is empty. This implementation makes use of
+     * the {@code getKeys()} method (which must be defined by concrete
+     * sub classes) to find out whether properties exist.
+     *
+     * @return a flag whether this configuration is empty
+     */
+    public boolean isEmpty()
+    {
+        return !getKeys().hasNext();
+    }
+
+    /**
+     * Checks whether the specified key is stored in this configuration.
+     *
+     * @param key the key
+     * @return a flag whether this key exists in this configuration
+     */
+    public boolean containsKey(String key)
+    {
+        return getProperty(key) != null;
+    }
+
+    /**
+     * Removes the property with the given key. <strong>This operation is not
+     * supported and will throw an UnsupportedOperationException.</strong>
+     *
+     * @param key the key of the property to be removed
+     * @throws UnsupportedOperationException because this operation is not
+     * allowed
+     */
+    @Override
+    public void clearProperty(String key)
+    {
+        throw new UnsupportedOperationException("Read only configuration");
+    }
+
+    /**
+     * Adds a property to this configuration. <strong>This operation is not
+     * supported and will throw an UnsupportedOperationException.</strong>
+     *
+     * @param key the key of the property
+     * @param obj the value to be added
+     * @throws UnsupportedOperationException because this operation is not
+     * allowed
+     */
+    @Override
+    protected void addPropertyDirect(String key, Object obj)
+    {
+        throw new UnsupportedOperationException("Read only configuration");
+    }
+
+    /**
+     * Takes care of list delimiters in property values. This method checks if
+     * delimiter parsing is enabled and the passed in value contains a delimiter
+     * character. If this is the case, a split operation is performed.
+     *
+     * @param value the property value to be examined
+     * @return the processed value
+     */
+    protected Object handleDelimiters(Object value)
+    {
+        if (!isDelimiterParsingDisabled() && value instanceof String)
+        {
+            List<String> list = PropertyConverter.split((String) value,
+                    getListDelimiter());
+            value = list.size() > 1 ? list : list.get(0);
+        }
+
+        return value;
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/web/ServletConfiguration.java b/src/main/java/org/apache/commons/configuration/web/ServletConfiguration.java
new file mode 100644
index 0000000..e1742d7
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/web/ServletConfiguration.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.web;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+
+/**
+ * A configuration wrapper around a {@link ServletConfig}. This configuration
+ * is read only, adding or removing a property will throw an
+ * UnsupportedOperationException.
+ *
+ * @author <a href="mailto:ebourg at apache.org">Emmanuel Bourg</a>
+ * @version $Id: ServletConfiguration.java 1211128 2011-12-06 20:56:41Z oheger $
+ * @since 1.1
+ */
+public class ServletConfiguration extends BaseWebConfiguration
+{
+    /** Stores a reference to the wrapped {@code ServletConfig}.*/
+    protected ServletConfig config;
+
+    /**
+     * Create a ServletConfiguration using the initialization parameter of
+     * the specified servlet.
+     *
+     * @param servlet the servlet
+     */
+    public ServletConfiguration(Servlet servlet)
+    {
+        this(servlet.getServletConfig());
+    }
+
+    /**
+     * Create a ServletConfiguration using the servlet initialization parameters.
+     *
+     * @param config the servlet configuration
+     */
+    public ServletConfiguration(ServletConfig config)
+    {
+        this.config = config;
+    }
+
+    public Object getProperty(String key)
+    {
+        return handleDelimiters(config.getInitParameter(key));
+    }
+
+    public Iterator<String> getKeys()
+    {
+        // According to the documentation of getInitParameterNames() the
+        // enumeration is of type String.
+        @SuppressWarnings("unchecked")
+        Enumeration<String> en = config.getInitParameterNames();
+        return Collections.list(en).iterator();
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/web/ServletContextConfiguration.java b/src/main/java/org/apache/commons/configuration/web/ServletContextConfiguration.java
new file mode 100644
index 0000000..e826e9e
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/web/ServletContextConfiguration.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.web;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+
+/**
+ * A configuration wrapper to read the initialization parameters of a servlet
+ * context. This configuration is read only, adding or removing a property will
+ * throw an UnsupportedOperationException.
+ *
+ * @author <a href="mailto:ebourg at apache.org">Emmanuel Bourg</a>
+ * @version $Id: ServletContextConfiguration.java 1211129 2011-12-06 20:57:01Z oheger $
+ * @since 1.1
+ */
+public class ServletContextConfiguration extends BaseWebConfiguration
+{
+    /** Stores the wrapped servlet context.*/
+    protected ServletContext context;
+
+    /**
+     * Create a ServletContextConfiguration using the context of
+     * the specified servlet.
+     *
+     * @param servlet the servlet
+     */
+    public ServletContextConfiguration(Servlet servlet)
+    {
+        this.context = servlet.getServletConfig().getServletContext();
+    }
+
+    /**
+     * Create a ServletContextConfiguration using the servlet context
+     * initialization parameters.
+     *
+     * @param context the servlet context
+     */
+    public ServletContextConfiguration(ServletContext context)
+    {
+        this.context = context;
+    }
+
+    public Object getProperty(String key)
+    {
+        return handleDelimiters(context.getInitParameter(key));
+    }
+
+    public Iterator<String> getKeys()
+    {
+        // According to the documentation of getInitParameterNames() the
+        // enumeration is of type String.
+        @SuppressWarnings("unchecked")
+        Enumeration<String> en = context.getInitParameterNames();
+        return Collections.list(en).iterator();
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/web/ServletFilterConfiguration.java b/src/main/java/org/apache/commons/configuration/web/ServletFilterConfiguration.java
new file mode 100644
index 0000000..b52a771
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/web/ServletFilterConfiguration.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.web;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+
+import javax.servlet.FilterConfig;
+
+/**
+ * A configuration wrapper around a {@link FilterConfig}. This configuration is
+ * read only, adding or removing a property will throw an
+ * UnsupportedOperationException.
+ *
+ * @author <a href="mailto:ebourg at apache.org">Emmanuel Bourg</a>
+ * @version $Id: ServletFilterConfiguration.java 1211130 2011-12-06 20:57:19Z oheger $
+ * @since 1.1
+ */
+public class ServletFilterConfiguration extends BaseWebConfiguration
+{
+    /** Stores the wrapped filter config.*/
+    protected FilterConfig config;
+
+    /**
+     * Create a ServletFilterConfiguration using the filter initialization parameters.
+     *
+     * @param config the filter configuration
+     */
+    public ServletFilterConfiguration(FilterConfig config)
+    {
+        this.config = config;
+    }
+
+    public Object getProperty(String key)
+    {
+        return handleDelimiters(config.getInitParameter(key));
+    }
+
+    public Iterator<String> getKeys()
+    {
+        // According to the documentation of getInitParameterNames() the
+        // enumeration is of type String.
+        @SuppressWarnings("unchecked")
+        Enumeration<String> en = config.getInitParameterNames();
+        return Collections.list(en).iterator();
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/web/ServletRequestConfiguration.java b/src/main/java/org/apache/commons/configuration/web/ServletRequestConfiguration.java
new file mode 100644
index 0000000..6c6a909
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/web/ServletRequestConfiguration.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.web;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletRequest;
+
+/**
+ * A configuration wrapper to read the parameters of a servlet request. This
+ * configuration is read only, adding or removing a property will throw an
+ * UnsupportedOperationException.
+ *
+ * @author <a href="mailto:ebourg at apache.org">Emmanuel Bourg</a>
+ * @version $Id: ServletRequestConfiguration.java 1211131 2011-12-06 20:57:37Z oheger $
+ * @since 1.1
+ */
+public class ServletRequestConfiguration extends BaseWebConfiguration
+{
+    /** Stores the wrapped request.*/
+    protected ServletRequest request;
+
+    /**
+     * Create a ServletRequestConfiguration using the request parameters.
+     *
+     * @param request the servlet request
+     */
+    public ServletRequestConfiguration(ServletRequest request)
+    {
+        this.request = request;
+    }
+
+    public Object getProperty(String key)
+    {
+        String[] values = request.getParameterValues(key);
+
+        if (values == null || values.length == 0)
+        {
+            return null;
+        }
+        else if (values.length == 1)
+        {
+            return handleDelimiters(values[0]);
+        }
+        else
+        {
+            // ensure that escape characters in all list elements are removed
+            List<Object> result = new ArrayList<Object>(values.length);
+            for (int i = 0; i < values.length; i++)
+            {
+                Object val = handleDelimiters(values[i]);
+                if (val instanceof Collection)
+                {
+                    result.addAll((Collection<?>) val);
+                }
+                else
+                {
+                    result.add(val);
+                }
+            }
+            return result;
+        }
+    }
+
+    public Iterator<String> getKeys()
+    {
+        // According to the documentation of getParameterMap(), keys are Strings.
+        @SuppressWarnings("unchecked")
+        Map<String, ?> parameterMap = request.getParameterMap();
+        return parameterMap.keySet().iterator();
+    }
+}
diff --git a/src/main/java/org/apache/commons/configuration/web/package.html b/src/main/java/org/apache/commons/configuration/web/package.html
new file mode 100644
index 0000000..744e7c4
--- /dev/null
+++ b/src/main/java/org/apache/commons/configuration/web/package.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<html>
+<head>
+</head>
+<body>
+
+<p>
+This package contains some implementations of the <code>Configuration</code>
+interface that are useful in web environments.
+</p>
+<p>
+<font size="-2">$Id: package.html 439648 2006-09-02 20:42:10Z oheger $</font>
+</p>
+
+</body>
+</html>
diff --git a/src/main/javacc/PropertyListParser.jj b/src/main/javacc/PropertyListParser.jj
new file mode 100644
index 0000000..eaf1567
--- /dev/null
+++ b/src/main/javacc/PropertyListParser.jj
@@ -0,0 +1,291 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+options {
+    STATIC = false;
+}
+
+
+PARSER_BEGIN(PropertyListParser)
+
+package org.apache.commons.configuration.plist;
+
+import java.util.Date;
+import java.util.List;
+import java.util.ArrayList;
+
+import org.apache.commons.configuration.HierarchicalConfiguration;
+import org.apache.commons.configuration.HierarchicalConfiguration.Node;
+
+import org.apache.commons.codec.binary.Hex;
+
+/**
+ * JavaCC based parser for the PropertyList format.
+ *
+ * @author Emmanuel Bourg
+ * @version $Revision$, $Date$
+ */
+class PropertyListParser {
+
+    /**
+     * Remove the quotes at the beginning and at the end of the specified String.
+     */
+    protected String removeQuotes(String s)
+    {
+        if (s == null)
+        {
+            return null;
+        }
+
+        if (s.startsWith("\"") && s.endsWith("\"") && s.length() >= 2)
+        {
+            s = s.substring(1, s.length() - 1);
+        }
+
+        return s;
+    }
+
+    protected String unescapeQuotes(String s)
+    {
+        return s.replaceAll("\\\\\"", "\"");
+    }
+
+    /**
+     * Remove the white spaces and the data delimiters from the specified
+     * string and parse it as a byte array.
+     */
+    protected byte[] filterData(String s) throws ParseException
+    {
+        if (s == null)
+        {
+            return null;
+        }
+
+        // remove the delimiters
+        if (s.startsWith("<") && s.endsWith(">") && s.length() >= 2)
+        {
+            s = s.substring(1, s.length() - 1);
+        }
+
+        // remove the white spaces
+        s = s.replaceAll("\\s", "");
+
+        // add a leading 0 to ensure well formed bytes
+        if (s.length() % 2 != 0)
+        {
+            s = "0" + s;
+        }
+
+        // parse and return the bytes
+        try
+        {
+            return Hex.decodeHex(s.toCharArray());
+        }
+        catch (Exception e)
+        {
+            throw (ParseException) new ParseException("Unable to parse the byte[] : " + e.getMessage());
+        }
+    }
+
+    /**
+     * Parse a date formatted as <*D2002-03-22 11:30:00 +0100>
+     */
+    protected Date parseDate(String s) throws ParseException
+    {
+        return PropertyListConfiguration.parseDate(s);
+    }
+
+}
+
+PARSER_END(PropertyListParser)
+
+SKIP : { " " | "\t" | "\n" | "\r" }
+
+// Handle comments
+MORE : { "/*": IN_COMMENT }
+< IN_COMMENT > MORE : { <  ~[] > }
+< IN_COMMENT > SKIP : { "*/": DEFAULT } 
+
+MORE : { "//": IN_SINGLE_LINE_COMMENT }
+< IN_SINGLE_LINE_COMMENT > SPECIAL_TOKEN : {
+    < SINGLE_LINE_COMMENT: "\n"|"\r"|"\r\n" > : DEFAULT }
+< IN_SINGLE_LINE_COMMENT > MORE : { <  ~[] > } 
+
+TOKEN : { <ARRAY_BEGIN     : "(" > }
+TOKEN : { <ARRAY_END       : ")" > }
+TOKEN : { <ARRAY_SEPARATOR : "," > }
+
+TOKEN : { <DICT_BEGIN     : "{" > }
+TOKEN : { <DICT_END       : "}" > }
+TOKEN : { <DICT_SEPARATOR : ";" > }
+TOKEN : { <EQUAL : "=" > }
+
+TOKEN : { <DATA_START : "<" > }
+TOKEN : { <DATA_END : ">" > }
+
+TOKEN : { <DATE_START : "<*D" > }
+
+TOKEN : { < QUOTE : "\"" > }
+TOKEN : { < #LETTER : ~[" ", "\t", "\n", "\r", "(", ")", ",", "{", "}", ";", "=", "\""] > }
+TOKEN : { < #WHITE : " " | "\t" | "\n" | "\r" > }
+TOKEN : { < #HEXA : ["0"-"9", "a"-"f", "A"-"F"] > }
+TOKEN : { < DATA : <DATA_START> (<HEXA> | <WHITE>)* <DATA_END> > }
+TOKEN : { < DATE : <DATE_START> (["0"-"9"] | ":" | " " | "+" | "-" | "Z")* <DATA_END> > }
+TOKEN : { < STRING :  (<LETTER>)+ > }
+TOKEN : { < QUOTED_STRING :
+            <QUOTE>
+            (<LETTER> | <WHITE> | <ESCAPED_QUOTE> | <EQUAL>
+            | <ARRAY_BEGIN> | <ARRAY_END> | <ARRAY_SEPARATOR>
+            | <DICT_BEGIN> | <DICT_END> | <DICT_SEPARATOR>)* <QUOTE> > }
+TOKEN : { < ESCAPED_QUOTE : "\\\"" > }
+
+PropertyListConfiguration parse() :
+{
+    PropertyListConfiguration configuration = null;
+}
+{
+    configuration = Dictionary()
+    <EOF>
+    { return configuration; }
+}
+
+PropertyListConfiguration Dictionary() :
+{
+    PropertyListConfiguration configuration = new PropertyListConfiguration();
+    List<Node> children = new ArrayList<Node>();
+    Node child = null;
+}
+{
+    <DICT_BEGIN>
+    (
+        child = Property()
+        {
+            if (child.getValue() instanceof HierarchicalConfiguration)
+            {
+                // prune & graft the nested configuration to the parent configuration
+                HierarchicalConfiguration conf = (HierarchicalConfiguration) child.getValue();
+                Node root = conf.getRoot();
+                root.setName(child.getName());
+                children.add(root);
+            }
+            else
+            {
+                children.add(child);
+            }
+        }
+    )*
+    <DICT_END>
+    {
+        for (int i = 0; i < children.size(); i++)
+        {
+            child = children.get(i);
+            configuration.getRoot().addChild(child);
+        }
+
+        return configuration;
+    }
+}
+
+Node Property() :
+{
+    String key = null;
+    Object value = null;
+    Node node = new Node();
+}
+{
+    key = String()
+    { node.setName(key); }
+    <EQUAL>
+    value = Element()
+    { node.setValue(value); }
+    (<DICT_SEPARATOR>)?
+    { return node; }
+}
+
+Object Element() :
+{
+    Object value = null;
+}
+{
+    LOOKAHEAD(2)
+    
+    value = Array()
+    { return value; }
+    |
+    value = Dictionary()
+    { return value; }
+    |
+    value = String()
+    { return value; }
+    |
+    value = Data()
+    { return value; }
+    |
+    value = Date()
+    { return value; }
+}
+
+List Array() :
+{
+    List<Object> list = new ArrayList<Object>();
+    Object element = null;
+}
+{
+    <ARRAY_BEGIN>
+    (
+        element = Element()
+        { list.add(element); }
+        (
+            <ARRAY_SEPARATOR>
+            element = Element()
+            { list.add(element); }
+        )*
+    )?
+    <ARRAY_END>
+    { return list; }
+}
+
+String String() :
+{
+    Token token = null;
+    String value = null;
+}
+{
+    token = <QUOTED_STRING>
+    { return unescapeQuotes(removeQuotes(token.image)); }
+    |
+    token = <STRING>
+    { return token.image; }
+}
+
+byte[] Data() :
+{
+    Token token;
+}
+{
+    token = <DATA>
+    { return filterData(token.image); }
+}
+
+Date Date() :
+{
+    Token token;
+}
+{
+    token = <DATE>
+    { return parseDate(token.image); }
+}
diff --git a/src/main/resources/PropertyList-1.0.dtd b/src/main/resources/PropertyList-1.0.dtd
new file mode 100644
index 0000000..5924f4d
--- /dev/null
+++ b/src/main/resources/PropertyList-1.0.dtd
@@ -0,0 +1,35 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!ENTITY % plistObject "(array | data | date | dict | real | integer | string | true | false )" >
+<!ELEMENT plist %plistObject;>
+<!ATTLIST plist version CDATA "1.0" >
+
+<!-- Collections -->
+<!ELEMENT array (%plistObject;)*>
+<!ELEMENT dict (key, %plistObject;)*>
+<!ELEMENT key (#PCDATA)>
+
+<!--- Primitive types -->
+<!ELEMENT string (#PCDATA)>
+<!ELEMENT data (#PCDATA)> <!-- Contents interpreted as Base-64 encoded -->
+<!ELEMENT date (#PCDATA)> <!-- Contents should conform to a subset of ISO 8601 (in particular, YYYY '-' MM '-' DD 'T' HH ':' MM ':' SS 'Z'.  Smaller units may be omitted with a loss of precision) -->
+
+<!-- Numerical primitives -->
+<!ELEMENT true EMPTY>  <!-- Boolean constant true -->
+<!ELEMENT false EMPTY> <!-- Boolean constant false -->
+<!ELEMENT real (#PCDATA)> <!-- Contents should represent a floating point number matching ("+" | "-")? d+ ("."d*)? ("E" ("+" | "-") d+)? where d is a digit 0-9.  -->
+<!ELEMENT integer (#PCDATA)> <!-- Contents should represent a (possibly signed) integer number in base 10 -->
diff --git a/src/main/resources/digesterRules.xml b/src/main/resources/digesterRules.xml
new file mode 100644
index 0000000..fe23648
--- /dev/null
+++ b/src/main/resources/digesterRules.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<!DOCTYPE digester-rules PUBLIC "-//Jakarta Apache //DTD digester-rules XML V1.0//EN" "http://jakarta.apache.org/commons/digester/dtds/digester-rules.dtd">
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<digester-rules>
+
+    <pattern value="configuration/properties">
+        <object-create-rule classname="org.apache.commons.configuration.PropertiesConfiguration"/>
+        <set-properties-rule/>
+        <set-next-rule methodname="addConfiguration" paramtype="org.apache.commons.configuration.Configuration"/>
+        <call-method-rule methodname="load"/>
+    </pattern>
+
+    <pattern value="configuration/xml">
+        <object-create-rule classname="org.apache.commons.configuration.XMLConfiguration"/>
+        <set-properties-rule/>
+        <set-next-rule methodname="addConfiguration" paramtype="org.apache.commons.configuration.Configuration"/>
+        <call-method-rule methodname="load"/>
+    </pattern>
+
+    <pattern value="configuration/jndi">
+        <object-create-rule classname="org.apache.commons.configuration.JNDIConfiguration"/>
+        <set-properties-rule/>
+        <set-next-rule methodname="addConfiguration" paramtype="org.apache.commons.configuration.Configuration"/>
+    </pattern>
+
+</digester-rules>
diff --git a/src/main/resources/properties.dtd b/src/main/resources/properties.dtd
new file mode 100644
index 0000000..9d2040f
--- /dev/null
+++ b/src/main/resources/properties.dtd
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<!-- DTD for properties -->
+
+<!ELEMENT properties ( comment?, entry* ) >
+
+<!ATTLIST properties version CDATA #FIXED "1.0">
+
+<!ELEMENT comment (#PCDATA)>
+
+<!ELEMENT entry (#PCDATA)>
+
+<!ATTLIST entry key CDATA #REQUIRED>
diff --git a/src/site/resources/images/logo.png b/src/site/resources/images/logo.png
new file mode 100644
index 0000000..ee0a162
Binary files /dev/null and b/src/site/resources/images/logo.png differ
diff --git a/src/site/resources/images/logo.xcf b/src/site/resources/images/logo.xcf
new file mode 100644
index 0000000..ed5e17d
Binary files /dev/null and b/src/site/resources/images/logo.xcf differ
diff --git a/src/site/resources/profile.cobertura b/src/site/resources/profile.cobertura
new file mode 100644
index 0000000..e69de29
diff --git a/src/site/site.xml b/src/site/site.xml
new file mode 100644
index 0000000..477c29e
--- /dev/null
+++ b/src/site/site.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<project name="Commons Configuration">
+  <bannerRight>
+    <name>Commons Configuration</name>
+    <src>/images/logo.png</src>
+    <href>/index.html</href>
+  </bannerRight>
+
+  <body>
+    <menu name="Configuration">
+      <item name="Home"                         href="/index.html"/>  
+      <item name="Download"                     href="http://commons.apache.org/configuration/download_configuration.cgi"/>  
+      <item name="Release History"              href="/changes-report.html"/>
+      <item name="User's Guide"                 href="/userguide/user_guide.html"/>
+      <item name="Runtime Dependencies"         href="/dependencies.html"/>
+      <item name="Javadoc"                      href="/apidocs/index.html"/>
+    </menu>
+
+    <menu name="Development">
+      <item name="Building"                     href="/building.html"/>
+      <item name="Issue Tracking"               href="issue-tracking.html"/>
+    </menu>
+
+  </body>
+
+</project>
diff --git a/src/site/xdoc/building.xml b/src/site/xdoc/building.xml
new file mode 100644
index 0000000..8660ebc
--- /dev/null
+++ b/src/site/xdoc/building.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- ===================================================================== -->
+<!-- $Id: building.xml 1197981 2011-11-05 16:10:56Z oheger $ -->
+<!-- ===================================================================== -->
+
+<document>
+
+    <properties>
+        <title>Building</title>
+    </properties>
+
+    <body>
+
+        <section name="Overview">
+          <p>
+            Commons Configuration can be built using
+            <a href="http://maven.apache.org">maven</a> or
+            <a href="http://ant.apache.org">ant</a>.
+          </p>
+        </section>
+
+        <section name="Maven 2">
+          <p>
+            Commons Configuration uses Maven 2 as its build tool. The recommended
+            version is Maven 2.2.1. To build the Configuration
+            jar, change into the directory where the source distribution resides and run
+            "mvn install". This will compile the source and tests, run the tests, and then
+            package the jar. The jar will also be copied into the local maven repository
+            for use by other builds.
+          </p>
+          <p>
+            This build requires a JDK 1.5 or higher. It is possible to build
+            Commons Configuration on a JDK 1.4 by specifying the "java-1.4"
+            profile: "mvn install -Pjava-1.4". This profile and the settings required are described in
+            detail in <a href="http://commons.apache.org/commons-parent-pom.html#Testing_with_different_Java_versions">
+            Testing with different Java versions</a>. If this profile is active,
+            some classes depending on Commons VFS (which is available for Java
+            1.5+ only) are excluded from compilation.
+          </p>
+          <p>
+            To build the web site run "mvn site". When it completes the web site will reside in
+            the target/site directory and may be viewed by opening target/site/index.html.
+          </p>
+        </section>
+        <section name="Running Functional Tests">
+          <p>
+            <code>TestWebdavConfigurationBuilder</code> is a functional test that tests
+            DefaultConfigurationBuilder with the configuration files stored in a WebDAV
+            server. To run the test the steps that follow
+            <ol>
+              <li>Copy all the files in the conf directory to the WebDAV server. Not all the
+              files are required but it is generally easier to use a tool like cadaver
+              and do an mput than try to copy the required files individually.</li>
+              <li>Add the following to the profiles section of settings.xml in the Maven home
+              directory. Modify the urls to match your setup.
+<source><![CDATA[
+   <profile>
+    <profile>
+      <id>webdav</id>
+      <activation>
+        <activeByDefault>false</activeByDefault>
+      </activation>
+      <properties>
+        <test.webdav.base>webdav://vfsusr:vfstest@192.168.10.133:80/conf</test.webdav.base>
+      </properties>
+    </profile>]]></source></li>
+              <li>run "mvn -P webdav test -Dtest=TestWebdavConfigurationBuilder". The test
+              can also be run using "mvn -P webdav test" but this will run all the unit tests
+              in addition to the WebDAV test.</li>
+            </ol>
+          </p>
+        </section>
+
+        <section name="Building with Ant">
+          <p>
+            In order to build the project with Apache Ant, some manual preparations
+            have to be done. The problem is that Commons Configuration uses
+            classes generated by <a href="http://javacc.java.net/">JavaCC</a>
+            to process configuration files in specific formats. These classes
+            are produced dynamically during the build process. While Maven is
+            able to download all required artifacts automatically, the Ant build
+            is not that smart. Therefore, it is required to download and install
+            JavaCC manually. Then a file named <code>build.properties</code> has
+            to be created in the root directory of Commons Configuration which
+            defines the <em>javacc_home</em> property; here the path to the
+            JavaCC installation has to be set. Commons Configuration already
+            ships with a <code>build.properties.sample</code> file which can be
+            copied and adapted correspondingly.
+          </p>
+          <p>
+            After these preparations have been done, the following Ant goals
+            can be used:
+            <ul>
+              <li>To build a jar file, change into Configuration's root directory
+              and run "ant jar". The result will be in the "target" subdirectory.
+              </li>
+              <li>To build the Javadocs, run "ant javadoc". The result will be
+              in "target/site/apidocs".</li>
+            </ul>
+          </p>
+        </section>
+    </body>
+
+</document>
diff --git a/src/site/xdoc/dependencies.xml b/src/site/xdoc/dependencies.xml
new file mode 100644
index 0000000..b49bc0d
--- /dev/null
+++ b/src/site/xdoc/dependencies.xml
@@ -0,0 +1,173 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- ===================================================================== -->
+<!-- $Id: dependencies.xml 1368230 2012-08-01 20:08:27Z oheger $ -->
+<!-- ===================================================================== -->
+
+<document>
+
+    <properties>
+        <title>Runtime dependencies</title>
+    </properties>
+
+    <body>
+
+        <section name="Runtime dependencies">
+
+            <p>
+              Commons Configuration requires Java 5 or later.
+            </p>
+            <p>
+              A lot of dependencies are declared in the Maven POM. These are all
+              needed during compile time. On runtime however you only need to
+              add the dependencies to your classpath that are required by the
+              parts of the Commons Configuration package you are using. The
+              following table helps you to determine which dependencies you
+              have to include based on the components you intend to use:
+            </p>
+
+            <table>
+                <thead>
+                    <tr>
+                        <th width="30%">Component</th>
+                        <th>Dependencies</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tr>
+                        <td>Core</td>
+                        <td>
+                            commons-lang<br/>
+                            commons-logging
+                        </td>
+                    </tr>
+                    <tr>
+                        <td>DefaultConfigurationBuilder</td>
+                        <td>commons-beanutils</td>
+                    </tr>
+                    <tr>
+                        <td>ConfigurationFactory (deprecated)</td>
+                        <td>commons-digester</td>
+                    </tr>
+                    <tr>
+                        <td>ConfigurationConverter</td>
+                        <td>commons-collections</td>
+                    </tr>
+                    <tr>
+                        <td>
+                          PropertyListConfiguration<br/>
+                          XMLPropertyListConfiguration
+                        </td>
+                        <td>commons-codec</td>
+                    </tr>
+                    <tr>
+                        <td>ConfigurationDynaBean</td>
+                        <td>commons-beanutils</td>
+                    </tr>
+                    <tr>
+                        <td>XPathExpressionEngine</td>
+                        <td>commons-jxpath</td>
+                    </tr>
+                    <tr>
+                        <td>CatalogResolver</td>
+                        <td>xml-resolver</td>
+                    </tr>
+                    <tr>
+                        <td>Web configurations</td>
+                        <td>servlet-api</td>
+                    </tr>
+                    <tr>
+                        <td>ExprLookup</td>
+                        <td>commons-jexl</td>
+                    </tr>
+                    <tr>
+                        <td>VFSFileSystem, VFSFileChangedReloadingStrategy</td>
+                        <td>commons-vfs</td>
+                    </tr>
+                </tbody>
+            </table>
+
+            <p>
+              <strong>Notes</strong>
+            </p>
+            <p>
+              <ul>
+                <li>Commons Configuration makes use of other
+                Commons components. You should be able to use the current
+                versions of these components together with Commons Configuration.
+                In some cases, when no specific features are used, older
+                versions will work, too. Below is a table with the version
+                numbers that have been tested:
+                <table>
+                  <thead>
+                    <tr>
+                      <th>Component</th>
+                      <th>Version</th>
+                    </tr>
+                  </thead>
+                  <tbody>
+                    <tr>
+                      <td>commons-lang</td>
+                      <td>2.2, 2.3, 2.4, 2.5, 2.6</td>
+                    </tr>
+                    <tr>
+                      <td>commons-collections</td>
+                      <td>3.1, 3.2, 3.2.1</td>
+                    </tr>
+                    <tr>
+                      <td>commons-logging</td>
+                      <td>1.0.4, 1.1, 1.1.1</td>
+                    </tr>
+                    <tr>
+                      <td>commons-digester</td>
+                      <td>1.6, 1.7, 1.8, 1.8.1</td>
+                    </tr>
+                    <tr>
+                      <td>commons-beanutils</td>
+                      <td>1.7.0, 1.8.0, 1.8.2, 1.8.3</td>
+                    </tr>
+                    <tr>
+                      <td>commons-codec</td>
+                      <td>1.3, 1.5, 1.6</td>
+                    </tr>
+                    <tr>
+                      <td>commons-jxpath</td>
+                      <td>1.2, 1.3</td>
+                    </tr>
+                    <tr>
+                      <td>commons-jexl</td>
+                      <td>2.1.1</td>
+                    </tr>
+                    <tr>
+                      <td>commons-vfs</td>
+                      <td>2.0</td>
+                    </tr>
+                    <tr>
+                      <td>xml-resolver</td>
+                      <td>1.2</td>
+                    </tr>
+                  </tbody>
+                </table>
+                </li>
+              </ul>
+            </p>
+        </section>
+
+    </body>
+
+</document>
diff --git a/src/site/xdoc/download_configuration.xml b/src/site/xdoc/download_configuration.xml
new file mode 100644
index 0000000..ea36ab5
--- /dev/null
+++ b/src/site/xdoc/download_configuration.xml
@@ -0,0 +1,138 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<!--
+ +======================================================================+
+ |****                                                              ****|
+ |****      THIS FILE IS GENERATED BY THE COMMONS BUILD PLUGIN      ****|
+ |****                    DO NOT EDIT DIRECTLY                      ****|
+ |****                                                              ****|
+ +======================================================================+
+ | TEMPLATE FILE: download-page-template.xml                            |
+ | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates |
+ +======================================================================+
+ |                                                                      |
+ | 1) Re-generate using: mvn commons:download-page                      |
+ |                                                                      |
+ | 2) Set the following properties in the component's pom:              |
+ |    - commons.componentid (required, alphabetic, lower case)          |
+ |    - commons.release.version (required)                              |
+ |    - commons.binary.suffix (optional)                                |
+ |      (defaults to "-bin", set to "" for pre-maven2 releases)         |
+ |                                                                      |
+ | 3) Example Properties                                                |
+ |                                                                      |
+ |  <properties>                                                        |
+ |    <commons.componentid>math</commons.componentid>                   |
+ |    <commons.release.version>1.2</commons.release.version>            |
+ |  </properties>                                                       |
+ |                                                                      |
+ +======================================================================+
+-->
+<document>
+  <properties>
+    <title>Download Commons Configuration</title>
+    <author email="dev at commons.apache.org">Commons Documentation Team</author>
+  </properties>
+  <body>
+    <section name="Download Commons Configuration">
+    <subsection name="Using a Mirror">
+      <p>
+        We recommend you use a mirror to download our release
+        builds, but you <strong>must</strong> verify the integrity of
+        the downloaded files using signatures downloaded from our main
+        distribution directories. Recent releases (48 hours) may not yet
+        be available from the mirrors.
+      </p>
+
+      <p>
+        You are currently using <b>[preferred]</b>.  If you
+        encounter a problem with this mirror, please select another
+        mirror.  If all mirrors are failing, there are <i>backup</i>
+        mirrors (at the end of the mirrors list) that should be
+        available.
+        <br></br>
+        [if-any logo]<a href="[link]"><img align="right" src="[logo]" border="0"></img></a>[end]
+      </p>
+
+      <form action="[location]" method="get" id="SelectMirror">
+        <p>
+          Other mirrors:
+          <select name="Preferred">
+          [if-any http]
+            [for http]<option value="[http]">[http]</option>[end]
+          [end]
+          [if-any ftp]
+            [for ftp]<option value="[ftp]">[ftp]</option>[end]
+          [end]
+          [if-any backup]
+            [for backup]<option value="[backup]">[backup] (backup)</option>[end]
+          [end]
+          </select>
+          <input type="submit" value="Change"></input>
+        </p>
+      </form>
+
+      <p>
+        The <a href="http://www.apache.org/dist/commons/KEYS">KEYS</a>
+        link links to the code signing keys used to sign the product.
+        The <code>PGP</code> link downloads the OpenPGP compatible signature from our main site.
+        The <code>MD5</code> link downloads the checksum from the main site.
+      </p>
+    </subsection>
+    </section>
+    <section name="Commons Configuration 1.10 ">
+      <subsection name="Binaries">
+        <table>
+          <tr>
+              <td><a href="[preferred]/commons/configuration/binaries/commons-configuration-1.10-bin.tar.gz">commons-configuration-1.10-bin.tar.gz</a></td>
+              <td><a href="http://www.apache.org/dist/commons/configuration/binaries/commons-configuration-1.10-bin.tar.gz.md5">md5</a></td>
+              <td><a href="http://www.apache.org/dist/commons/configuration/binaries/commons-configuration-1.10-bin.tar.gz.asc">pgp</a></td>
+          </tr>
+          <tr>
+              <td><a href="[preferred]/commons/configuration/binaries/commons-configuration-1.10-bin.zip">commons-configuration-1.10-bin.zip</a></td>
+              <td><a href="http://www.apache.org/dist/commons/configuration/binaries/commons-configuration-1.10-bin.zip.md5">md5</a></td>
+              <td><a href="http://www.apache.org/dist/commons/configuration/binaries/commons-configuration-1.10-bin.zip.asc">pgp</a></td>
+          </tr>
+        </table>
+      </subsection>
+      <subsection name="Source">
+        <table>
+          <tr>
+              <td><a href="[preferred]/commons/configuration/source/commons-configuration-1.10-src.tar.gz">commons-configuration-1.10-src.tar.gz</a></td>
+              <td><a href="http://www.apache.org/dist/commons/configuration/source/commons-configuration-1.10-src.tar.gz.md5">md5</a></td>
+              <td><a href="http://www.apache.org/dist/commons/configuration/source/commons-configuration-1.10-src.tar.gz.asc">pgp</a></td>
+          </tr>
+          <tr>
+              <td><a href="[preferred]/commons/configuration/source/commons-configuration-1.10-src.zip">commons-configuration-1.10-src.zip</a></td>
+              <td><a href="http://www.apache.org/dist/commons/configuration/source/commons-configuration-1.10-src.zip.md5">md5</a></td>
+              <td><a href="http://www.apache.org/dist/commons/configuration/source/commons-configuration-1.10-src.zip.asc">pgp</a></td>
+          </tr>
+        </table>
+      </subsection>
+    </section>
+    <section name="Archives">
+        <p>
+          Older releases can be obtained from the archives.
+        </p>
+        <ul>
+          <li class="download"><a href="[preferred]/commons/configuration/">browse download area</a></li>
+          <li><a href="http://archive.apache.org/dist/commons/configuration/">archives...</a></li>
+        </ul>
+    </section>
+  </body>
+</document>
diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml
new file mode 100644
index 0000000..0aa8893
--- /dev/null
+++ b/src/site/xdoc/index.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- ===================================================================== -->
+<!-- $Id: index.xml 1164913 2011-09-03 19:16:04Z oheger $ -->
+<!-- ===================================================================== -->
+<document>
+
+  <properties>
+    <author email="pete at kazmier.com">Pete Kazmier</author>
+    <author email="mpoeschl at marmot.at">Martin Poeschl</author>
+    <author email="jason at zenplex.com">Jason van Zyl</author>
+    <author email="epugh at upstate.com">Eric Pugh</author>
+    <author email="tobrien at discursive.com">Tim O'Brien</author>
+    <author email="rgoers at apache.org">Ralph Goers</author>
+    <title>Java Configuration API</title>
+  </properties>
+
+  <body>
+    <section name="Intro">
+      <p>
+      	The Commons Configuration software library provides a generic configuration interface which enables
+        a Java application to read configuration data from a variety of sources. Commons Configuration
+      	provides typed access to single, and multi-valued configuration parameters as demonstrated
+        by the following code:
+
+<source><![CDATA[
+Double double = config.getDouble("number");
+Integer integer = config.getInteger("number");
+]]></source>
+
+      </p>
+      <p>
+		Configuration parameters may be loaded from the following sources:
+
+        <ul>
+          <li>Properties files</li>
+          <li>XML documents</li>
+          <li>Windows INI files</li>
+          <li>Property list files (plist)</li>
+          <li>JNDI</li>
+          <li>JDBC Datasource</li>
+          <li>System properties</li>
+          <li>Applet parameters</li>
+          <li>Servlet parameters</li>
+        </ul>
+
+        Different configuration sources can be mixed using a <code>ConfigurationFactory</code> and
+		a <code>CompositeConfiguration</code>. Additional sources of configuration parameters can
+        be created by using custom configuration objects. This customization can be achieved by
+		extending <code>AbstractConfiguration</code> or <code>AbstractFileConfiguration</code>.
+      </p>
+	  <p>
+		The full Javadoc API documentation is available <a href="apidocs/index.html">here</a>.
+	  </p>
+    </section>  	
+    
+    <section name="Latest Release">
+      <p>
+        The latest release of Apache Commons Configuration is available from the
+        <a href="http://commons.apache.org/configuration/download_configuration.cgi">Apache download area</a>. 
+        It is also available from the <a href="http://repo1.maven.org/maven2/commons-configuration/commons-configuration/">Maven repository</a>.
+        The <a href="changes-report.html">Changes Report</a> explains all of the changes and bug fixes that have been made.
+      </p>
+    </section>
+    
+    <section name="History">
+      <p>
+        Commons Configuration started as code in Apache JServ.  The JServ code was subsequently
+        added to <a href="http://jakarta.apache.org/turbine">Jakarta Turbine</a>. After Jakarta
+        Turbine, this configuration interface moved to <a href="http://jakarta.apache.org/velocity">Jakarta Velocity</a>
+        and underwent various improvements.  After Velocity, this code was introduced to the
+        <a href="http://commons.apache.org">Apache Commons</a> as <code>ExtendedProperties</code>.
+        Configuration began life in the Commons as a Sandbox component, and was promoted to the
+        Commons Proper in late 2003.
+      </p>
+    </section>
+
+    <section name="Bugs">
+      <p>
+        Bugs may be reported via the <a href="http://issues.apache.org/jira/browse/CONFIGURATION">ASF JIRA</a>
+        system. Detailed information can be found on the <a href="issue-tracking.html">issue tracking page</a>.
+      </p>
+    </section>
+
+  </body>
+</document>
diff --git a/src/site/xdoc/issue-tracking.xml b/src/site/xdoc/issue-tracking.xml
new file mode 100644
index 0000000..0542086
--- /dev/null
+++ b/src/site/xdoc/issue-tracking.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<!--
+ +======================================================================+
+ |****                                                              ****|
+ |****      THIS FILE IS GENERATED BY THE COMMONS BUILD PLUGIN      ****|
+ |****                    DO NOT EDIT DIRECTLY                      ****|
+ |****                                                              ****|
+ +======================================================================+
+ | TEMPLATE FILE: issue-tracking-template.xml                           |
+ | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates |
+ +======================================================================+
+ |                                                                      |
+ | 1) Re-generate using: mvn commons:jira-page                          |
+ |                                                                      |
+ | 2) Set the following properties in the component's pom:              |
+ |    - commons.jira.id  (required, alphabetic, upper case)             |
+ |    - commons.jira.pid (required, numeric)                            |
+ |                                                                      |
+ | 3) Example Properties                                                |
+ |                                                                      |
+ |  <properties>                                                        |
+ |    <commons.jira.id>MATH</commons.jira.id>                           |
+ |    <commons.jira.pid>12310485</commons.jira.pid>                     |
+ |  </properties>                                                       |
+ |                                                                      |
+ +======================================================================+
+-->
+<document>
+  <properties>
+    <title>Commons Configuration Issue tracking</title>
+    <author email="dev at commons.apache.org">Commons Documentation Team</author>
+  </properties>
+  <body>
+
+    <section name="Commons Configuration Issue tracking">
+      <p>
+      Commons Configuration uses <a href="http://issues.apache.org/jira/">ASF JIRA</a> for tracking issues.
+      See the <a href="http://issues.apache.org/jira/browse/CONFIGURATION">Commons Configuration JIRA project page</a>.
+      </p>
+
+      <p>
+      To use JIRA you may need to <a href="http://issues.apache.org/jira/secure/Signup!default.jspa">create an account</a>
+      (if you have previously created/updated Commons issues using Bugzilla an account will have been automatically
+      created and you can use the <a href="http://issues.apache.org/jira/secure/ForgotPassword!default.jspa">Forgot Password</a>
+      page to get a new password).
+      </p>
+
+      <p>
+      If you would like to report a bug, or raise an enhancement request with
+      Commons Configuration please do the following:
+      <ol>
+        <li><a href="http://issues.apache.org/jira/secure/IssueNavigator.jspa?reset=true&pid=12310467&sorter/field=issuekey&sorter/order=DESC&status=1&status=3&status=4">Search existing open bugs</a>.
+            If you find your issue listed then please add a comment with your details.</li>
+        <li><a href="mail-lists.html">Search the mailing list archive(s)</a>.
+            You may find your issue or idea has already been discussed.</li>
+        <li>Decide if your issue is a bug or an enhancement.</li>
+        <li>Submit either a <a href="http://issues.apache.org/jira/secure/CreateIssueDetails!init.jspa?pid=12310467&issuetype=1&priority=4&assignee=-1">bug report</a>
+            or <a href="http://issues.apache.org/jira/secure/CreateIssueDetails!init.jspa?pid=12310467&issuetype=4&priority=4&assignee=-1">enhancement request</a>.</li>
+      </ol>
+      </p>
+
+      <p>
+      Please also remember these points:
+      <ul>
+        <li>the more information you provide, the better we can help you</li>
+        <li>test cases are vital, particularly for any proposed enhancements</li>
+        <li>the developers of Commons Configuration are all unpaid volunteers</li>
+      </ul>
+      </p>
+
+      <p>
+      For more information on subversion and creating patches see the
+      <a href="http://www.apache.org/dev/contributors.html">Apache Contributors Guide</a>.
+      </p>
+
+      <p>
+      You may also find these links useful:
+      <ul>
+        <li><a href="http://issues.apache.org/jira/secure/IssueNavigator.jspa?reset=true&pid=12310467&sorter/field=issuekey&sorter/order=DESC&status=1&status=3&status=4">All Open Commons Configuration bugs</a></li>
+        <li><a href="http://issues.apache.org/jira/secure/IssueNavigator.jspa?reset=true&pid=12310467&sorter/field=issuekey&sorter/order=DESC&status=5&status=6">All Resolved Commons Configuration bugs</a></li>
+        <li><a href="http://issues.apache.org/jira/secure/IssueNavigator.jspa?reset=true&pid=12310467&sorter/field=issuekey&sorter/order=DESC">All Commons Configuration bugs</a></li>
+      </ul>
+      </p>
+    </section>
+  </body>
+</document>
diff --git a/src/site/xdoc/javadocarchive.xml b/src/site/xdoc/javadocarchive.xml
new file mode 100644
index 0000000..79016b1
--- /dev/null
+++ b/src/site/xdoc/javadocarchive.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<document>
+  <properties>
+    <title>Javadoc Archives</title>
+  </properties>
+  <body>
+
+    <section name="Javadoc Archives">
+
+      <p>Commons Configuration 1.7 (<a href="apidocs_1.7">javadoc</a>)</p>
+      
+      <p>Commons Configuration 1.6 (<a href="apidocs_1.6">javadoc</a>)</p>
+      
+      <p>Commons Configuration 1.5 (<a href="apidocs_1.5">javadoc</a>)</p>
+
+      <p>Commons Configuration 1.4 (<a href="apidocs_1.4">javadoc</a>)</p>
+
+      <p>Commons Configuration 1.3 (<a href="apidocs_1.3">javadoc</a>)</p>
+
+      <p>Commons Configuration 1.2 (<a href="apidocs_1.2">javadoc</a>)</p>
+
+      <p>Commons Configuration 1.1 (<a href="apidocs_1.1">javadoc</a>)</p>
+
+      <p>Commons Configuration 1.0 (<a href="apidocs_1.0">javadoc</a>)</p>
+
+    </section>
+
+  </body>
+</document>
diff --git a/src/site/xdoc/mail-lists.xml b/src/site/xdoc/mail-lists.xml
new file mode 100644
index 0000000..3b64284
--- /dev/null
+++ b/src/site/xdoc/mail-lists.xml
@@ -0,0 +1,202 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<!--
+ +======================================================================+
+ |****                                                              ****|
+ |****      THIS FILE IS GENERATED BY THE COMMONS BUILD PLUGIN      ****|
+ |****                    DO NOT EDIT DIRECTLY                      ****|
+ |****                                                              ****|
+ +======================================================================+
+ | TEMPLATE FILE: mail-lists-template.xml                               |
+ | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates |
+ +======================================================================+
+ |                                                                      |
+ | 1) Re-generate using: mvn commons:mail-page                          |
+ |                                                                      |
+ | 2) Set the following properties in the component's pom:              |
+ |    - commons.componentid (required, alphabetic, lower case)          |
+ |                                                                      |
+ | 3) Example Properties                                                |
+ |                                                                      |
+ |  <properties>                                                        |
+ |    <commons.componentid>math</commons.componentid>                   |
+ |  </properties>                                                       |
+ |                                                                      |
+ +======================================================================+
+-->
+<document>
+  <properties>
+    <title>Commons Configuration Mailing Lists</title>
+    <author email="dev at commons.apache.org">Commons Documentation Team</author>
+  </properties>
+  <body>
+
+    <section name="Overview">
+      <p>
+        <a href="index.html">Commons Configuration</a> shares mailing lists with all the other 
+        <a href="http://commons.apache.org/components.html">Commons Components</a>.
+        To make it easier for people to only read messages related to components they are interested in,
+        the convention in Commons is to prefix the subject line of messages with the component's name,
+        for example:
+        <ul>
+          <li>[configuration] Problem with the ...</li>
+        </ul>
+      </p>
+      <p>
+        Questions related to the usage of Commons Configuration should be posted to the
+        <a href="http://mail-archives.apache.org/mod_mbox/commons-user/">User List</a>.
+        <br />
+        The <a href="http://mail-archives.apache.org/mod_mbox/commons-dev/">Developer List</a>
+        is for questions and discussion related to the development of Commons Configuration.
+        <br />
+        Please do not cross-post; developers are also subscribed to the user list.
+      </p>
+      <p>
+        <strong>Note:</strong> please don't send patches or attachments to any of the mailing lists.
+        Patches are best handled via the <a href="issue-tracking.html">Issue Tracking</a> system. 
+        Otherwise, please upload the file to a public server and include the URL in the mail. 
+      </p>
+    </section>
+
+    <section name="Commons Configuration Mailing Lists">
+      <p>
+        <strong>Please prefix the subject line of any messages for <a href="index.html">Commons Configuration</a>
+        with <i>[configuration]</i></strong> - <i>thanks!</i>
+        <br />
+        <br />
+      </p>
+
+      <table>
+        <tr>
+          <th>Name</th>
+          <th>Subscribe</th>
+          <th>Unsubscribe</th>
+          <th>Post</th>
+          <th>Archive</th>
+          <th>Other Archives</th>
+        </tr>
+
+
+        <tr>
+          <td>
+            <strong>Commons User List</strong>
+            <br /><br />
+            Questions on using Commons Configuration.
+            <br /><br />
+          </td>
+          <td><a href="mailto:user-subscribe at commons.apache.org">Subscribe</a></td>
+          <td><a href="mailto:user-unsubscribe at commons.apache.org">Unsubscribe</a></td>
+          <td><a href="mailto:user at commons.apache.org?subject=[configuration]">Post</a></td>
+          <td><a href="http://mail-archives.apache.org/mod_mbox/commons-user/">mail-archives.apache.org</a></td>
+          <td><a href="http://markmail.org/list/org.apache.commons.users/">markmail.org</a><br />
+              <a href="http://www.mail-archive.com/user@commons.apache.org/">www.mail-archive.com</a><br />
+              <a href="http://news.gmane.org/gmane.comp.jakarta.commons.devel">news.gmane.org</a>
+          </td>
+        </tr>
+
+
+        <tr>
+          <td>
+            <strong>Commons Developer List</strong>
+            <br /><br />
+            Discussion of development of Commons Configuration.
+            <br /><br />
+          </td>
+          <td><a href="mailto:dev-subscribe at commons.apache.org">Subscribe</a></td>
+          <td><a href="mailto:dev-unsubscribe at commons.apache.org">Unsubscribe</a></td>
+          <td><a href="mailto:dev at commons.apache.org?subject=[configuration]">Post</a></td>
+          <td><a href="http://mail-archives.apache.org/mod_mbox/commons-dev/">mail-archives.apache.org</a></td>
+          <td><a href="http://markmail.org/list/org.apache.commons.dev/">markmail.org</a><br />
+              <a href="http://www.mail-archive.com/dev@commons.apache.org/">www.mail-archive.com</a><br />
+              <a href="http://news.gmane.org/gmane.comp.jakarta.commons.devel">news.gmane.org</a>
+          </td>
+        </tr>
+
+
+        <tr>
+          <td>
+            <strong>Commons Issues List</strong>
+            <br /><br />
+            Only for e-mails automatically generated by the <a href="issue-tracking.html">issue tracking</a> system.
+            <br /><br />
+          </td>
+          <td><a href="mailto:issues-subscribe at commons.apache.org">Subscribe</a></td>
+          <td><a href="mailto:issues-unsubscribe at commons.apache.org">Unsubscribe</a></td>
+          <td><i>read only</i></td>
+          <td><a href="http://mail-archives.apache.org/mod_mbox/commons-issues/">mail-archives.apache.org</a></td>
+          <td><a href="http://markmail.org/list/org.apache.commons.issues/">markmail.org</a><br />
+              <a href="http://www.mail-archive.com/issues@commons.apache.org/">www.mail-archive.com</a>
+          </td>
+        </tr>
+
+
+        <tr>
+          <td>
+            <strong>Commons Commits List</strong>
+            <br /><br />
+            Only for e-mails automatically generated by the <a href="source-repository.html">source control</a> sytem.
+            <br /><br />
+          </td>
+          <td><a href="mailto:commits-subscribe at commons.apache.org">Subscribe</a></td>
+          <td><a href="mailto:commits-unsubscribe at commons.apache.org">Unsubscribe</a></td>
+          <td><i>read only</i></td>
+          <td><a href="http://mail-archives.apache.org/mod_mbox/commons-commits/">mail-archives.apache.org</a></td>
+          <td><a href="http://markmail.org/list/org.apache.commons.commits/">markmail.org</a><br />
+              <a href="http://www.mail-archive.com/commits@commons.apache.org/">www.mail-archive.com</a>
+          </td>
+        </tr>
+
+      </table>
+
+    </section>
+    <section name="Apache Mailing Lists">
+      <p>
+        Other mailing lists which you may find useful include:
+      </p>
+
+      <table>
+        <tr>
+          <th>Name</th>
+          <th>Subscribe</th>
+          <th>Unsubscribe</th>
+          <th>Post</th>
+          <th>Archive</th>
+          <th>Other Archives</th>
+        </tr>
+        <tr>
+          <td>
+            <strong>Apache Announce List</strong>
+            <br /><br />
+            General announcements of Apache project releases.
+            <br /><br />
+          </td>
+          <td><a class="externalLink" href="mailto:announce-subscribe at apache.org">Subscribe</a></td> 
+          <td><a class="externalLink" href="mailto:announce-unsubscribe at apache.org">Unsubscribe</a></td> 
+          <td><i>read only</i></td>
+          <td><a class="externalLink" href="http://mail-archives.apache.org/mod_mbox/www-announce/">mail-archives.apache.org</a></td> 
+          <td><a class="externalLink" href="http://markmail.org/list/org.apache.announce/">markmail.org</a><br />
+              <a class="externalLink" href="http://old.nabble.com/Apache-News-and-Announce-f109.html">old.nabble.com</a><br />
+              <a class="externalLink" href="http://www.mail-archive.com/announce@apache.org/">www.mail-archive.com</a><br />
+              <a class="externalLink" href="http://news.gmane.org/gmane.comp.apache.announce">news.gmane.org</a>
+          </td>
+        </tr>
+      </table>
+
+    </section>
+  </body>
+</document>
diff --git a/src/site/xdoc/userguide/howto_basicfeatures.xml b/src/site/xdoc/userguide/howto_basicfeatures.xml
new file mode 100644
index 0000000..47a9f17
--- /dev/null
+++ b/src/site/xdoc/userguide/howto_basicfeatures.xml
@@ -0,0 +1,361 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<document>
+
+ <properties>
+  <title>Basic Features</title>
+  <author email="oheger at apache.org">Oliver Heger</author>
+ </properties>
+
+<body>
+	<section name="Basic features and AbstractConfiguration">
+    <p>
+      The <code>Configuration</code> interface defines a whole bunch of methods.
+      Implementing these methods all from scratch can be quite hard. Because of
+      that the <code><a href="../apidocs/org/apache/commons/configuration/AbstractConfiguration.html">
+      AbstractConfiguration</a></code> class exists. This class serves as a
+      common base class for most of the <code>Configuration</code> implementations
+      in <em>Commons Configuration</em> and provides a great deal of the
+      functionality required by the interface. So for creating a custom
+      <code>Configuration</code> implementation this class will be a good
+      starting point.
+    </p>
+    <p>
+      In addition to base implementations for lots of the methods declared in
+      the <code>Configuration</code> interface the <code>AbstractConfiguration</code>
+      class provides some other handy and convenient features. Because this
+      class is at the very root of the class hierarchy in <em>Commons
+      Configuration</em> these features are available in most of the specific
+      implementations of the <code>Configuration</code> interface provided by
+      this library. We will cover some of these basic features in this section.
+    </p>
+
+    <subsection name="Handling of missing properties">
+	<p>
+      What is a configuration object supposed to do if you pass in a key to one
+      of its get methods that does not map to an existing property? Well, the
+      default behavior as implemented in <code>AbstractConfiguration</code> is
+      to return <b>null</b> if the return value is an object type. For primitive
+      types as return values returning <b>null</b> (or any other special
+      value) is not possible, so in this case a <code>NoSuchElementException</code>
+      is thrown:
+    </p>
+    <source><![CDATA[
+// This will return null if no property with key "NonExistingProperty" exists
+String strValue = config.getString("NonExistingProperty");
+
+// This will throw a NoSuchElementException exception if no property with
+// key "NonExistingProperty" exists
+long longValue = config.getLong("NonExistingProperty");
+]]></source>
+    <p>
+      For object types like <code>String</code>, <code>BigDecimal</code>, or
+      <code>BigInteger</code> this default behavior can be changed: If the
+      <code>setThrowExceptionOnMissing()</code> method is called with an
+      argument of <b>true</b>, these methods will behave like their primitive
+      counter parts and also throw an exception if the passed in property key
+      cannot be resolved.
+    </p>
+    <p>
+      <em>Note:</em> Unfortunately support for the <code>throwExceptionOnMissing</code>
+      property is not always consistent: The methods <code>getList()</code> and
+      <code>getStringArray()</code> do not evaluate this flag, but return an
+      empty list or array if the requested property cannot be found. Maybe this
+      behavior will be changed in a future major release.
+    </p>
+    </subsection>
+
+    <subsection name="List handling">
+    <p>
+      With <code>getList()</code> and <code>getStringArray()</code> the
+      <code>Configuration</code> interface defines methods for dealing with
+      properties that have multiple values. When a configuration source (e.g.
+      a properties file, an XML document, or a JNDI context) is processed the
+      corresponding <code>Configuration</code> implementation detects such
+      properties with multiple values and ensures that the data is correctly
+      stored.
+    </p>
+    <p>
+      When modifying properties the <code>addProperty()</code> and
+      <code>setProperty()</code> methods of <code>AbstractConfiguration</code>
+      also implement special list handling. The property value that is passed
+      to these methods can be a list or an array resulting in a property
+      with multiple values. If the property value is a string, it is checked
+      whether it contains the <em>list delimiter character</em>. If this is
+      the case, the string is splitted, and its single parts are added one
+      by one. The list delimiter character is the comma by default. It is
+      also taken into account when the configuration source is loaded
+      (i.e. string values of properties will be checked whether they contain
+      this delimiter). By using the <code>setListDelimiter()</code>
+      method you can set it to a different character. Here are some examples:
+    </p>
+<source>
+// Change the list delimiter character to a slash
+config.setListDelimiter('/');
+// Now add some properties
+config.addProperty("greeting", "Hello, how are you?");
+config.addProperty("colors.pie",
+  new String[] { "#FF0000", "#00FF00", "#0000FF" });
+config.addProperty("colors.graph", "#808080/#00FFCC/#6422FF");
+
+// Access data
+String salut = config.getString("greeting");
+List<Object> colPie = config.getList("colors.pie");
+String[] colGraph = config.getStringArray("colors.graph");
+
+String firstPieColor = config.getString("colors.pie");
+</source>
+    <p>
+      In this example the list delimiter character is changed from a comma
+      to a slash. Because of this the <code>greeting</code> property won't
+      be split, but remains a single string. The string passed as value
+      for the <code>colors.graph</code> property in opposite contains the
+      new delimiter character and thus will result in a property with three
+      values. Note that lists are of type <code>Object</code>. This is because
+      the concrete class of the values of a property is not known. For instance,
+      if you call <code>addProperty("answer", 42)</code>, an
+      <code>Integer</code> object will be stored in the configuration.
+    </p>
+    <p>
+      Of interest is also the last line of the example fragment. Here the
+      <code>getString()</code> method is called for a property that has
+      multiple values. This call will return the first value of the list.
+    </p>
+    <p>
+      If you want to change the list delimiter character for all configuration
+      objects, you can use the static <code>setDefaultListDelimiter()</code>
+      method of <code>AbstractConfiguration</code>. It is also possible to
+      disable splitting of string properties at all for a Configuration
+      instance by calling its <code>setDelimiterParsingDisabled()</code>
+      method with a value of <b>true</b>.
+    </p>
+    </subsection>
+
+    <subsection name="Variable Interpolation">
+    <p>
+      If you are familiar with Ant or Maven, you have most certainly already encountered
+      the variables (like <code>${token}</code>) that are automatically expanded when the
+      configuration file is loaded. Commons Configuration supports this feature as well,
+      here is an example (we use a properties file in this example, but other
+      configuration sources work the same way; you can learn more about
+      properties files in the chapter <a href="howto_properties.html">Properties
+      files</a>):
+    </p>
+<source>
+application.name = Killer App
+application.version = 1.6.2
+
+application.title = ${application.name} ${application.version}
+</source>
+    <p>
+      If you now retrieve the value for the <code>application.title</code>
+      property, the result will be <code>Killer App 1.6.2</code>. So per default
+      variables are interpreted as the keys of other properties. This is only a
+      special case, the general syntax of a variable name is
+      <code>${prefix:name}</code>. The prefix tells Commons Configuration that
+      the variable is to be evaluated in a certain context. We have already seen
+      that the context is the current configuration instance if the prefix is
+      missing. The following other prefix names are supported by default:
+      <table border="1">
+      <tr>
+        <th>Prefix</th>
+        <th>Description</th>
+      </tr>
+      <tr>
+        <td valign="top">sys</td>
+        <td>This prefix marks a variable to be a system property. Commons
+        Configuration will search for a system property with the given name and
+        replace the variable by its value. This is a very easy means for
+        accessing the values of system properties in every configuration
+        implementation.</td>
+      </tr>
+      <tr>
+        <td valign="top">const</td>
+        <td>The <code>const</code> prefix indicates that a variable is to be
+        interpreted as a constant member field of a class (i.e. a field with the
+        <b>static final</b> modifiers). The name of the variable must be of the form
+        <code><full qualified class name>.<field name></code>. The
+        specified class will be loaded and the value of this field will be
+        obtained.</td>
+      </tr>
+      <tr>
+        <td valign="top">env</td>
+        <td>Variables can also reference OS-specific environment properties.
+        This is indicated by the <code>env</code> prefix.</td>
+      </tr>
+      </table>
+      Here are some examples (again using properties syntax):
+    </p>
+    <source><![CDATA[
+user.file = ${sys:user.home}/settings.xml
+
+action.key = ${const:java.awt.event.KeyEvent.VK_CANCEL}
+
+java.home = ${env:JAVA_HOME}
+]]></source>
+    <p>
+      If a variable cannot be resolved, e.g. because the name is invalid or an
+      unknown prefix is used, it won't be replaced, but is returned as is
+      including the dollar sign and the curly braces.
+    </p>
+    </subsection>
+
+    <subsection name="Customizing interpolation">
+    <p>
+      This sub section goes a bit behind the scenes of interpolation and
+      explains some approaches how you can add your own interpolation facilities.
+      Under the hood interpolation is implemented using the
+      <code>StrSubstitutor</code> class of the <code>text</code> package of
+      <a href="http://commons.apache.org/lang/">Commons Lang</a>. This
+      class uses objects derived from the <code>StrLookup</code> class for
+      resolving variables. <code>StrLookup</code> defines a simple
+      <code>lookup()</code> method that must be implemented by custom
+      implementations; it expects the name of a variable as argument and
+      returns the corresponding value (further details can be found in the
+      documentation of Commons Lang). The standard prefixes for variables we
+      have covered so far are indeed realized by special classes derived from
+      <code>StrLookup</code>.
+    </p>
+    <p>
+      It is now possible to create your own implementation of <code>StrLookup</code>
+      and make it available for all configuration objects under a custom
+      prefix. We will show how this can be achieved. The first step is to
+      create a new class derived from <code>StrLookup</code>, which must
+      implement the <code>lookup()</code> method. As an example we implement a
+      rather dull lookup object that simply returns a kind of "echo"
+      for the variable passed in:
+    </p>
+    <source><![CDATA[
+import org.apache.commons.lang.text.StrLookup;
+
+public class EchoLookup extends StrLookup
+{
+    public String lookup(String varName)
+    {
+        return "Value of variable " + varName;
+    }
+}
+]]></source>
+    <p>
+      Now we want this class to be called for variables with the prefix
+      <code>echo</code>. For this purpose the <code>EchoLookup</code> class
+      has to be registered at the
+      <code><a href="../apidocs/org/apache/commons/configuration/interpol/ConfigurationInterpolator.html">
+      ConfigurationInterpolator</a></code> class with the desired prefix.
+      <code>ConfigurationInterpolator</code> implements a thin wrapper over the
+      <code>StrLookup</code> API defined by Commons Lang. It has a static
+      <code>registerGlobalLookup()</code> method, which we have to call as
+      follows:
+    </p>
+    <source><![CDATA[
+// Place this code somewhere in an initialization section of your application
+ConfigurationInterpolator.registerGlobalLookup("echo", new EchoLookup());
+    ]]></source>
+    <p>
+      Each <code>AbstractConfiguration</code> object that is created after this
+      line is executed will contain the new lookup class and can thus resolve
+      variables of the form <code>${echo:my_variable}</code>.
+    </p>
+    <p>
+      Each instance of <code>AbstractConfiguration</code> is associated with a
+      <code>ConfigurationInterpolator</code> object. This object is created by
+      the <code>createInterpolator()</code> method on first access of one of
+      the interpolation features. By overriding this method even deeper
+      intervention in the interpolation mechanism is possible. For instance
+      a custom implementation can add further lookup objects to the interpolator,
+      which are then only used by this configuration instance.
+    </p>
+    </subsection>
+    <subsection name="Using Expressions">
+      <p>
+        In addition to the simple lookup mechanisms previously described, Commond Configuration
+        provides <code>ExprLookup</code> which uses <a href="http://commons.apache.org/jexl">Apache
+        Commons Jexl</a> to allow expressions to be resolved wherever a StrLookup is allowed. This
+        example shows an alternate way of obtaining a system property if the ExprLookup is
+        configured.
+      </p>
+<source><![CDATA[
+user.file = ${expr:System.getProperty("user.home"}/settings.xml
+]]></source>
+      <p>
+        <code>ExprLookup</code> is not enabled by default, it must be manually added or configured via
+        <code>DefaultConfigurationBuilder</code>. Builds that use Maven 2 and reference Commons
+        Configuration will not include a dependency on Jexl, so if this feature is used the
+        dependency on Jexl must be manually added to the project.
+      </p>
+      <p>
+        When using <code>DefaultConfigurationBuilder</code> adding <code>ExprLookup</code> is
+        straightforward.
+      </p>
+<source><![CDATA[
+<configuration>
+  <header>
+    <result/>
+    <lookups>
+      <lookup config-prefix="expr"
+              config-class="org.apache.commons.configuration.interpol.ExprLookup">
+        <variables>
+          <variable name="System" value="Class:java.lang.System"/>
+          <variable name"net" value="Class:java.net.InetAddress"/>
+          <variable name="String" value="Class:org.apache.commons.lang.StringUtils"/>
+        </variables>
+      </lookup>
+    </lookups>
+  </header>
+  <override>
+    <xml fileName="${expr:System.getProperty('basePath') +
+         String.lowercase(net.localHost.hostName) + '/testMultiConfiguration_default.xml'}"
+         config-name="defaultConfig" delimiterParsingDisabled="true">
+    </xml>
+  </override>
+</configuration>
+]]></source>
+      <p>
+        The example above shows how to invoke static methods during expression evaluation. The
+        next example shows mixing expression evaluation with a subordinate lookup to
+        obtain the "basePath" system property. Note the difference in how the
+        system property was obtained in the previous example.
+      </p>
+<source><![CDATA[
+<configuration>
+  <header>
+    <result/>
+    <lookups>
+      <lookup config-prefix="expr"
+              config-class="org.apache.commons.configuration.interpol.ExprLookup">
+        <variables>
+          <variable name"net" value="Class:java.net.InetAddress"/>
+          <variable name="String" value="Class:org.apache.commons.lang.StringUtils"/>
+        </variables>
+      </lookup>
+    </lookups>
+  </header>
+  <override>
+    <xml fileName="${expr:$[sys:basePath] + 
+         String.lowercase(net.localHost.hostName) + '/testMultiConfiguration_default.xml'}"
+         config-name="defaultConfig" delimiterParsingDisabled="true">
+    </xml>
+  </override>
+</configuration>
+]]></source>
+    </subsection>
+  </section>
+</body>
+
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/userguide/howto_beans.xml b/src/site/xdoc/userguide/howto_beans.xml
new file mode 100644
index 0000000..85d55df
--- /dev/null
+++ b/src/site/xdoc/userguide/howto_beans.xml
@@ -0,0 +1,358 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<document>
+
+ <properties>
+  <title>Declaring Beans Howto</title>
+  <author email="oheger at apache.org">Oliver Heger</author>
+ </properties>
+
+<body>
+	<section name="Declaring and Creating Beans">
+    <p>
+      Often it is good practice to make heavy use of Java interfaces and program
+      an application or components against these interfaces rather than concrete
+      implementation classes. This makes it possible to switch to different implementations
+      without having to modify calling code. However the problem remains how a
+      concrete implementation of an interface is obtained. Simply using the
+      <b>new</b> operator on a specific implementation class would somehow break
+      the interface concept because then the code would have an explicit reference
+      to a concrete implementation.
+    </p>
+    <p>
+      A solution to this problem is to define the concrete implementation class
+      that should be used in a configuration file. Client code would obtain an
+      object (or a bean) from the configuration and cast it to the service
+      interface. This way the caller would have no knowledge about which concrete
+      implementation is used; it would only interact with the service through
+      the interface. By changing the configuration file and entering a different
+      class name for the implementation class the behavior of the application
+      can be altered, e.g. to inject a test stub for the used service.
+    </p>
+    <p>
+      <em>Note: The concept of defining service objects in configuration files
+      and let them be created by a special container has grown popular these
+      days. Especially IoC containers like <a href="http://jakarta.apache.org/hivemind/">HiveMind</a>
+      or <a href="http://www.springframework.org/">Spring</a> offer wide
+      functionality related to this topic. Commons Configuration is not and has
+      no ambitions to become an IoC container. The provided functionality for
+      declaring and creating beans is very basic and limited compared to the
+      specialists. So if you are in need of enhanced features like the creation
+      of complete networks of service objects, life cycle handling and such things,
+      you should in any case use a real IoC container. For simple use cases
+      however the functionality of Commons Configuration might be sufficient,
+      and we have tried to provide hooks for extending the predefined mechanisms.</em>
+    </p>
+
+      <subsection name="Basic Concepts">
+      <p>
+        Beans (we use the term <em>bean</em> here to name any plain old Java
+        object that is defined in a configuration file and can be instantiated
+        by Commons Configuration) are defined in configuration files in a specific
+        format, a so called <em>Bean declaration</em>. Such a declaration contains
+        all information needed to create an instance of this bean class, e.g.
+        the full qualified name of the class and initialization parameters. We will
+        explain how a bean declaration looks like in short.
+      </p>
+      <p>
+        On the Java side three entities are involved in the creation of a bean:
+        <ul>
+          <li>A <em>bean factory</em>: This is an object that implements the
+          <code><a href="../apidocs/org/apache/commons/configuration/beanutils/BeanFactory.html">BeanFactory</a></code>
+          interface and knows how to create an instance of a bean class. In most
+          cases calling code does not directly deal with a bean factory.</li>
+          <li>An implementation of the
+          <code><a href="../apidocs/org/apache/commons/configuration/beanutils/BeanDeclaration.html">BeanDeclaration</a></code>
+          interface. This object knows how the bean declaration in the configuration
+          file is organized and how the needed information can be extracted. So
+          the way the bean is declared in the configuration file must match the
+          expectations of this object.</li>
+          <li>The utility class
+          <code><a href="../apidocs/org/apache/commons/configuration/beanutils/BeanHelper.html">BeanHelper</a></code>
+          brings all these together and performs the bean creation operation.
+          Usually client code will create a <code>BeanDeclaration</code> object
+          from a <code>Configuration</code> implementation and then pass it to
+          one of the <code>createBean()</code> methods of <code>BeanHelper</code>.
+          That's it!</li>
+        </ul>
+        For all of the interfaces mentioned above default implementations are
+        provided, which in many cases can be used out of the box.
+      </p>
+      </subsection>
+      
+      <subsection name="An Example">
+      <p>
+        After this theory let's get into practice using an example. Consider a
+        GUI application that makes use of a <em>Window manager</em> to display
+        its windows and dialogs to the user. There is a <code>WindowManager</code>
+        interface containing methods for opening, displaying, hiding, and
+        disposing windows. Different implementations of this interface exist, e.g.
+        providing different look & feel or special functionality. The concrete
+        set of methods of the interface does not matter for this example.
+      </p>
+      <p>
+        Now in the application's configuration it shall be specified that the
+        concrete implementation <code>DefaultWindowManager</code> should be
+        used as <code>WindowManager</code>. This is a plain Java class implementing
+        the <code>WindowManager</code> interface. Some fragments are shown in
+        the following listing:
+      </p>
+<source><![CDATA[
+package examples.windows;
+
+public class DefaultWindowManager implements WindowManager
+{
+    // are windows allowed to be resized?
+    private boolean resizable;
+    // do windows have a close button?
+    private boolean closable;
+    
+    // Default size of new windows
+    private int defaultWidth;
+    private int defaultHeight;
+    
+    WindowStyleDefinition styleDefinition;
+    
+    // getters and setters ommitted, also the WindowManager methods
+}
+]]></source>
+      <p>
+        As you can see, the <code>DefaultWindowManager</code> class has some
+        simple properties for defining the windows. There is also a property
+        named <code>StyleDefinition</code> whose type is another bean (such
+        a style definition may contain information about themes, colors, fonts
+        of the window and so on). How can we now write a configuration file so
+        that a bean of the <code>DefaultWindowManager</code> class is declared
+        and initialization properties are defined? In an XML configuration file
+        this will look as follows:
+      </p>
+<source><![CDATA[
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<config>
+  <gui>
+    <windowManager config-class="examples.windows.DefaultWindowManager"
+      closable="false" resizable="true" defaultWidth="400"
+      defaultHeight="250">
+      <styleDefinition config-class="examples.windows.WindowStyleDefinition"
+        backColor="#ffffff" foreColor="0080ff" iconName="myicon" />
+    </windowManager>
+  </gui>
+</config>
+]]></source>
+      <p>
+        This XML document contains a valid bean declaration starting with the
+        <code>windowManager</code> element and including its sub elements. Note
+        the following points:
+        <ul>
+          <li>The (full qualified) class of the bean is specified using the
+          <code>config-class</code> attribute. (Attributes starting with the
+          prefix "config-" are reserved; they contain special meta
+          data for the bean creation process.)</li>
+          <li>Other attributes of the <code>windowManager</code> element correspond
+          to properties of the <code>DefaultWindowManager</code> class. These
+          properties will be initialized with the values specified here.</li>
+          <li>For the <code>styleDefinition</code> property, which is itself a
+          bean, a sub element (matching the property's name) exists. The structure
+          of this element is analogous to the structure of the <code>windowManager</code>
+          element; indeed it could even have further sub elements defining
+          bean properties of the <code>WindowStyleDefinition</code> class.</li>
+        </ul>
+        The basic structure of a bean declaration should have become clear by
+        this example.
+      </p>
+      <p>
+        Now let's see how we can access this declaration and create an instance.
+        This is demonstrated in the code fragment below:
+      </p>
+<source><![CDATA[
+XMLConfiguration config = new XMLConfiguration("windowconfig.xml");
+BeanDeclaration decl = new XMLBeanDeclaration(config, "gui.windowManager");
+WindowManager wm = (WindowManager) BeanHelper.createBean(decl);
+]]></source>
+      <p>
+        This fragment loads the configuration file using a <code>XMLConfiguration</code>
+        object. Then a bean declaration object is created, in this case an
+        instance of the <code><a href="../apidocs/org/apache/commons/configuration/beanutils/XMLBeanDeclaration.html">XMLBeanDeclaration</a></code>
+        class, which can deal with bean declarations in XML documents. This
+        declaration is passed to the static <code>createBean()</code> method of
+        the <code><a href="../apidocs/org/apache/commons/configuration/beanutils/BeanHelper.html">BeanHelper</a></code>
+        class, which returns the new bean instance.
+      </p>
+      <p>
+        <code>BeanHelper</code> defines some overloaded versions of the
+        <code>createBean()</code> method. Some allow to pass in a default bean
+        class; then it is not necessary to define the class in the bean declaration
+        - an instance of this default class will be created if it is lacking in
+        the configuration file. If the bean cannot be created for some reason
+        (e.g. a wrong class name was specified), a <code>ConfigurationRuntimeException</code>
+        will be thrown.
+      </p>
+      </subsection>
+      
+      <subsection name="Extending the Basic Mechanism">
+      <p>
+        As was pointed out in the introduction of this chapter support for creating
+        beans is focused on the basics. But there are some possibilities of hooking
+        in and add custom extensions. This can be done in the following ways:
+        <ul>
+          <li>By defining a custom <code>BeanDeclaration</code> implementation</li>
+          <li>By providing a custom <code>BeanFactory</code> implementation</li>
+        </ul>
+      </p>
+      <p>
+        A specialized bean declaration is needed when you have to deal with
+        configuration files that contain bean declarations in a different format
+        than the ones supported by the available default implementations. Then it
+        is the responsibility of your implementation to parse the configuration
+        data and extract the required information to create the bean. Basically
+        your <code><a href="../apidocs/org/apache/commons/configuration/beanutils/BeanDeclaration.html">BeanDeclaration</a></code>
+        implementation must be able to provide the following data:
+        <ul>
+          <li>The name of the class for which an instance is to be created.</li>
+          <li>The name of the bean factory that is used to create the bean. Here
+          <b>null</b> can be returned, then a default factory is used. (See
+          below for more information about working with custom bean factories.)</li>
+          <li>An optional parameter to be passed to the bean factory. If a factory
+          is used that supports additional parameters, the current parameter
+          values are also obtained from the bean declaration.</li>
+          <li>A map with the properties to be set on the newly created bean.
+          This map's keys are names of properties, its values are the corresponding
+          property values. The default bean factory will process this map and
+          call the corresponding setter methods on the newly created bean object.</li>
+          <li>A map with further <code>BeanDeclaration</code> objects for
+          initializing properties of the new bean that are itself beans. These
+          bean declarations are treated exactly as the one that is currently
+          processed. The resulting beans will then be set as properties on the
+          processed bean (the names of these properties are again obtained from
+          the keys of the map).</li>
+        </ul>
+      </p>
+      <p>
+        While creating a custom <code>BeanDeclaration</code> implementation
+        allows you to adapt the format of bean declarations in configuration files,
+        you can manipulate the bean creation mechanism itself by creating a
+        specialized implementation of the
+        <code><a href="../apidocs/org/apache/commons/configuration/beanutils/BeanFactory.html">BeanFactory</a></code>
+        interface. For this purpose the following steps are necessary:
+        <ol>
+          <li>Create a class implementing the <code>BeanFactory</code> interface.
+          This interface is quite simple. It defines one method for creating an
+          instance of a class whose <code>Class</code> object is provided, and
+          another method, which is called for querying a default class.</li>
+          <li>Register this new factory class at the <code>BeanHelper</code>
+          class.</li>
+          <li>In the bean declaration in your configuration file refer to the
+          factory that should be used for creating the bean.</li>
+        </ol>
+      </p>
+      <p>
+        We will provide an example that covers all these steps. This example
+        deals with a <em>singleton</em> factory, i.e. an implementation of
+        <code>BeanFactory</code> that returns always the same instance of a
+        provided bean class.
+      </p>
+      <p>
+        We start with the creation of the factory class. The basic idea is that
+        the functionality for creating and initializing beans is already provided
+        by the <code><a href="../apidocs/org/apache/commons/configuration/beanutils/DefaultBeanFactory.html">DefaultBeanFactory</a></code>
+        class, so we extend this class. Our implementation only has to deal with
+        the singleton stuff: We keep a map that stores already created bean
+        instances and can be accessed by the name of their classes. In the
+        factory's <code>createBean()</code> method we check if for the passed in
+        class already an instance exists. If this is the case, it is directly
+        returned. Otherwise we call the inherited <code>createBean()</code> method
+        and store its result in the map. (Note that this implementation is a bit
+        simplicistic; a real world implementation would also have to take the
+        initialization parameters into account. But for the purpose of an example
+        it should be good enough). Here is the code:
+      </p>
+<source><![CDATA[
+public class SingletonBeanFactory extends DefaultBeanFactory
+{
+    /** A map for the so far created instances.*/
+    private Map beans;
+    
+    public SingletonBeanFactory()
+    {
+        super();
+        beans = new HashMap();
+    }
+    
+    // Creates the bean. Checks if already an instance exists.
+    public synchronized Object createBean(Class beanClass, BeanDeclaration decl,
+        Object param) throws Exception
+    {
+        Object bean = beans.get(beanClass.getName());
+        if (bean != null)
+        {
+            // Yes, there is already an instance
+            return bean;
+        }
+        else
+        {
+            // No, create it now (done by the super class)
+            bean = super.createBean(beanClass, decl, param);
+            // Store it in map
+            beans.put(beanClass.getName(), bean);
+            return bean;
+        }
+    }
+}
+]]></source>
+      <p>
+        Note the <b>synchronized</b> key word, which is necessary because the
+        method can be accessed by multiple threads concurrently. Now we have to
+        register an instance of this class at the <code>BeanHelper</code> class.
+        This can be done in the initialization phase of your application and
+        looks as follows:
+      </p>
+<source><![CDATA[
+BeanHelper.registerBeanFactory("SINGLETON", new SingletonBeanFactory());
+]]></source>
+      <p>
+        To make use of the new factory a bean declaration must contain an
+        attribute that refers to the name under which the factory was registered.
+        This is demonstrated by the fragment below:
+      </p>
+<source><![CDATA[
+<config>
+...
+    <services>
+      <fileService config-class="my.package.services.FileServiceImpl"
+        config-factory="SINGLETON"
+        property1="value1" property2="value2">
+        <!-- Here can be nested bean declarations -->
+      </fileService>
+      ...
+</config>
+]]></source>
+      <p>
+        In this fragment the <code>fileService</code> element contains a bean
+        declaration for some service object. Apart from the <code>config-class</code>
+        attribute the important part is the <code>config-factory</code> attribute.
+        This attribute tells the <code>BeanHelper</code> class that it should
+        use a special factory when it processes this bean declaration. As was
+        demonstrated by this example, it should not be too difficult to extend
+        the custom mechanism for creating beans.
+      </p>
+      </subsection>
+    </section>
+</body>
+
+</document>
diff --git a/src/site/xdoc/userguide/howto_combinedconfiguration.xml b/src/site/xdoc/userguide/howto_combinedconfiguration.xml
new file mode 100644
index 0000000..d143d94
--- /dev/null
+++ b/src/site/xdoc/userguide/howto_combinedconfiguration.xml
@@ -0,0 +1,758 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<document>
+
+ <properties>
+  <title>Combined Configurations</title>
+  <author email="oheger at apache.com">Oliver Heger</author>
+ </properties>
+
+<body>
+    <section name="Combined Configuration">
+    <p>
+      The <code><a href="../apidocs/org/apache/commons/configuration/CombinedConfiguration.html">
+      CombinedConfiguration</a></code> class provides an alternative for handling
+      multiple configuration sources. Its API is very similar to the
+      <code>CompositeConfiguration</code> class, which was discussed in the
+      <a href="howto_compositeconfiguration.html#Composite Configuration Details">last
+      section</a>. There are the following differences however:
+    </p>
+    <p>
+      <ul>
+        <li>A <code>CombinedConfiguration</code> is a truely
+        <a href="howto_xml.html#Hierarchical properties">hierarchical
+        configuration</a>. This means that all the enhanced facilities
+        provided by <code>HierarchicalConfiguration</code> (e.g. expression
+        engines) can be used.</li>
+        <li>A <code>CombinedConfiguration</code> is not limited to implementing
+        an override semantics for the properties of the contained configurations.
+        Instead it has the concept of so-called <em>node combiners</em>, which
+        know how properties of multiple configuration sources can be combined.
+        Node combiners are discussed later in detail. For instance, there is a
+        node combiner implementation available that constructs a union of the
+        contained configurations.</li>
+        <li>Contained configurations can be assigned a name. They can later be
+        accessed by their name.</li>
+        <li>Each contained configuration can have an optional prefix. Its
+        properties are then added under this prefix to the combined
+        configuration.</li>
+        <li>There is no concept of an <em>in memory configuration</em>. Changes
+        to a combined configuration are handled in a different way.</li>
+      </ul>
+   	</p>
+
+    <subsection name="How it works">
+    <p>
+      A <code>CombinedConfiguration</code> provides a logic view on the
+      properties of the configurations it contains. This view is determined
+      by the associated node combiner object. Because of that it must be
+      re-constructed whenever one of these contained configurations is
+      changed.
+    </p>
+    <p>
+      To achieve this, a <code>CombinedConfiguration</code> object registers
+      itself as an event listener at the configurations that are added to it.
+      It will then be notified for every modification that occurs. If such a
+      notification is received, the internally managed view is invalidated.
+      When a property of the combined configuration is to be accessed, the view
+      is checked whether it is valid. If this is the case, the property's value
+      can be directly fetched. Otherwise the associated node combiner is asked
+      to re-construct the view.
+    </p>
+    </subsection>
+    
+    <subsection name="Node combiners">
+    <p>
+      A <em>node combiner</em> is an object of a class that inherits from the
+      abstract <code><a href="../apidocs/org/apache/commons/configuration/tree/NodeCombiner.html">NodeCombiner</a></code>
+      class. This class defines an abstract <code>combine()</code> method, which
+      takes the root nodes of two hierarchical configurations and returns the
+      root node of the combined node structure. It is up to a concrete
+      implementation how this combined structure will look like. Commons
+      Configuration ships with three concrete implementations
+      <code><a href="../apidocs/org/apache/commons/configuration/tree/OverrideCombiner.html">OverrideCombiner</a></code>,
+      <code><a href="../apidocs/org/apache/commons/configuration/tree/MergeCombiner.html">MergeCombiner</a></code>
+      and <code><a href="../apidocs/org/apache/commons/configuration/tree/UnionCombiner.html">UnionCombiner</a></code>,
+      which implement an override, merge, and union semantics respectively.
+    </p>
+    <p>
+      Constructing a combination of multiple node hierarchies is not a trivial
+      task. The available implementations descend the passed in node hierarchies
+      in a recursive manner to decide, which nodes have to be copied into the
+      resulting structure. Under certain circumstances two nodes of the source
+      structures can be combined into a single result node, but unfortunately
+      this process cannot be fully automated, but sometimes needs some hints
+      from the developer. As an example consider the following XML configuration
+      sources:
+    </p>
+    <source><![CDATA[
+<configuration>
+  <database>
+    <tables>
+      <table>
+        <name>users</name>
+        <fields>
+          <field>
+            <name>user_id</name>
+          </field>
+          ...
+        </fields>
+      </table>
+    </tables>
+  </database>
+</configuration>
+]]></source>
+    <p>and</p>
+    <source><![CDATA[
+<configuration>
+  <database>
+    <tables>
+      <table>
+        <name>documents</name>
+        <fields>
+          <field>
+            <name>document_id</name>
+          </field>
+          ...
+        </fields>
+      </table>
+    </tables>
+  </database>
+</configuration>
+]]></source>
+    <p>
+      These two configuration sources define database tables. Each source
+      defines one table. When constructing a union for these sources the result
+      should look as follows:
+    </p>
+    <source><![CDATA[
+<configuration>
+  <database>
+    <tables>
+      <table>
+        <name>users</name>
+        <fields>
+          <field>
+            <name>user_id</name>
+          </field>
+          ...
+        </fields>
+      </table>
+      <table>
+        <name>documents</name>
+        <fields>
+          <field>
+            <name>document_id</name>
+          </field>
+          ...
+        </fields>
+      </table>
+    </tables>
+  </database>
+</configuration>
+]]></source>
+    <p>
+      As you can see, the resulting structure contains two <code>table</code>
+      nodes while the nodes <code>database</code> and <code>tables</code> appear
+      only once. For a human being this is quite logic because <code>database</code>
+      and <code>tables</code> define the overall structure of the configuration
+      data, and there can be multiple tables. A node combiner however does not
+      know anything about structure nodes, list nodes, or whatever. From its point of
+      view there is no detectable difference between the <code>tables</code>
+      nodes and the <code>table</code> nodes in the source structures: both
+      appear once in each source file and have no values. So without any
+      assistance the result constructed by the <code>UnionCombiner</code> when
+      applied on the two example sources would be a bit different:
+    </p>
+    <source><![CDATA[
+<configuration>
+  <database>
+    <tables>
+      <table>
+        <name>users</name>
+        <fields>
+          <field>
+            <name>user_id</name>
+          </field>
+          ...
+        </fields>
+        <name>documents</name>
+        <fields>
+          <field>
+            <name>document_id</name>
+          </field>
+          ...
+        </fields>
+      </table>
+    </tables>
+  </database>
+</configuration>
+]]></source>
+    <p>
+      Note that the <code>table</code> node would be considered a structure
+      node, too, and would not be duplicated. This is probably not what was
+      desired. To deal with such situations it is possible to tell the node
+      combiner that certain nodes are list nodes and thus should not be
+      combined. So in this concrete example the <code>table</code> node should
+      be declared as a list node, then we would get the expected result. We will
+      see below how this is done. Note that this explicit declaration of a list
+      node is necessary only in situations where there is ambiguity. If in one
+      of our example configuration sources multiple tables had been defined, the
+      node combiner would have concluded itself that <code>table</code> is a list
+      node and would have acted correspondigly.
+    </p>
+    <p>
+      The examples the follow are provided to further illustrate the differences
+      between the combiners that are delivered with Commons Configuration. The first
+      two files are the files that will be combined.
+    </p>
+    <table border='0'>
+    <tr>
+      <th width="50%">testfile1.xml</th>
+      <th width="50%">testfile2.xml</th>
+    </tr>
+    <tr><td width="50%">
+<source><![CDATA[<config>
+  <gui>
+    <bgcolor>green</bgcolor>
+    <selcolor>yellow</selcolor>
+    <level default="2">1</level>
+  </gui>
+  <net>
+    <proxy>
+      <url>http://www.url1.org</url>
+      <url>http://www.url2.org</url>
+      <url>http://www.url3.org</url>
+    </proxy>
+    <service>
+      <url>http://service1.org</url>
+    </service>
+    <server>
+    </server>
+  </net>
+  <base>
+    <services>
+      <security>
+        <login>
+          <user>Admin</user>
+          <passwd type="secret"/>
+        </login>
+      </security>
+    </services>
+  </base>
+  <database>
+    <tables>
+      <table id="1">
+        <name>documents</name>
+        <fields>
+          <field>
+            <name>docid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>docname</name>
+            <type>varchar</type>
+          </field>
+          <field>
+            <name>authorID</name>
+            <type>int</type>
+          </field>
+        </fields>
+      </table>
+    </tables>
+  </database>
+  <Channels>
+    <Channel id="1" type="half">
+      <Name>My Channel</Name>
+    </Channel>
+    <Channel id="2">
+      <MoreChannelData>more test 2 data</MoreChannelData>
+    </Channel>
+    <Channel id="3" type="half">
+      <Name>Test Channel</Name>
+    </Channel>
+    <Channel id="4">
+      <Name>Channel 4</Name>
+    </Channel>
+  </Channels>
+</config>
+]]></source></td><td width="50%">
+<source><![CDATA[<config>
+  <base>
+    <services>
+      <security>
+        <login>
+          <user type="default">scotty</user>
+          <passwd>BeamMeUp</passwd>
+        </login>
+      </security>
+    </services>
+  </base>
+  <gui>
+    <bgcolor>black</bgcolor>
+    <fgcolor>blue</fgcolor>
+    <level min="1">4</level>
+  </gui>
+  <net>
+    <server>
+      <url>http://appsvr1.com</url>
+      <url>http://appsvr2.com</url>
+      <url>http://testsvr.com</url>
+      <url>http://backupsvr.com</url>
+    </server>
+    <service>
+      <url type="2">http://service2.org</url>
+      <url type="2">http://service3.org</url>
+    </service>
+  </net>
+  <database>
+    <tables>
+      <table id="2">
+        <name>tasks</name>
+        <fields>
+          <field>
+            <name>taskid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>taskname</name>
+            <type>varchar</type>
+          </field>
+        </fields>
+      </table>
+    </tables>
+  </database>
+  <Channels>
+    <Channel id="1">
+      <Name>Channel 1</Name>
+      <ChannelData>test 1 data</ChannelData>
+    </Channel>
+    <Channel id="2" type="full">
+      <Name>Channel 2</Name>
+      <ChannelData>test 2 data</ChannelData>
+    </Channel>
+    <Channel id="3" type="full">
+      <Name>Channel 3</Name>
+      <ChannelData>test 3 data</ChannelData>
+    </Channel>
+    <Channel id="4" type="half">
+      <Name>Test Channel 1</Name>
+    </Channel>
+    <Channel id="4" type="full">
+      <Name>Test Channel 2</Name>
+    </Channel>
+  </Channels>
+</config>
+]]></source></td></tr></table>
+      <p>
+        The first listing shows the result of using the <code>OverrideCombiner</code>.
+      </p>
+      <table>
+        <tr><th width="40%">OverrideCombiner Results</th><th width="60%">Notes</th></tr>
+        <tr><td width="40%">
+ <source><![CDATA[<config>
+  <gui>
+    <bgcolor>green</bgcolor>
+    <selcolor>yellow</selcolor>
+    <level default='2' min='1'>1</level>
+    <fgcolor>blue</fgcolor>
+  </gui>
+  <net>
+    <proxy>
+      <url>http://www.url1.org</url>
+      <url>http://www.url2.org</url>
+      <url>http://www.url3.org</url>
+    </proxy>
+    <service>
+      <url>http://service1.org</url>
+    </service>
+    <server>
+      <url>http://appsvr1.com</url>
+      <url>http://appsvr2.com</url>
+      <url>http://testsvr.com</url>
+      <url>http://backupsvr.com</url>
+    </server>
+  </net>
+  <base>
+    <services>
+      <security>
+        <login>
+          <user type='default'>Admin</user>
+          <passwd type='secret'>BeamMeUp</passwd>
+        </login>
+      </security>
+    </services>
+  </base>
+  <database>
+    <tables>
+      <table id='1'>
+        <name>documents</name>
+        <fields>
+          <field>
+            <name>docid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>docname</name>
+            <type>varchar</type>
+          </field>
+          <field>
+            <name>authorID</name>
+            <type>int</type>
+          </field>
+        </fields>
+      </table>
+    </tables>
+  </database>
+  <Channels>
+    <Channel id='1' type='half'>
+      <Name>My Channel</Name>
+    </Channel>
+    <Channel id='2'>
+      <MoreChannelData>more test 2 data</MoreChannelData>
+    </Channel>
+    <Channel id='3' type='half'>
+      <Name>Test Channel</Name>
+    </Channel>
+  </Channels>
+</config>
+]]></source></td><td width="60%">
+      <p>
+        The features that are significant in this file are:
+        <ul>
+          <li>In the gui section each of the child elements only appears once. The level element
+          merges the attributes from the two files and uses the element value of the first file.</li>
+          <li>In the security section the user type attribute was obtained from the second file
+          while the user value came from the first file. Alternately, the password type was
+          obtained from the first file while the value came from the second.</li>
+          <li>Only the data from table 1 was included.</li>
+          <li>Channel 1 in the first file completely overrode Channel 1 in the second file.</li>
+          <li>Channel 2 in the first file completely overrode Channel 2 in the second file. While
+          the attributes were merged in the case of the login elements the type attribute
+          was not merged in this case.</li>
+          <li>Again, only Channel 3 from the first file was included.</li>
+        </ul>
+      </p>
+      <p>
+        How the Channel elements ended up may not at first be obvious. The <code>OverrideCombiner</code>
+        simply noticed that the Channels element had three child elements named Channel and
+        used that to determine that only the contents of the Channels element in the first file
+        would be used.
+      </p></td></tr></table>
+      <p>
+        The next file is the the result of using the <code>UnionCombiner</code>
+      </p>
+      <table>
+        <tr>
+          <th width="40%">UnionCombiner Results</th>
+          <th width="60%">Notes</th>
+        </tr>
+        <tr><td width="40%">
+ <source><![CDATA[<config>
+  <gui>
+    <bgcolor>green</bgcolor>
+    <selcolor>yellow</selcolor>
+    <level default='2'>1</level>
+    <bgcolor>black</bgcolor>
+    <fgcolor>blue</fgcolor>
+    <level min='1'>4</level>
+  </gui>
+  <net>
+    <proxy>
+      <url>http://www.url1.org</url>
+      <url>http://www.url2.org</url>
+      <url>http://www.url3.org</url>
+    </proxy>
+    <service>
+      <url>http://service1.org</url>
+      <url type='2'>http://service2.org</url>
+      <url type='2'>http://service3.org</url>
+    </service>
+    <server></server>
+    <server>
+      <url>http://appsvr1.com</url>
+      <url>http://appsvr2.com</url>
+      <url>http://testsvr.com</url>
+      <url>http://backupsvr.com</url>
+    </server>
+  </net>
+  <base>
+    <services>
+      <security>
+        <login>
+          <user>Admin</user>
+          <passwd type='secret'></passwd>
+          <user type='default'>scotty</user>
+          <passwd>BeamMeUp</passwd>
+        </login>
+      </security>
+    </services>
+  </base>
+  <database>
+    <tables>
+      <table id='1' id='2'>
+        <name>documents</name>
+        <fields>
+          <field>
+            <name>docid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>docname</name>
+            <type>varchar</type>
+          </field>
+          <field>
+            <name>authorID</name>
+            <type>int</type>
+          </field>
+          <field>
+            <name>taskid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>taskname</name>
+            <type>varchar</type>
+          </field>
+        </fields>
+        <name>tasks</name>
+      </table>
+    </tables>
+  </database>
+  <Channels>
+    <Channel id='1' type='half'>
+      <Name>My Channel</Name>
+    </Channel>
+    <Channel id='2'>
+      <MoreChannelData>more test 2 data</MoreChannelData>
+    </Channel>
+    <Channel id='3' type='half'>
+      <Name>Test Channel</Name>
+    </Channel>
+    <Channel id='1'>
+      <Name>Channel 1</Name>
+      <ChannelData>test 1 data</ChannelData>
+    </Channel>
+    <Channel id='2' type='full'>
+      <Name>Channel 2</Name>
+      <ChannelData>test 2 data</ChannelData>
+    </Channel>
+    <Channel id='3' type='full'>
+      <Name>Channel 3</Name>
+      <ChannelData>test 3 data</ChannelData>
+    </Channel>
+  </Channels>
+</config>
+]]></source></td><td width="60%">
+      <p>
+        The feature that is significant in this file is rather obvious. It is just a simple
+        union of the contents of the two files.
+      </p>
+      </td></tr></table>
+      <p>
+        Finally, the last file is the result of using the <code>MergeCombiner</code>
+      </p>
+      <table>
+        <tr>
+          <th width="40%">MergeCombiner Results</th>
+          <th width="60%">Notes</th>
+        </tr>
+        <tr><td width="40%">
+<source><![CDATA[<config>
+  <gui>
+    <bgcolor>green</bgcolor>
+    <selcolor>yellow</selcolor>
+    <level default='2' min='1'>1</level>
+    <fgcolor>blue</fgcolor>
+  </gui>
+  <net>
+    <proxy>
+      <url>http://www.url1.org</url>
+      <url>http://www.url2.org</url>
+      <url>http://www.url3.org</url>
+    </proxy>
+    <service>
+      <url>http://service1.org</url>
+    </service>
+    <server>
+      <url>http://appsvr1.com</url>
+      <url>http://appsvr2.com</url>
+      <url>http://testsvr.com</url>
+      <url>http://backupsvr.com</url>
+    </server>
+  </net>
+  <base>
+    <services>
+      <security>
+        <login>
+          <user type='default'>Admin</user>
+          <passwd type='secret'></passwd>
+        </login>
+      </security>
+    </services>
+  </base>
+  <database>
+    <tables>
+      <table id='1'>
+        <name>documents</name>
+        <fields>
+          <field>
+            <name>docid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>docname</name>
+            <type>varchar</type>
+          </field>
+          <field>
+            <name>authorID</name>
+            <type>int</type>
+          </field>
+        </fields>
+      </table>
+      <table id='2'>
+        <name>tasks</name>
+        <fields>
+          <field>
+            <name>taskid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>taskname</name>
+            <type>varchar</type>
+          </field>
+        </fields>
+      </table>
+    </tables>
+  </database>
+  <Channels>
+    <Channel id='1' type='half'>
+      <Name>My Channel</Name>
+      <ChannelData>test 1 data</ChannelData>
+    </Channel>
+    <Channel id='2' type='full'>
+      <MoreChannelData>more test 2 data</MoreChannelData>
+      <Name>Channel 2</Name>
+      <ChannelData>test 2 data</ChannelData>
+    </Channel>
+    <Channel id='3' type='half'>
+      <Name>Test Channel</Name>
+    </Channel>
+    <Channel id='3' type='full'>
+      <Name>Channel 3</Name>
+      <ChannelData>test 3 data</ChannelData>
+    </Channel>
+  </Channels>
+</config>
+]]></source></td><td width="60%">
+      <p>
+        The features that are significant in this file are:
+        <ul>
+          <li>In the gui section the elements were merged.</li>
+          <li>In the net section the elements were merged, with the exception of the urls.</li>
+          <li>In the security section the user and password were merged. Notice that the
+          empty value for the password from the first file overrode the password in the
+          second file.</li>
+          <li>Both table elements appear</li>
+          <li>Channel 1 and Channel 2 were merged</li>
+          <li>Both Channel 3 elements appear as they were determined to not be the same.</li>
+        </ul>
+      </p>
+      <p>
+        When merging elements attributes play a critical role. If an element has an attribute that
+        appears in both sources, the value of that attribute must be the same for the elements to be
+        merged.
+      </p>
+      <p>
+        Merging is only allowed between a single node in each of the files, so if an element
+        in the first file matches more than one element in the second file no merging will take
+        place and the element from the first file (and its contents) are included and the elements
+        in the second file are not. If the element is marked as a list node then the elements from
+        the second file will also be included.
+      </p></td></tr></table>
+    </subsection>
+
+	<subsection name="Constructing a CombinedConfiguration">
+	<p>
+      To create a <code>CombinedConfiguration</code> object you specify the node
+      combiner to use and then add an arbitrary number of configurations. We will
+      show how to construct a union configuration from the two example sources
+      introduced earlier:
+    </p>
+<source><![CDATA[
+// Load the source configurations
+XMLConfiguration conf1 = new XMLConfiguration("table1.xml");
+XMLConfiguration conf2 = new XMLConfiguration("table2.xml");
+
+// Create and initialize the node combiner
+NodeCombiner combiner = new UnionCombiner();
+combiner.addListNode("table");  // mark table as list node
+            // this is needed only if there are ambiguities
+
+// Construct the combined configuration
+CombinedConfiguration cc = new CombinedConfiguration(combiner);
+cc.addConfiguration(conf1, "tab1");
+cc.addConfiguration(conf2);
+]]></source>
+    <p>
+      Here we also specified a name for one of the configurations, so it can
+      later be accessed by <code>cc.getConfiguration("tab1");</code>. Access by
+      index is also supported. After that the properties in the combined
+      configuration can be accessed as if it were a normal hierarchical
+      configuration
+    </p>
+    </subsection>
+    
+    <subsection name="Dealing with changes">
+    <p>
+      There is nothing that prevents you from updating a combined configuration,
+      e.g. by calling methods like <code>addProperty()</code> or
+      <code>removeProperty()</code>. The problem is that depending on the used
+      node combiner it might no be clear, which of the contained configurations
+      will be modified or whether one is modified at all.
+    </p>
+    <p>
+      Typical node combiners work by copying parts of the node structures of
+      the source configurations into the target structure and linking them
+      togehter using special link nodes. So updates of the combined node structure
+      will either effect nodes from one of the contained configuration (then
+      the changes are directly visible in this configuration) or one of the link
+      nodes (then they cannot really be saved).
+    </p>
+    <p>
+      It is also possible that a change is done at the combined node structure,
+      which is not compatible with the current node combiner. Imagine that an
+      <code>OverrideCombiner</code> is used and that a
+      property should be removed. This property will then be removed from one
+      of the contained configurations. Now it may happen that this removed
+      property had hidden property values of other contained configurations.
+      Their values won't become visible automatically, but only after the
+      combined view was re-constructed.
+    </p>
+    <p>
+      Because of that it is recommended that changes are not done at the
+      combined configuration, but only at contained configurations. This way
+      the correct configuration to be updated can unambigously be identified.
+      Obtaining the configuration to be updated from the combined configuration
+      is easy when it was given a name.
+    </p>
+    </subsection>
+	</section>
+</body>
+
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/userguide/howto_compositeconfiguration.xml b/src/site/xdoc/userguide/howto_compositeconfiguration.xml
new file mode 100644
index 0000000..e1e6ed2
--- /dev/null
+++ b/src/site/xdoc/userguide/howto_compositeconfiguration.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<document>
+
+ <properties>
+  <title>Composite Configuration Details</title>
+  <author email="epugh at opensourceconnections.com">Eric Pugh</author>
+ </properties>
+
+<body>
+
+    <section name="Composite Configuration Details">
+        <p>
+            There are many use cases when you want to collect the properties
+            of several configuration sources and access them like a single
+            configuration object. One way to do that is using the
+            <code><a href="../apidocs/org/apache/commons/configuration/CompositeConfiguration.html">
+            CompositeConfiguration</a></code> class.
+        </p>
+        <p>
+            A <code>CompositeConfiguration</code> object contains a list of
+            other configuration objects. When properties are accessed from a
+            composite configuration the object takes the passed in property key
+            and iterates over the list of the contained configurations. As soon
+            as a value is found for the key it is returned. This means that a
+            <code>CompositeConfiguration</code> implements a kind of override
+            semantics, i.e. the properties of configurations that were added
+            first hide the property values of configurations added later.
+        </p>
+    	<p>
+    		We will discuss how you can establish a "default" choice for your
+    		Composite Configuration as well as save changes made to your
+    		Composite Configuration.
+    	</p>
+    	<subsection name="Setting Up Defaults">
+    		<p>
+    			Defaults are very simple.  You can just add them as your last configuration object, 
+    			either through the ConfigurationFactory or manually:
+    		</p>
+    			<source><![CDATA[
+Configuration defaults = new PropertiesConfiguration(fileToDefaults);
+Configuration otherProperties = new PropertiesConfiguration(fileToOtherProperties);
+CompositeConfiguration cc = new CompositeConfiguration();
+cc.addConfiguration(otherProperties);
+cc.addConfiguration(fileToDefaults);
+]]></source>
+		</subsection>
+		
+		<subsection name="Saving Changes">
+			<p>
+				If you have a non static Configuration where you want to 
+				save changes made to a configuration, and you are using a
+				CompositeConfiguration, then you will need to pass into
+				the constructor of the CompositeConfiguration what Configuration
+				to save the changes via.
+			</p>
+    			<source><![CDATA[
+PropertiesConfiguration saveConfiguration = new PropertiesConfiguration(fileToSaveChangesIn);
+Configuration cc = new CompositeConfiguration(saveConfiguration);
+cc.setProperty("newProperty","new value");
+
+saveConfiguration.save();
+]]></source>
+			<p>
+				Alternatively, you can just request the
+				in-memory configuration that stores the changes. The following
+                example fragment copies all properties from the in-memory
+                configuration into a <code>DatabaseConfiguration</code>, so
+                that they are made persistent:
+    			<source><![CDATA[
+Configuration changes = myCompositeConfiguration.getInMemoryConfiguration();
+DatabaseConfiguration config = new DatabaseConfiguration(datasource, "configuration", "key", "value");
+for (Iterator<String> i = changes.getKeys(); i.hasNext()){
+	String key = i.next();
+	Object value = changes.get(key);
+	config.setProperty(key,value);
+}
+]]></source>
+			</p>
+
+		</subsection>
+	</section>
+</body>
+
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/userguide/howto_configurationbuilder.xml b/src/site/xdoc/userguide/howto_configurationbuilder.xml
new file mode 100644
index 0000000..cc90ea1
--- /dev/null
+++ b/src/site/xdoc/userguide/howto_configurationbuilder.xml
@@ -0,0 +1,894 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- $Id: howto_configurationbuilder.xml 1156501 2011-08-11 06:22:49Z oheger $ -->
+
+<document>
+
+ <properties>
+  <title>Configuration Builder Howto</title>
+ </properties>
+
+<body>
+	<section name="Using DefaultConfigurationBuilder">
+    <p>
+      This section explains how a
+      <code><a href="../apidocs/org/apache/commons/configuration/DefaultConfigurationBuilder.html">
+      DefaultConfigurationBuilder</a></code>object is setup that provides
+      access to a collection of different configuration sources.
+      <code>DefaultConfigurationBuilder</code> is the option of choice for
+      applications that have to deal with multiple configuration sources. It
+      provides the following features:
+      <ul>
+        <li>Various configuration sources can be combined to a single
+        <a href="howto_combinedconfiguration.html#Combined Configuration">
+        CombinedConfiguration</a> object. This is a truly hierarchical
+        configuration supporting enhanced query facilities.</li>
+        <li>As configuration sources the most relevant <code>Configuration</code>
+        implementations provided by this library are supported. Sources are
+        defined as <a href="howto_beans.html#Declaring and Creating Beans">bean
+        declarations</a>, so complex initializations are possible.</li>
+        <li>Meta data can be provided to fine-tune the constructed
+        configuration.</li>
+        <li><code>DefaultConfigurationBuilder</code> is extensible. Custom
+        configuration sources can be added.</li>
+      </ul>
+    </p>
+    <p>
+      This document starts with some explanations of
+      <code>DefaultConfigurationBuilder</code> basics. Then the <em>configuration
+      definition files</em> processed by <code>DefaultConfigurationBuilder</code>
+      are discussed in detail. Finally an advanced example is presented.
+    </p>
+
+    <subsection name="The configuration definition file">
+    <p>
+      In previous chapters we have already seen how specific configuration
+      classes like <code>PropertiesConfiguration</code> or
+      <code>XMLConfiguration</code> can be used to load configuration data from
+      a single source. This may be sufficient for small applications, but if
+      requirements for configuration become more complex, additional support
+      for managing a set of different configuration sources is desired. This is
+      the domain of <code>DefaultConfigurationBuilder</code> which allows
+      combining multiple configuration sources. The properties defined in these
+      sources can then be accessed as if they were defined in a single
+      configuration file. The sources to be loaded have to be defined in a
+      XML document with a specific structure, a so-called <em>configuration
+      definition file</em>. The following listing shows a simple example of such
+      a definition file:
+    </p>
+    <source><![CDATA[
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+
+<configuration>
+  <properties fileName="usergui.properties"/>
+</configuration>
+]]></source>
+    <p>
+      A configuration definition file can contain an arbitrary number of
+      elements declaring the configuration sources to load. The
+      <code><properties></code> element is one of these; it is used to
+      include properties files. For this example we store the definition file
+      in the same directory as the properties file and call it
+      <code>config.xml</code>. The properties file used in this example is the
+      same as in the section about <a href="howto_properties.html">properties
+      files</a>.
+    </p>
+    </subsection>
+
+    <subsection name="Setting up a DefaultConfigurationBuilder">
+    <p>
+      Now we have to create a <code>DefaultConfigurationBuilder</code> object
+      and let it read this definition file. This is quite simple: Just create a
+      new instance and set the name of the definition file
+      (<code>DefaultConfigurationBuilder</code> is derived from
+      <code>XMLConfiguration</code>, so all options for specifying the document
+      to load are available here, too). The combined configuration collecting
+      all sources defined in the configuration definition file can then be
+      obtained by calling the <code>getConfiguration()</code> method:
+    </p>
+    <source><![CDATA[
+DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
+builder.setFile(new File("config.xml"));
+Configuration config = builder.getConfiguration(true);
+]]></source>
+    <p>
+      Now the <em>config</em> object can be accessed in the usual way to query
+      configuration properties, e.g. by using methods like <code>getString()</code>,
+      or <code>getInt()</code>. We will see in a moment how properties defined
+      in different configuration sources are accessed.
+    </p>
+    </subsection>
+
+    <subsection name="Overriding properties">
+    <p>
+      Using <code>DefaultConfigurationBuilder</code> to collect configuration
+      sources does not make much sense if there is only a single source to be
+      loaded. So let's add another one! This time we will embedd a XML file:
+      <em>gui.xml</em> which is shown in the next listing:
+    </p>
+    <source><![CDATA[
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<gui-definition>
+  <colors>
+    <background>#808080</background>
+    <text>#000000</text>
+    <header>#008000</header>
+    <link normal="#000080" visited="#800080"/>
+  </colors>
+  <rowsPerPage>15</rowsPerPage>
+</gui-definition>
+]]></source>
+    <p>
+      To make this XML document part of our global configuration we	have to
+      modify our configuration definition file to also include the new file. For
+      XML documents the element <code><xml></code> can be used so that we
+      ave now:
+    </p>
+    <source><![CDATA[
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+
+<configuration>
+  <properties fileName="usergui.properties"/>
+  <xml fileName="gui.xml"/>
+</configuration>
+]]></source>
+    <p>
+      The code for setting up the <code>DefaultConfigurationBuilder</code>
+      object remains the same. From the <code>Configuration</code> object
+      returned by the factory the new properties can be accessed in the usual
+      way.
+    </p>
+    <p>
+      There is one problem with this example configuration setup: The
+      <code>color.background</code> property is defined in both the properties
+      and the XML file, and - to make things worse - with different values.
+      Which value will be returned by a call to <code>getString()</code>?
+    </p>
+    <p>
+      The answer is that the configuration sources are searched in the order
+      they are defined in the configuration definition file. Here the properties
+      file is included first, then comes the XML file. Because the
+      <code>color.background</code> property can be found in the properties file
+      the value specified there will be returned (which happens to be
+      <code>#FFFFFF</code>).
+    </p>
+    <p>
+      It might not be obvious why it makes sense to define the value of one and
+      the same property in multiple configuration sources. But consider the
+      following scenario: An application comes with a set of default properties
+      and allows the user to override some or all of them. This can now easily
+      be realized by saving the user's settings in one file and the default
+      settings in another. Then in the configuration definition file the file
+      with the user settings is included first and after that the file with the
+      default values. The application code that queries these settings needs no
+      be aware whether a property was overriden by the user. <code>DefaultConfigurationBuilder</code>
+      takes care that properties defined in the first file (the user file) are
+      found; other properties which the user has not changed will still be
+      returned from the second file (the defaults file).
+    </p>
+    </subsection>
+
+    <subsection name="Optional configuration sources">
+    <p>
+      The example above with two configuration sources - one for user settings
+      and one with default values - raises an interesting question: What happens
+      if the user has not defined specific properties yet? Or what if a new user
+      starts our application for the first time and thus no user specific
+      properties exist?
+    </p>
+    <p>
+      The default behavior of <code>DefaultConfigurationBuilder</code> is to
+      throw a <code>ConfigurationException</code> exception if one of the sources
+      defined in the configuration definition file cannot be loaded. For our
+      example this behavior is not desired: the properties file with specific user
+      settings is not required. If it cannot be loaded, the example application
+      should still work because a complete set of configuration properties is
+      defined in the second file.
+    </p>
+    <p>
+      <code>DefaultConfigurationBuilder</code> supports such optional configuration
+      sources. For this purpose in the definition of a configuration source the
+      <code>config-optional</code> attribute can be placed. An example of this
+      is shown below:
+    </p>
+    <source><![CDATA[
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+
+<configuration>
+  <properties fileName="usersettings.properties" config-optional="true"/>
+  <properties fileName="default.properties"/>
+</configuration>
+]]></source>
+    <p>
+      In this configuration definition file the first properties file with user
+      specific settings is marked as optional. This means that if it cannot be
+      loaded, <code>DefaultConfigurationBuilder</code> will not throw an exception,
+      but only write a warning message to its logger. Note that the
+      <code>config-optional</code> attribute is absent for the second properties
+      file. Thus it is mandatory, and the <code>getConfiguration()</code> method
+      of <code>DefaultConfigurationBuilder</code> would throw an exception if it
+      could not be found.
+    </p>
+    </subsection>
+
+    <subsection name="Union configuration">
+    <p>
+      In an earlier section about the configuration definition file for
+      <code>DefaultConfigurationBuilder</code> it was stated that configuration
+      files included first can override properties in configuraton files
+      included later, and an example use case for this behaviour was given. There
+      may be cases when there are other requirements.
+    </p>
+    <p>
+      Let's continue the example with the application that somehow process
+      database tables and that reads the definitions of the affected tables from
+      its configuration. This example and the corresponding XML configuration
+      files were introduced in the section about <a href="howto_xml.html">XMLConfiguration</a>.
+      Now consider that this application grows larger and must be maintained by
+      a team of developers. Each developer works on a separated set of tables.
+      In such a scenario it would be problematic if the definitions for all
+      tables would be kept in a single file. It can be expected that this file
+      needs to be changed very often and thus can be a bottleneck for team
+      development when it is nearly steadily checked out. It would be much better
+      if each developer had an associated file with table definitions and all
+      these information could be linked together at the end.
+    </p>
+    <p>
+      <code>DefaultConfigurationBuilder</code> provides support for such a use case,
+      too. It is possible to specify in the configuration definition file that
+      from a set of configuration sources a logic union configuration is to be
+      constructed. Then all properties defined in the provided sources are
+      collected and can be accessed as if they had been defined in a single source.
+      To demonstrate this feature let us assume that a developer of the database
+      application has defined a specific XML file with a table definition named
+      <code>tasktables.xml</code>:
+    </p>
+    <source><![CDATA[
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+
+<config>
+  <table tableType="application">
+    <name>tasks</name>
+    <fields>
+      <field>
+        <name>taskid</name>
+        <type>long</type>
+      </field>
+      <field>
+        <name>name</name>
+        <type>java.lang.String</type>
+      </field>
+      <field>
+        <name>description</name>
+        <type>java.lang.String</type>
+      </field>
+      <field>
+        <name>responsibleID</name>
+        <type>long</type>
+      </field>
+      <field>
+        <name>creatorID</name>
+        <type>long</type>
+      </field>
+      <field>
+        <name>startDate</name>
+        <type>java.util.Date</type>
+      </field>
+      <field>
+        <name>endDate</name>
+        <type>java.util.Date</type>
+      </field>
+    </fields>
+  </table>
+</config>
+]]></source>
+    <p>
+      This file defines the structure of an additional table, which should be
+      added to the so far existing table definitions. To achieve this the
+      configuration definition file has to be changed: A new section is added
+      that contains the include elements of all configuration sources which
+      are to be combined.
+    </p>
+    <source><![CDATA[
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!-- Configuration definition file that demonstrates the
+     override and additional sections -->
+
+<configuration>
+  <override>
+    <properties fileName="usergui.properties"/>
+    <xml fileName="gui.xml"/>
+  </override>
+
+  <additional>
+    <xml fileName="tables.xml"/>
+    <xml fileName="tasktables.xml" config-at="tables"/>
+  </additional>
+</configuration>
+]]></source>
+    <p>
+      Compared to the older versions of this file some changes have been done.
+      One major difference is that the elements for including configuration
+      sources are no longer direct children of the root element, but are now
+      contained in either an <code><override></code> or <code><additional></code>
+      section. The names of these sections already imply their purpose.
+    </p>
+    <p>
+      The <code>override</code> section is not strictly necessary. Elements in
+      this section are treated as if they were children of the root element, i.e.
+      properties in the included configuration sources override properties in
+      sources included later. So the <code><override></code> tags could have
+      been ommitted, but for the sake of clearity it is recommended to use them
+      if there is also an <code><additional></code> section.
+    </p>
+    <p>
+      It is the <code><additonal></code> section that introduces a new behaviour.
+      All configuration sources listed here are combined to a union configuration.
+      In our example we have put two <code>xml</code> elements in this area
+      that load the available files with database table definitions. The syntax
+      of elements in the <code>additional</code> section is analogous to the
+      syntax described so far. In this example the <code>config-at</code>
+      attribute is introduced. It specifies the position in the logic union
+      configuration where the included properties are to be added. Here it is set
+      for the second element to the value <em>tables</em>. This is because the
+      file starts with a <code><table></code> element, but to be compatible
+      with the other table definition file it should be accessable under the key
+      <code>tables.table</code>.
+    </p>
+    <p>
+      After these modifications have been performed, the configuration obtained
+      from <code>DefaultConfigurationBuilder</code> allows access to three database
+      tables. A call of <code>config.getString("tables.table(2).name");</code>
+      results in a value of <em>tasks</em>. In an analogous way it is possible
+      to retrieve the fields of the third table.
+    </p>
+    <p>
+      Note that it is also possible to override properties defined in an
+      <code>additonal</code> section. This can be done by placing a configuration
+      source in the <code>override</code> section that defines properties that
+      are also defined in one of the sources listed in the <code>additional</code>
+      section. The example does not make use of that. Note also that the order of
+      the <code>override</code> and <code>additional</code> sections in a
+      configuration definition file does not matter. Sources in an <code>override</code>
+      section are always treated with higher priority (otherwise they could not
+      override the values of other sources).
+    </p>
+    </subsection>
+
+   <subsection name="Configuration definition file reference">
+    <p>
+      Configuration definition files are XML documents telling
+      <code>DefaultConfigurationBuilder</code> which configuration sources to
+      load and how to process them in order to create the resulting combined
+      configuration.
+    </p>
+    <p>
+      <strong>Overall structure of a configuration definition file</strong>
+    </p>
+    <p>
+      A configuration definition file for <code>DefaultConfigurationBuilder</code>
+      can contain three sections, all of which are optional. A skeleton looks as
+      follows:
+    </p>
+    <source><![CDATA[
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+
+<configuration systemProperties="path to property file">
+  <header>
+    <!-- Meta data about the resulting combined configuration -->
+  </header>
+  <override>
+    <!-- Configuration declarations with override semantics -->
+  </override>
+  <additional>
+    <!-- Configuration declarations that form a union configuration -->
+  </additional>
+</configuration>
+]]></source>
+    <p>
+      <strong>Declaring configuration sources</strong>
+    </p>
+    <p>
+      The <code>override</code> and <code>additional</code> sections have already
+      been introduced when the basics of <code>DefaultConfigurationBuilder</code>
+      were discussed. They contain declarations for the configuration sources to be
+      embedded. For convenience reasons it is also possible to declare
+      configuration sources outside these sections; they are then treated as if
+      they were placed inside the <code>override</code> section.
+    </p>
+    <p>
+      Each declaration of a configuration source is represented by an XML
+      element whose name determines the type of the configuration source.
+      Attributes or nested elements can be used to provide additional
+      configuration options for the sources to be included (e.g. a name of a
+      file to be loaded or a reloading strategy). Below is a list of all
+      tags which can be used out of the box:
+    </p>
+    <p>
+      <dl>
+        <dt>properties</dt>
+        <dd>With this element properties files can be included. The name of
+        the file to load is specified using the <code>fileName</code>
+        attribute. Which configuration class is created by this tag
+        depends on the extension of the file to load: If the extension
+        is ".xml", a <code>XMLPropertiesConfiguration</code> object is
+        created, which is able to process the XML properties format
+        introduced in Java 5.0. Otherwise a <code>PropertiesConfiguration</code>
+        object is created, the default reader for properties files.</dd>
+        <dt>xml</dt>
+        <dd>The <code>xml</code> element can be used to load XML configuration
+        files. It also uses the <code>fileName</code> attribute to
+        determine the name of the file to load and creates an instance
+        of <code>XMLConfiguration</code>.</dd>
+        <dt>jndi</dt>
+        <dd>As the name implies, with this element JNDI resources can be
+        included in the resulting configuration. Under the hood this is
+        done by an instance of the <code>JNDIConfiguration</code>
+        class. The <code>prefix</code> attribute can be used to
+        select a subset of the JNDI tree.</dd>
+        <dt>plist</dt>
+        <dd>The <code>plist</code> element allows to embedd configuration
+        files in the NeXT / OpenStep or Mac OS X format. Again the
+        name of the file to load is specified through the
+        <code>fileName</code> attribute. If a XML file is specified,
+        a <code>XMLPropertyListConfiguration</code> object is created
+        to process the file. Otherwise this task is delegated to a
+        <code>PropertyListConfiguration</code> instance.</dd>
+        <dt>system</dt>
+        <dd>With this element an instance of <code>SystemConfiguration</code>
+        is added to the resulting configuration allowing access to
+        system properties. <em>Note:</em> Using this element system properties
+        are directly made available. Alternatively the
+        interpolation features introduced in version 1.4 (see
+        <a href="howto_basicfeatures.html#Variable_Interpolation">
+        Variable Interpolation</a> for more details) can be used for referencing
+        system properties.</dd>
+        <dt>configuration</dt>
+        <dd>The <code>configuration</code> tag allows other configuration
+        definition files to be included. This makes it possible to nest these
+        definition files up to an arbitrary depth. In fact, this tag will
+        create another <code>DefaultConfigurationBuilder</code> object,
+        initialize it, and obtain the <code>CombinedConfiguation</code> from it.
+        This combined configuration will then be added to the resulting
+        combined configuration. Like all file-based configurations the
+        <code>fileName</code> attribute can be used to specify the configuration
+        definition file to be loaded. This file must be an XML document that
+        conforms to the format described here. Some of the most important
+        settings are copied from the original <code>DefaultConfigurationBuilder</code>
+        object to the newly created builder:
+        <ul>
+        <li>the base path under which configuration files are searched</li>
+        <li>some flags, e.g. for controlling delimiter parsing or throwing
+        exceptions on missing properties</li>
+        <li>the logger</li>
+        <li>the configuration and error listeners</li>
+        </ul>
+        </dd>
+        <dt>ini</dt>
+        <dd>This tag can be used to include an ini file into the resulting
+        combined configuration. Behind the scenes an instance of
+        <code><a href="../apidocs/org/apache/commons/configuration/HierarchicalINIConfiguration.html">
+        HierarchicalINIConfiguration</a></code> is used to load the ini file.</dd>
+        <dt>env</dt>
+        <dd>With this tag direct access to environment properties can be enabled.
+        This works in the same way as the <code><system></code> tag for
+        Java system properties.</dd>
+      </dl>
+    </p>
+    <p>
+      In the declaration of a configuration source it is possible to set
+      properties on the corresponding configuration objects. Configuration
+      declarations are indeed
+      <a href="howto_beans.html#Declaring and Creating Beans">Bean
+      declarations</a>. That means they can have attributes matching simple
+      properties of the configuration object to create, and sub elements
+      matching complex properties. The following example fragment shows how
+      complex initialization can be performed in a configuration declaration:
+    </p>
+    <source><![CDATA[
+  <properties fileName="test.properties" throwExceptionOnMissing="true">
+    <reloadingStrategy refreshDelay="10000"
+    config-class="org.apache.commons.configuration.reloading.FileChangedReloadingStrategy"/>
+  </properties>
+  <xml fileName="test.xml" delimiterParsingDisabled="true">
+    <expressionEngine config-class="org.apache.commons.configuration.tree.DefaultExpressionEngine"
+      propertyDelimiter="/" indexStart="[" indexEnd="]"/>
+  </xml>
+]]></source>
+    <p>
+      In this example a configuration source for a properties file and one for
+      an XML document are defined. For the properties source the
+      <code>throwExceptionOnMissing</code> property is set to <b>true</b>,
+      which means that it should throw an exception if a requested property is
+      not found. In addition it is assigned a reloading strategy, which is
+      declared and configured in a sub element. The XML configuration source is
+      initialized in a similar way: a simple property is set, and an expression
+      engine is assigned. More information about the format for declaring objects
+      and initializing their properties can be found in the section about
+      <a href="howto_beans.html#Declaring and Creating Beans">bean
+      declarations</a>.
+    </p>
+    <p>
+      In addition to the attributes that correspond to properties of the
+      configuration object to be created, a configuration declaration can have a
+      set of special attributes that are evaluated by
+      <code>DefaultConfigurationBuilder</code> when it creates the objects.
+      These attributes are listed in the following table:
+    </p>
+    <p>
+    <table border="1">
+    <tr>
+      <th>Attribute</th>
+      <th>Meaning</th>
+    </tr>
+    <tr>
+      <td valign="top"><code>config-name</code></td>
+      <td>Allows a name to be specified for this configuration. This name can
+      be used to obtain a reference to the configuration from the resulting
+      combined configuration (see below).</td>
+    </tr>
+    <tr>
+      <td valign="top"><code>config-at</code></td>
+      <td>With this attribute an optional prefix can be specified for the
+      properties of the corresponding configuration.</td>
+    </tr>
+    <tr>
+      <td valign="top"><code>config-optional</code></td>
+      <td>Declares a configuration as optional. This means that errors that
+      occur when creating the configuration are silently ignored. The default
+      behavior when an error occurs is that no configuration is added to
+      the resulting combined configuration. This behavior can be used to find
+      out whether an optional configuration could be successfully created or
+      not. If you specify a name for the optional configuration (using the
+      <code>config-name</code> attribute), you can later check whether the
+      combined configuration contains a configuration with this name. With the
+      <code>config-forceCreate</code> attribute (see below) this default
+      behavior can be changed.</td>
+    </tr>
+    <tr>
+      <td valign="top"><code>config-forceCreate</code></td>
+      <td>This boolean attribute is only evaluated for configurations declared as
+      optional. It determines the behavior of the configuration builder when
+      the optional configuration could not be created. If set to <b>true</b>,
+      the builder tries to create an empty, uninitialized configuration of the
+      correct type and add it to the resulting combined configuration. This is
+      especially useful for file-based configurations. Consider a use case
+      where an application wants to store user specific configuration files in
+      the users' home directories. When a user starts this application for the
+      first time, the user configuration does not exist yet. If it is declared
+      as <em>optional</em> and <em>forceCreate</em>, the missing configuration
+      file won't cause an error, but an empty configuration will be created.
+      The application can then obtain this configuration, add properties to it
+      (e.g. user specific settings) and save it. Without the
+      <code>config-forceCreate</code> attribute the application would have to
+      check whether the user configuration exists in the combined configuration
+      and eventually create it manually. Note that not all configuration
+      providers support this mechanism. Sometimes it may not be possible to
+      create an empty configuration if the standard initialization fails. In
+      this case no configuration will be added to the combined configuration
+      (with other words: the <code>config-forceCreate</code> attribute will not
+      have any effect).</td>
+    </tr>
+    </table>
+    </p>
+    <p>
+      <em>Note:</em> In older versions of Commons Configuration the attributes
+      <code>config-at</code> and <code>config-optional</code> were named
+      <code>at</code> and <code>optional</code> respective. They have been
+      renamed in order to avoid possible name clashes with property names for
+      configuration sources. However, for reasons of backwards compatibility,
+      the old attribute names can still be used.
+    </p>
+    <p>
+      Another useful feature is the built-in support for interpolation (i.e.
+      variable substitution): You can use variables in your configuration
+      definition file that are defined in declared configuration sources. For
+      instance, if the name of a configuration file to be loaded is defined by
+      the system property <code>CONFIG_FILE</code>, you can do something like
+      this:
+    </p>
+    <source><![CDATA[
+<configuration>
+  <!-- Load the system properties -->
+  <system/>
+  <!-- Now load the config file, using a system property as file name -->
+  <properties fileName="${CONFIG_FILE}"/>
+</configuration>
+]]></source>
+    <p>
+      Note that you can refer only to properties that have already been loaded.
+      If you change the order of the <code><system></code> and the
+      <code><properties></code> elements in the example above, an error
+      will occur because the <code>${CONFIG_FILE}</code> variable will then be
+      undefined at the moment it is evaluated.
+    </p>
+     <source><![CDATA[
+<configuration systemProperties="systemProperties.xml">
+  <!-- Load the system properties -->
+  <system/>
+  <!-- Now load the config file, using a system property as file name -->
+  <properties fileName="${CONFIG_FILE}"/>
+</configuration>
+]]></source>
+    <p>
+      This example differs from the previous one by the <code>systemProperties</code>
+      attribute added to the root element. It causes the specified to be read
+      and all properties defined therein to be added to the system properties.
+      So properties like <em>CONFIG_FILE</em> can be defined in a properties
+      file and are then treated as if they were system properties.
+    </p>
+    <p>
+      <strong>The header section</strong>
+    </p>
+    <p>
+      In the header section properties of the resulting combined configuration
+      object can be set. The main part of this section is a bean declaration
+      that is used for creating the resulting configuration object. Other
+      elements can be used for customizing the
+      <a href="howto_combinedconfiguration.html#Node combiners">Node combiners</a>
+      used by the override and the union combined configuration. The following
+      example shows a header section that uses all supported properties:
+    </p>
+    <source><![CDATA[
+  <header>
+    <result delimiterParsingDisabled="true" forceReloadCheck="true">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.OverrideCombiner"/>
+      <expressionEngine config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <combiner>
+      <override>
+        <list-nodes>
+          <node>table</node>
+          <node>list</node>
+        </list-nodes>
+      </override>
+      <additional>
+        <list-nodes>
+          <node>table</node>
+        </list-nodes>
+      </additional>
+    </combiner>
+  </header>
+]]></source>
+    <p>
+      The <code>result</code> element points to the bean declaration for the
+      resulting combined configuration. In this example we set some attributes
+      and initialize the node combiner (which is not necessary because the
+      default override combiner is specified), and the expression engine to be
+      used. Note that the <code>config-class</code> attribute makes it
+      possible to inject custom classes for the resulting configuration or the
+      node combiner.
+    </p>
+    <p>
+      The <code>combiner</code> section allows nodes to be defined as list nodes.
+      This can be necessary for certain node combiner implementations to work
+      correctly. More information can be found in the section about
+      <a href="howto_combinedconfiguration.html#Node combiners">Node combiners</a>.
+    </p>
+    <p>
+      <em>Note:</em> From time to time the question is raised whether there is a
+      document type definition or a schema defining exactly the structure of a
+      configuration definition file. Frankly, the answer is no. This is due to
+      the fact that the format is extensible. As will be shown below, it is
+      possible to register yout own tags in order to embedd custom configuration
+      sources.
+    </p>
+    </subsection>
+
+    <subsection name="An example">
+    <p>
+      After all that theory let's go through a more complex example! We start
+      with the configuration definition file that looks like the following:
+    </p>
+    <source><![CDATA[
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true" forceReloadCheck="true">
+      <expressionEngine config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <combiner>
+      <additional>
+        <list-nodes>
+          <node>table</node>
+        </list-nodes>
+      </additional>
+    </combiner>
+  </header>
+  <override>
+    <properties fileName="user.properties" throwExceptionOnMissing="true"
+      config-name="properties" config-optional="true">
+      <reloadingStrategy refreshDelay="10000"
+      config-class="org.apache.commons.configuration.reloading.FileChangedReloadingStrategy"/>
+    </properties>
+    <xml fileName="settings.xml" config-name="xml"/>
+  </override>
+  <additional>
+    <xml config-name="tab1" fileName="table1.xml" config-at="database.tables"/>
+    <xml config-name="tab2" fileName="table2.xml" config-at="database.tables"
+        validating="true"/>
+  </additional>
+</configuration>
+]]></source>
+    <p>
+      This configuration definition file includes four configuration sources and
+      sets some properties for the resulting <code>CombinedConfiguration</code>.
+      Of special interest is the <code>forceReloadCheck</code> property, which
+      enables a special check for detecting property changes in the contained
+      configuration sources. If this property is not set, reloading won't work.
+      Because we have configured a reloading strategy for one of the included
+      configuration sources we have to set this flag so that this reloading
+      strategy can function properly. More details about this topic can be
+      found in the Javadocs for
+      <code><a href="../apidocs/org/apache/commons/configuration/CombinedConfiguration.html">
+      CombinedConfiguration</a></code>. We also set some properties for the
+      configurations to be loaded; for instance we declare that one of the XML
+      configurations should be validated.
+    </p>
+    <p>
+      With the following code we can create a <code>DefaultConfigurationBuilder</code>
+      and load this file:
+    </p>
+    <source><![CDATA[
+DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
+builder.setFile(new File("configuration.xml"));
+CombinedConfiguration cc = builder.getConfiguration(true);
+]]></source>
+    <p>
+      It would have been possible to specify the location of the configuration
+      definition file in multiple other ways, e.g. as a URL. The boolean argument
+      in the call to <code>getConfiguration()</code> determines whether the
+      configuration definition file should be loaded. For our simple example we
+      want this to happen, but it would also be possible to load the file
+      manually (by calling the <code>load()</code> method), and after that
+      updating the configuration. (Remember that <code>DefaultConfigurationBuilder</code>
+      is derived from <code>XMLConfiguration</code>, that means you can use all methods
+      provided by this class to alter its data, e.g. to add further configuration
+      sources.) If the configuration's data was manually changed, you should
+      call <code>getConfiguration()</code> with the argument <b>false</b>.
+      <code>XMLConfiguration</code> also provides the <code>registerEntityId()</code>
+      method that can be used to define the location of DTD files (refer to the
+      section <a href="howto_xml.html#Validation_of_XML_configuration_files">
+      Validation of XML configuration files</a> for more details). This method
+      is available for <code>DefaultConfigurationBuilder</code>, too. The
+      entities registered here will be passed to the loaded child XML
+      configurations. So you can register the DTDs of all child XML configurations
+      globally at the configuration builder.
+    </p>
+    <p>
+      In the <code>header</code> section we have chosen an XPATH expression
+      engine for the resulting configuration. So we can query our properties
+      using the convenient XPATH syntax. By providing the <code>config-name</code>
+      attribute we have given all configuration sources a name. This name can
+      be used to obtain the corresponding sources from the combined
+      configuration. For configurations in the override section this is
+      directly possible:
+    </p>
+    <source><![CDATA[
+Configuration propertiesConfig = cc.getConfiguration("properties");
+Configuration xmlConfig = cc.getConfiguration("xml");
+]]></source>
+    <p>
+      Configurations in the <code>additional</code> section are treated a bit
+      differently: they are all packed together in another combined configuration
+      and then added to the resulting combined configuration. So in our example
+      the combined configuration <code>cc</code> will contain three configurations:
+      the two configurations from the override section, and the combined
+      configuration with the <code>additional</code> configurations. The latter
+      is stored under a name determined by the <code>ADDITIONAL_NAME</code>
+      constant of <code>DefaultConfigurationBuilder</code>. The following
+      code shows how the configurations of the <code>additional</code> section
+      can be accessed:
+    </p>
+    <source><![CDATA[
+CombinedConfiguration ccAdd = (CombinedConfiguration)
+  cc.getConfiguration(DefaultConfigurationBuilder.ADDITIONAL_NAME);
+Configuration tab1Config = ccAdd.getConfiguration("tab1");
+Configuration tab2Config = ccAdd.getConfiguration("tab2");
+]]></source>
+    </subsection>
+    
+    <subsection name="Extending the configuration definition file format">
+    <p>
+      If you have written a custom configuration class, you might want to
+      declare instances of this class in a configuration definition file, too.
+      With <code>DefaultConfigurationBuilder</code> this is now possible by
+      registering a <em>ConfigurationProvider</em>.
+    </p>
+    <p>
+      <code>ConfigurationProvider</code> is an inner class defined in
+      <code>DefaultConfigurationBuilder</code>. Its task is to create and
+      initialize a configuration object. Whenever <code>DefaultConfigurationBuilder</code>
+      encounters a tag in the <code>override</code> or the <code>additional</code>
+      section it checks whether for this tag a <code>ConfigurationProvider</code>
+      was registered. If this is the case, the provider is asked to create a
+      new configuration instance; otherwise an exception will be thrown.
+    </p>
+    <p>
+      So for adding support for a new configuration class you have to create an
+      instance of <code>ConfigurationProvider</code> (or a derived class) and
+      register it at the configuration builder using the
+      <code>addConfigurationProvider()</code> method. This method expects the
+      name of the associated tag and the provider instance as arguments.
+    </p>
+    <p>
+      If your custom configuration class does not need any special initialization,
+      you can use the <code>ConfigurationProvider</code> class directly. It is
+      able of creating an instance of a specified class (which must be derived
+      from <code>AbstractConfiguration</code>). Let's take a look at an example
+      where we want to add support for a configuration class called
+      <code>MyConfiguration</code>. The corresponding tag in the configuration
+      definition file should have the name <code>myconfig</code>. The code for
+      registering the new provider and loading the configuration definition file
+      looks as follows:
+    </p>
+    <source><![CDATA[
+DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
+DefaultConfigurationBuilder.ConfigurationProvider provider = new
+  DefaultConfigurationBuilder.ConfigurationProvider(MyConfiguration.class);
+builder.addConfigurationProvider("myconfig", provider);
+
+builder.setFileName("configuration.xml");
+Configuration config = builder.getConfiguration();
+]]></source>
+    <p>
+      If your configuration provider is registered this way, your configuration
+      definition file can contain the <code>myconfig</code> tag just as any
+      other tag for declaring a configuration source:
+    </p>
+    <source><![CDATA[
+<configuration>
+  <additional>
+    <xml fileName="settings.xml"/>
+    <myconfig delimiterParsingDisabled="true"/>
+  </additional>
+</configuration>
+]]></source>
+    <p>
+      As is demonstrated in this example, it is possible to specify attributes
+      for initializing properties of your configuration object. In this example
+      we set the default <code>delimiterParsingDisabled</code> property
+      inherited from <code>AbstractConfiguration</code>. Of course you can set
+      custom properties of your configuration class, too.
+    </p>
+    <p>
+      If your custom configuration class is a file-based configuration, you
+      should use the <code>FileConfigurationProvider</code> class instead of
+      <code>ConfigurationProvider</code>. <code>FileConfigurationProvider</code>
+      is another inner class of <code>DefaultConfigurationBuilder</code> that
+      knows how to deal with file-based configurations: it ensures that the
+      correct base path is set and takes care of invoking the <code>load()</code>
+      method.
+    </p>
+    <p>
+      If your custom configuration class requires special initialization, you
+      need to create your own provider class that extends
+      <code>ConfigurationProvider</code>. Here you will have to override the
+      <code>getConfiguration(ConfigurationDeclaration)</code> method, which is
+      responsible for creating the configuration instance (all information
+      necessary for this purpose can be obtained from the passed in declaration
+      object). It is recommended that you call the inherited method first,
+      which will instantiate and initialize the new configuration object. Then
+      you can perform your specific initialization.
+    </p>
+    </subsection>
+    </section>
+</body>
+
+</document>
diff --git a/src/site/xdoc/userguide/howto_events.xml b/src/site/xdoc/userguide/howto_events.xml
new file mode 100644
index 0000000..ab8a951
--- /dev/null
+++ b/src/site/xdoc/userguide/howto_events.xml
@@ -0,0 +1,240 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<document>
+
+ <properties>
+  <title>Configuration Events Howto</title>
+  <author email="oheger at apache.org">Oliver Heger</author>
+ </properties>
+
+<body>
+	<section name="Configuration Events">
+    <p>
+      All configuration classes derived from
+      <code><a href="../apidocs/org/apache/commons/configuration/AbstractConfiguration.html">
+      AbstractConfiguration</a></code> allow to register event listeners, which
+      are notified whenever the configuration's data is changed. This provides
+      an easy means for tracking updates on a configuration.
+    </p>
+
+    <subsection name="Configuration listeners">
+	<p>
+      Objects that are interested in update events triggered by configurations
+      must implement the
+      <code><a href="../apidocs/org/apache/commons/configuration/event/ConfigurationListener.html">
+      ConfigurationListener</a></code> interface. This interface defines a
+      single method <code>configurationChanged()</code>, which is passed a
+      <code><a href="../apidocs/org/apache/commons/configuration/event/ConfigurationEvent.html">
+      ConfigurationEvent</a></code> object. The event object contains all
+      information available about the modification, including:
+      <ul>
+        <li>A source object, which is usually the configuration object that was
+        modified.</li>
+        <li>The event's type. This is a numeric value that corresponds to
+        constant declarations in concrete configuration classes. It describes
+        what exactly has happended.</li>
+        <li>If available, the name of the property whose modification caused the
+        event.</li>
+        <li>If available, the value of the property that caused this event.</li>
+        <li>A flag whether this event was generated before or after the update
+        of the source configuration. A modification of a configuration typically
+        causes two events: one event before and one event after the modification
+        is performed. This allows event listeners to react at the correct point
+        of time.</li>
+      </ul>
+      Depending on the event type not all of this data may be available.
+    </p>
+    <p>
+      For resolving the numeric event type use constants defined in
+      <code>AbstractConfiguration</code> or derived classes. These constants
+      start with the prefix <code>EVENT_</code> and have a speaking name. Here
+      is an incomplete list of available event types with the configuration
+      classes, in which they are defined:
+    </p>
+    <p>
+      <dl>
+        <dt>AbstractConfiguration</dt>
+        <dd>EVENT_ADD_PROPERTY (a property was added; the name of the affected
+        property and the value that was added can be obtained from the
+        event object), EVENT_SET_PROPERTY (a property's value was changed; the
+        event object stores the name of the affected property and its new value),
+        EVENT_CLEAR_PROPERTY (a property was removed from the configuration;
+        its name is stored in the event object), EVENT_CLEAR (the configuration
+        was cleared)</dd>
+        <dt>AbstractFileConfiguration</dt>
+        <dd>EVENT_RELOAD (the configuration was reloaded)</dd>
+        <dt>HierarchicalConfiguration</dt>
+        <dd>EVENT_ADD_NODES (the <code>addNodes()</code> method was called;
+        the event object contains the key, to which the nodes were added, and
+        a collection with the new nodes as value),
+        EVENT_CLEAR_TREE (the <code>clearTree()</code> method was called; the
+        event object stores the key of the removed sub tree),
+        EVENT_SUBNODE_CHANGED (a <code>SubnodeConfiguration</code> that was
+        created from this configuration has been changed. The value property
+        of the event object contains the original event object as it was sent by
+        the subnode configuration. <em>Note: At the moment it is not possible
+        to map the property key as it was received from the subnode configuration
+        into the namespace of the parent configuration.)</em></dd>
+      </dl>
+    </p>
+    </subsection>
+
+    <subsection name="An example">
+    <p>
+      Implementing an event listener is quite easy. As an example we are going
+      to define an event listener, which logs all received configuration events
+      to the console. The class could look as follows:
+    </p>
+    <source><![CDATA[
+import org.apache.commons.configuration.event.ConfigurationEvent;
+import org.apache.commons.configuration.event.ConfigurationListener;
+
+public class ConfigurationLogListener implements ConfigurationListener
+{
+    public void configurationChanged(ConfigurationEvent event)
+    {
+        if (!event.isBeforeUpdate())
+        {
+            // only display events after the modification was done
+            System.out.println("Received event!");
+            System.out.println("Type = " + event.getType());
+            if (event.getPropertyName() != null)
+            {
+                System.out.println("Property name = " + event.getPropertyName());
+            }
+            if (event.getPropertyValue() != null)
+            {
+                System.out.println("Property value = " + event.getPropertyValue());
+            }
+        }
+    }
+}
+]]></source>
+    <p>
+      Now an instance of this event listener class has to be registered at a
+      configuration object:
+    </p>
+    <source><![CDATA[
+AbstractConfiguration config = ... // somehow create the configuration
+ConfigurationListener listener = new ConfigurationLogListener();
+config.addConfigurationListener(listener);
+...
+config.addProperty("newProperty", "newValue"); // will fire an event
+]]></source>
+    </subsection>
+    <subsection name="Error listeners">
+    <p>
+      Some implementations of the <code>Configuration</code> interface operate
+      on underlying storages that can throw exceptions on each property access.
+      As an example consider <code>
+      <a href="../apidocs/org/apache/commons/configuration/DatabaseConfiguration.html">
+      DatabaseConfiguration</a></code>: this configuration class issues an SQL
+      statement for each accessed property, which can potentially cause a
+      <code>SQLException</code>.
+    </p>
+    <p>
+      In earlier versions of <em>Commons Configuration</em> such exceptions
+      were simply logged and then swallowed. So for clients it was impossible
+      to find out if something went wrong. From version 1.4 on there is a new
+      way of dealing with those internal errors: the concept of <em>error
+      listeners</em>.
+    </p>
+    <p>
+      A configuration error listener is very similar to a regular configuration
+      event listener. Instead of the <code>ConfigurationListener</code>
+      interface it has to implement the
+      <code><a href="../apidocs/org/apache/commons/configuration/event/ConfigurationErrorListener.html">
+      ConfigurationErrorListener</a></code> interface, which defines a single method
+      <code>configurationError()</code>. In case of an internal error this
+      method is invoked, and a
+      <code><a href="../apidocs/org/apache/commons/configuration/event/ConfigurationErrorEvent.html">
+      ConfigurationErrorEvent</a></code> with information about that error is
+      passed. By inheriting from <code>ConfigurationEvent</code>
+      <code>ConfigurationErrorEvent</code> supports all information that is
+      available for normal configuration listeners, too (e.g. the event type or
+      the property that was accessed when the problem occurred; note that the
+      <code>isBefore()</code> method does not really make sense for error
+      events because an error can only occur after something was done, so it
+      returns always <b>false</b> is this context). This data can
+      be used to find out when and where the error happened. In addition there
+      is the <code>getCause()</code> method that returns the <code>Throwable</code>
+      object, which generated this event (i.e. the causing exception).
+    </p>
+    <p>
+      We can now continue our example from the previous section and make our
+      example configuration listener also capable of tracing error events. To
+      achieve this we let the <code>ConfigurationLogListener</code> class also
+      implement the <code>ConfigurationErrorListener</code> interface:
+    </p>
+    <source>
+import org.apache.commons.configuration.event.ConfigurationEvent;
+import org.apache.commons.configuration.event.ConfigurationListener;
+<b>import org.apache.commons.configuration.event.ConfigurationListener;</b>
+
+public class ConfigurationLogListener
+  implements ConfigurationListener, <b>ConfigurationErrorListener</b>
+{
+    public void configurationChanged(ConfigurationEvent event)
+    {
+      // remains unchanged, see above
+      ...
+    }
+    
+    <b>public void configurationError(ConfigurationErrorEvent event)
+    {
+        System.out.println("An internal error occurred!");
+        // Log the standard properties of the configuration event
+        configurationChanged(event);
+        // Now log the exception
+        event.getCause().printStackTrace();
+    }</b>
+}
+</source>
+    <p>
+      Now the listener object has to be registered as an error listener, too.
+      For this purpose <code>AbstractConfiguration</code> provides the
+      <code>addErrorListener()</code> method. The following example fragment
+      shows the registration of the log listener object:
+    </p>
+    <source>
+AbstractConfiguration config = ... // somehow create the configuration
+ConfigurationListener listener = new ConfigurationLogListener();
+config.addConfigurationListener(listener);
+<b>config.addErrorListener((ConfigurationErrorListener) listener);</b>
+...
+config.addProperty("newProperty", "newValue"); // will fire an event
+</source>
+    <p>
+      Note: <code>AbstractConfiguration</code> already implements a mechanism
+      for writing internal errors to a logger object: It has the protected
+      <code>addErrorLogListener()</code> method that can be called by derived
+      classes to register a listener that will output all occurring internal
+      errors using the default logger. Configuration implementations like
+      <code>DatabaseConfiguration</code> that are affected by potential internal
+      errors call this method during their initialization. So the default
+      behavior of <em>Commons Configuration</em> for these classes is not
+      changed: they still catch occurring exceptions and log them. However by
+      registering specific error listeners it is now possible for clients to
+      implement their own handling of such errors.
+    </p>
+    </subsection>
+    </section>
+</body>
+
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/userguide/howto_filebased.xml b/src/site/xdoc/userguide/howto_filebased.xml
new file mode 100644
index 0000000..21fa2d2
--- /dev/null
+++ b/src/site/xdoc/userguide/howto_filebased.xml
@@ -0,0 +1,244 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<document>
+
+ <properties>
+  <title>File-based Configurations</title>
+  <author email="oheger at apache.org">Oliver Heger</author>
+ </properties>
+
+<body>
+    <section name="File-based Configurations">
+        <p>
+            Often configuration properties are stored in files on the user's hard
+          disk, e.g. in .properties files or as XML documents. Configuration
+          classes that deal with such properties need to provide typical operations
+          like loading or saving files. The files to be processed can be specified
+          in several different flavors like <code>java.io.File</code> objects,
+          relative or absolute path names, or URLs.
+        </p>
+        <p>
+          To provide a consistent way of dealing with configuration files in
+          Commons Configuration the <code><a href="../apidocs/org/apache/commons/configuration/FileConfiguration.html">FileConfiguration</a></code>
+          interface exists. <code>FileConfiguration</code> defines a standard
+          API for accessing files and is implemented by many configuration
+          implementations, including <code>PropertiesConfiguration</code> and
+          <code>XMLConfiguration</code>.
+        </p>
+        <p>
+          In the following sections we take a closer look at the methods of the
+          <code>FileConfiguration</code> interface and how they are used.
+        </p>
+
+        <subsection name="Specifying the file">
+          <p>
+            The <code>FileConfiguration</code> interface contains several
+            methods for specifying the file to be loaded. The following variants
+            are supported:
+            <ul>
+              <li>With the <code>setFile()</code> method the data file can be
+              specified as a <code>java.io.File</code> object.</li>
+              <li>The <code>setURL()</code> takes a <code>java.net.URL</code>
+              as argument; the file will be loaded from this URL.</li>
+              <li>The methods <code>setFileName()</code> and <code>setBasePath()</code>
+              allows to specify the path of the data file. The base path is
+              important if relative paths are to be resolved based on this file.</li>
+            </ul>
+          </p>
+          <p>
+            While a <code>File</code> or a URL uniquely identify a file, the
+            situation is a bit ambigous when only a base path and a file name are
+            set. These can be arbitrary strings (even full URLs) whose exact
+            meaning must be detected when the file is loaded. For this purpose
+            file-based configurations perform the following checks (in this
+            order):
+            <ul>
+              <li>If the combination from base path and file name is a full URL
+              that points to an existing file, this URL will be used to load
+              the file.</li>
+              <li>If the combination from base path and file name is an absolute
+              file name and this file exists, it will be loaded.</li>
+              <li>If the combination from base path and file name is a relative
+              file path that points to an existing file, this file will be loaded.</li>
+              <li>If a file with the specified name exists in the user's home
+              directory, this file will be loaded.</li>
+              <li>Otherwise the file name is interpreted as a resource name, and
+              it is checked whether the data file can be loaded from the classpath.</li>
+            </ul>
+            If all these checks fail, a <code>ConfigurationException</code> will
+            be thrown.
+          </p>
+        </subsection>
+
+        <subsection name="Loading">
+          <p>
+            After the file name has been defined using one of the methods mentioned
+            above, the <code>load()</code> method can be called. This method tries
+            to locate the file and open it. If this fails, a <code>ConfigurationException</code>
+            is thrown.
+          </p>
+          <p>
+            The <code>FileConfiguration</code> interface defines multiple overloaded
+            <code>load()</code> methods. The one that takes no argument will
+            always operate on the file name that has been set earlier. All
+            other methods allow to specify the source to be loaded. This can be
+            done as <code>java.io.File</code>, <code>java.net.URL</code>, string
+            (containing either an absolute or relative path), input stream, or
+            reader. When using these variants of the <code>load()</code> method
+            be aware of two things:
+            <ol>
+              <li>They do not change the configuration's file name. To do this
+              you have to explicitely call one of the setter methods.</li>
+              <li>The <code>load()</code> methods do not empty the
+              configuration before new data is loaded. This makes it easy to
+              construct union configurations by simply calling <code>load()</code>
+              multiple times. But if you want to reuse a <code>Configuration</code>
+              object and load a different file, remember to call the
+              <code>clear()</code> method first to ensure that old properties are
+              wiped out.</li>
+            </ol>
+          </p>
+          <p>
+            File-based configurations typically define a set of constructors that
+            correspond to the various setter methods for defining the data file.
+            These constructors will set the file and then invoke the <code>load()</code>
+            method. So creating a file-based configuration object and loading its
+            content can be done in a single step.
+          </p>
+        </subsection>
+
+        <subsection name="Saving">
+          <p>
+            Saving is implemented analogously to loading: There is a no argument
+            <code>save()</code> method that will use the internal file name. Then
+            for each <code>load()</code> method a corresponding <code>save()</code>
+            method exists that will write the data contained in the configuration
+            to different targets.
+          </p>
+          <p>
+            An example for loading, manipulating, and saving a configuration
+            (based on a <a href="howto_properties.html"><code>PropertiesConfiguration</code></a>)
+            could look as follows:
+          </p>
+<source>
+PropertiesConfiguration config = new PropertiesConfiguration("usergui.properties");
+config.setProperty("colors.background", "#000000");
+config.save();
+</source>
+          <p>
+            You can also save a copy of the configuration to another file:
+          </p>
+<source>
+PropertiesConfiguration config = new PropertiesConfiguration("usergui.properties");
+config.setProperty("colors.background", "#000000");
+config.save("usergui.backup.properties);
+</source>
+        </subsection>
+
+        <subsection name="Automatic Saving">
+          <p>
+            If you want to ensure that every modification of a configuration
+            object is immideately written to disk, you can enable the automatic
+            saving mode. This is done through the <code>setAutoSave()</code>
+            method as shown in the following example:
+          </p>
+<source>
+PropertiesConfiguration config = new PropertiesConfiguration("usergui.properties");
+config.setAutoSave(true);
+config.setProperty("colors.background", "#000000"); // the configuration is saved after this call
+</source>
+          <p>
+            Be careful with this mode when you have many updates on your
+            configuration. This will lead to many I/O operations, too.
+          </p>
+        </subsection>
+
+        <subsection name="Automatic Reloading">
+        <p>
+          A common issue with file-based configurations is to handle the
+          reloading of the data file when it changes. This is especially important
+          if you have long running applications and do not want to restart them
+          when a configuration file was updated. Commons Configuration has the
+          concept of so called <em>reloading strategies</em> that can be
+          associated with a file-based configuration. Such a strategy monitors
+          a configuration file and is able to detect changes. A default reloading
+          strategy is <code><a href="../apidocs/org/apache/commons/configuration/reloading/FileChangedReloadingStrategy.html">FileChangedReloadingStrategy</a></code>.
+          It can be set on a file-based configuration as follows:
+        </p>
+<source>
+PropertiesConfiguration config = new PropertiesConfiguration("usergui.properties");
+config.setReloadingStrategy(new FileChangedReloadingStrategy());
+</source>
+        <p>
+          <code>FileChangedReloadingStrategy</code> works as follows: On every
+          property access the configuration checks its associated reloading
+          strategy. <code>FileChangedReloadingStrategy</code> will then obtain
+          the last modification date of the configuration file and check whether
+          it has changed since the last access. If this is the case, a reload is
+          triggered. To avoid often disk access when multiple properties are
+          queried from the configuration, a <em>refresh delay</em> can be set on
+          the reloading strategy. This is a time in milli seconds with the meaning
+          that the reloading strategy will only once check the file's last
+          modification time in the period specified here.
+        </p>
+        </subsection>
+
+        <subsection name="Managed Reloading">
+        <p>
+          <code>ManagedReloadingStrategy</code> is an alternative to automatic
+          reloading. It allows to hot-reload properties on a running application
+          but only when requested by admin. The <code>refresh()</code> method
+          will force a reload of the configuration source.
+        </p>
+        <p>
+          A typical use of this feature is to setup ManagedReloadingStrategy as
+          a JMX MBean. The following code sample uses Springframework
+          MBeanExporter to expose the ManagedReloadingStrategy to the JMX
+          console :
+<source>
+<![CDATA[
+<!-- A file based configuration bean -->
+<bean id="configuration" class="(...).PropertiesConfiguration">
+    <constructor-arg type="java.net.URL" value="file:${user.home}/custom.properties"/>
+    <property name="reloadingStrategy" ref="reloadingStrategy"/>
+</bean>
+
+<!-- The managed reloading strategy for the configuration bean -->
+<bean id="reloadingStrategy" class="...ManagedReloadingStrategy"/>
+
+<!-- The MBeanExporter that exposes reloadingStrategy to the JMX console -->
+<bean id="mbeanMetadataExporter" class="org.springframework.jmx.export.MBeanExporter">
+    <property name="server" ref="mbeanServer"/>
+    <property name="beans">
+        <map>
+            <entry key="myApp:bean=configuration" value-ref="reloadingStrategy"/>
+        </map>
+    </property>
+</bean>
+]]>
+</source>
+          With this configuration, the JMX console will expose the
+          "myApp:bean=configuration" MBean and it's refresh operation.
+        </p>
+        </subsection>
+    </section>
+
+</body>
+
+</document>
diff --git a/src/site/xdoc/userguide/howto_filesystems.xml b/src/site/xdoc/userguide/howto_filesystems.xml
new file mode 100644
index 0000000..aba2651
--- /dev/null
+++ b/src/site/xdoc/userguide/howto_filesystems.xml
@@ -0,0 +1,131 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<document>
+
+ <properties>
+  <title>File Systems</title>
+  <author email="rgoers at apache.org">Ralph Goers</author>
+ </properties>
+
+<body>
+    <section name="File Systems">
+      <p>
+        In its default mode of operation Commons Configuration supports retrieving and storing
+        configuration files either on a local file system or via http. However, Commons
+        Configuration provides support for allowing other File System adapters. All file
+        access is accomplished through the <code>FileSystem</code> interface so accessing files
+        using other mechanisms is possible.
+      </p>
+      <p>
+        Commons Configuration also provides a second FileSystem which allows retrieval using
+        <a href="http://commons.apache.org/vfs">Apache Commons VFS</a>. As of this writing
+        Commons VFS supports 18 protocols for manipulating files.
+      </p>
+      <subsection name="Configuration">
+      <p>
+        The FileSystem used by Commons Configuration can be set in one of several ways:
+        <ol>
+          <li>A system property named "org.apache.commons.configuration.filesystem" can be defined
+          with the full class name of the desired <code>FileSystem</code> implementation to set the
+          default <code>FileSystem</code>.</li>
+          <li><code>FileSystem.setDefaultFilesystem()</code> can be called to directly set the
+          default <code>FileSystem</code> implementation.</li>
+          <li><code>DefaultConfigurationBuilder.setFileSystem()</code> can be called to set the
+          FileSystem implementation. <code>DefaultConfiguratonBuilder</code> will use this for each
+          configuration it creates</li>
+          <li><code>DefaultConfigurationBuilder</code> can be configured with the <code>FileSystem</code>
+          to be used when creating each of the configurations.</li>
+          <li>Each Configuration referenced in <code>DefaultConfigurationBuilder's</code>
+          configuration can be configured with the <code>FileSystem</code> to use for that
+          configuration.</li>
+          <li>Call setFileSystem() directly on any Configuration that implements <code>FileSystemBased.</code>
+          Both <code>AbstractFileConfiguration</code> and <code>AbstractHierarchicalFileConfiguration</code>
+          implement <code>FileSystemBased</code></li>
+        </ol>
+      </p>
+      <p>
+        The example that follows shows how to add <code>FileSystem</code> configuration to
+        <code>DefaultConfigurationBuilder</code>.
+      </p>
+     <source><![CDATA[
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true" forceReloadCheck="true">
+      <expressionEngine config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <fileSystem config-class="org.apache.commons.configuration.VFSFileSystem"/>
+  </header>
+  <override>
+    <xml fileName="settings.xml" config-name="xml">
+      <fileSystem config-class="org.apache.commons.configuration.DefaultFileSystem"/>
+    </xml>
+  </override>
+</configuration>
+]]></source>
+      </subsection>
+      <subsection name="File Options Provider">
+        <p>
+          Commons VFS allows options to the underlying file systems being used. Commons Configuration
+          allows applications to provide these by implementing the <code>FileOptionsProvider</code> interface
+          and registering the provider with the <code>FileSystem</code>. <code>FileOptionsProvider</code>
+          has a single method that must be implemented, <code>getOptions</code>, which returns a Map
+          containing the keys and values that the <code>FileSystem</code> might use. The getOptions
+          method is called as each configuration uses VFS to create a <code>FileOjbect</code> to
+          access the file. The map returned does not have to contain the same keys and/or values
+          each time it is called. For example, the value of the <code>currentUser</code> key can be
+          set to the id of the currently logged in user to allow a WebDAV save to record the userid
+          as a file attribute.
+        </p>
+      </subsection>
+      <subsection name="File Reloading Strategy">
+        <p>
+          The <code><a href="../apidocs/org/apache/commons/configuration/reloading/VFSFileChangedReloadingStrategy.html">VFSFileChangedReloadingStrategy</a></code>
+          can be used to cause Configurations accessed via the <code>VFSFileSystem</code> to be
+          monitored and reloaded when the files are modified. The example below shows how
+          <code>DefaultConfigurationBuilder</code> can be configured to use
+          <code>VFSFilChangedReloadingStrategy</code>.
+          In the example below both test.properties and settings.xml would be checked for changes
+          once per minute.
+        </p>
+       <source><![CDATA[
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true" forceReloadCheck="true">
+      <expressionEngine config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <fileSystem config-class="org.apache.commons.configuration.VFSFileSystem"/>
+  </header>
+  <override>
+    <properties fileName="test.properties" throwExceptionOnMissing="true">
+      <reloadingStrategy refreshDelay="60000"
+        config-class="org.apache.commons.configuration.reloading.VFSFileChangedReloadingStrategy"/>
+    </properties>
+    <xml fileName="settings.xml" config-name="xml">
+      <reloadingStrategy refreshDelay="60000"
+         config-class="org.apache.commons.configuration.reloading.VFSFileChangedReloadingStrategy"/>
+    </xml>
+  </override>
+</configuration>
+]]></source>
+      </subsection>
+    </section>
+
+</body>
+
+</document>
diff --git a/src/site/xdoc/userguide/howto_multitenant.xml b/src/site/xdoc/userguide/howto_multitenant.xml
new file mode 100644
index 0000000..857daba
--- /dev/null
+++ b/src/site/xdoc/userguide/howto_multitenant.xml
@@ -0,0 +1,171 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<document>
+
+  <properties>
+    <title>Mutli-tenant Configurations</title>
+    <author email="rgoers at apache.org">Ralph Goers</author>
+  </properties>
+
+  <body>
+    <section name="Multi-tenant Configurations">
+      <p>
+        In a multi-tenant environment a single instance of the application
+        while run on behalf of many clients. Typically, this will require
+        that each client have its own unique configuration. The simplest
+        approach to enable an application to be multi-tenant is for it
+        to not really be aware of it at all. This requires that the
+        configuration framework take on some of the responsility for
+        making the application work correctly.
+      </p>
+      <p>
+        One approach to enable this support in a web application might be
+        to use a Servlet Filter and then use the Log4j or SLF4J MDC to
+        save the attributes needed to identify the client. These attributes
+        can then be used to identify the specific client configuration to
+        be used. The classes described below use this technique to allow
+        configurations to transparently provide the configuration appropriate
+        for the clients.
+      </p>
+
+      <subsection name="MultiFileHierarchicalConfiguration">
+        <p>
+          The constructor for this class accepts a pattern. The pattern can
+          contain keys that will be resolved using the ConfigurationInterpolator
+          on each call to a method in the class. The configuration file will then
+          be located using the resolved pattern and a new XMLConfiguration
+          will be created and cached for subsequent requests. The ExpressionEngine,
+          ReloadingStrategy and listeners will be propogated to each of the
+          created configurations.
+        </p>
+        <p>
+          When used in a combined configuration it is often acceptable for a file
+          matching a particular pattern to be missing so, by default, most exceptions
+          encountered when loading files are ignored. To change this behavior
+          call setIgnoreException(false) or configure the attribute to false in
+          DefaultConfigurationBuilder's configuration file. If schema validation
+          is enabled validation exceptions will always cause a failure.
+        </p>
+      </subsection>
+      <subsection name="DynamicCombinedConfiguration">
+        <p>
+          The CombinedConfiguration class allows multiple configuration files to be
+          merged together. However, it will not merge a MultiFileHierarchicalConfiguration
+          properly since the underlying configuration file will be different depending
+          on the resolution of the location pattern. DynamicCombinedConfiguration
+          solves this by creating a new CombinedConfiguration for each pattern.
+        </p>
+      </subsection>
+      <subsection name="Sample Configuration">
+        <p>
+           This sample configuration illustrates how to use DynamicCombinedConfiguration
+           in combination with MultiFileHierarchicalConfiguration to create a multi-tenant
+           configuration.
+        </p>
+        <source><![CDATA[
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true" forceReloadCheck="true"
+            config-class="org.apache.commons.configuration.DynamicCombinedConfiguration"
+            keyPattern="$${sys:Id}">
+      <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <providers>
+      <provider config-tag="multifile"
+         config-class="org.apache.commons.configuration.DefaultConfigurationBuilder$FileConfigurationProvider"
+         configurationClass="org.apache.commons.configuration.MultiFileHierarchicalConfiguration"/>
+    </providers>
+  </header>
+  <override>
+    <multifile filePattern="/opt/configs/$$${sys:Id}/config.xml" config-name="clientConfig"/>
+    <xml fileName="/opt/configs/default/config.xml" config-name="defaultConfig"/>
+  </override>
+</configuration>
+]]></source>
+        <p>
+          Note how the variables have multiple '$'. This is how variables are escaped and
+          is necessary because the variables will be interpolated multiple times. Each
+          attempt will remove the leading '$'. When there is only a single '$' in front
+          of the '{' the interpolator will then resolve the variable. The first extra '$'
+          is necessary because DefaultConfigurationBuilder will interpolate any variables
+          in the configuration. In the case of the multifile configuration item two
+          leading '$' characters are necessary before the variable because it will be
+          interpolated by both DefaultConfigurationBuilder and DynamicCombinedConfiguration
+          before MultiFileHierarchicalConfiguration gets the chance to evaluate it.
+          Although in this example one would not expect system properties to change
+          at runtime, other types of lookups such as the MDCStrLookup provided with
+          SLF4J require that the variables be evaluated as the configuration is being
+          accessed instead of when the configuration file is processed to behave as desired.
+        </p>
+      </subsection>
+      <subsection name="PatternSubtreeConfigurationWrapper">
+        <p>
+          Applications are often composed of many components each of which need their
+          own configuration. This can be accomodated by having a configuration file
+          per component, but this can make things hard to manage when there are many
+          clients and many components. A second approach is to combine them into
+          a single configuration file. However, this either means the subcomponent
+          has to be aware of the surrounding configuration and navigate past it or the
+          application must be provided just the portion of the configuration it
+          can process. PatternSubtreeConfigurationWrapper can be used for this purpose.
+        </p>
+        <p>
+          Normal practice when using dependency injection frameworks is to have the
+          attributes needed to make components work correctly injected into them.
+          When working with Commons Configuration this works very well. Components
+          simply need to have a HierarchicalConfiguration attribute along with
+          a corresponding setter and getter. The injection framework can then be
+          used to provide the component with the correct configuration using
+          PatternSubtreeConfigurationWrapper as shown in the next example.
+        </p>
+        <p>
+        <source><![CDATA[
+  <bean id="configurationBuilder"
+        class="org.apache.commons.configuration.DefaultConfigurationBuilder">
+    <property name="fileName">
+      <value>configuration.xml</value>
+    </property>
+  </bean>
+  <bean id="applicationConfig" factory-bean="configurationBuilder"
+        factory-method="getConfiguration">
+  </bean>
+  <bean id="subcomponentConfig"
+        class="org.apache.commons.configuration.PatternSubtreeConfigurationWrapper"
+        autowire='autodetect'>
+    <constructor-arg index="0">
+      <ref bean="applicationConfig"/>
+    </constructor-arg>
+    <constructor-arg index="1" value="/Components/MyComponent"
+  </bean>
+  <bean id='MyComponent' class='org.test.MyComponent' autowire='autodetect'>
+    <property name="configuration">
+      <ref bean="subcomponentConfig"/>
+    </property>
+  </bean>
+]]></source>
+        </p>
+      </subsection>
+    </section>
+
+  </body>
+
+</document>
diff --git a/src/site/xdoc/userguide/howto_properties.xml b/src/site/xdoc/userguide/howto_properties.xml
new file mode 100644
index 0000000..4d1a245
--- /dev/null
+++ b/src/site/xdoc/userguide/howto_properties.xml
@@ -0,0 +1,445 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<document>
+  <properties>
+    <title>Properties files</title>
+    <author email="smanux at lfjr.net">Emmanuel Bourg</author>
+    <author email="oheger at apache.org">Oliver Heger</author>
+  </properties>
+
+  <body>
+
+    <section name="Properties files">
+      <p>
+        Properties files are a popular mean of configuring applications. Of course Commons Configuration
+        supports this format and enhances significantly the basic <code>java.util.Properties</code> class.
+        This section introduces the features of the 
+        <code><a href="../apidocs/org/apache/commons/configuration/PropertiesConfiguration.html">PropertiesConfiguration</a></code> class.
+        Note that <code>PropertiesConfiguration</code> is a very typical example
+        for an implementation of the <code>Configuration</code> interface and
+        many of the features described in this section (e.g. list handling or
+        interpolation) are supported by other configuration classes as well.
+        This is because most configuration implementations that ship with
+        Commons Configuration are derived from the common base class
+        <code><a href="../apidocs/org/apache/commons/configuration/AbstractConfiguration.html">AbstractConfiguration</a></code>,
+        which implements these features.
+      </p>
+
+      <subsection name="Using PropertiesConfiguration">
+        <p>
+          Let's start with a simple properties file named
+          <code>usergui.properties</code> with the following content:
+        </p>
+        <source><![CDATA[
+# Properties definining the GUI
+colors.background = #FFFFFF
+colors.foreground = #000080
+
+window.width = 500
+window.height = 300
+]]></source>
+
+        <p>
+          To load this file, you'll write:
+        </p>
+<source>
+Configuration config = new PropertiesConfiguration("usergui.properties");
+</source>
+        <p>
+          If you do not specify an absolute path, the file will be searched automatically
+          in the following locations:
+          <ul>
+            <li>in the current directory</li>
+            <li>in the user home directory</li>
+            <li>in the classpath</li>
+          </ul>
+        </p>
+        <p>
+          Instead of using a constructor that takes a file name you can also
+          invoke one of the <code>load()</code> methods. There are several
+          overloaded variants that allow you to load properties from various
+          sources. More information about loading properties files (and file-based
+          configurations in general) can be found in the section about
+          <a href="howto_filebased.html">File-based Configurations</a>.
+        </p>
+        <p>
+          After the properties file was loaded you can access its content through
+          the methods of the <code>Configuration</code> interface, e.g.
+        </p>
+<source>
+String backColor = config.getString("colors.background");
+Dimension size = new Dimension(config.getInt("window.width"),
+  config.getInt("window.height"));
+</source>
+      </subsection>
+
+      <subsection name="Includes">
+        <p>
+          If a property is named "<code>include</code>" and the value of that property is the
+          name of a file on the disk, that file will be included into the configuration. Here is
+          an example:
+        </p>
+<source>
+# usergui.properties
+
+include = colors.properties
+include = sizes.properties
+</source>
+
+<source>
+# colors.properties
+
+colors.background = #FFFFFF
+</source>
+
+      </subsection>
+
+      <subsection name="Lists and arrays">
+        <p>
+          As was already pointed out in the section
+          <a href="howto_basicfeatures.html#List_handling">List handling</a>
+          of <em>Basic features</em>, Commons Configuration has the ability to
+          return easily a list of values. For example a properties file can
+          contain a list of comma separated values:
+        </p>
+<source>
+# chart colors
+colors.pie = #FF0000, #00FF00, #0000FF
+</source>
+        <p>
+          You don't have to split the value manually, you can retrieve an array
+          or a <code>java.util.List</code> directly with:
+        </p>
+<source>
+String[] colors = config.getStringArray("colors.pie");
+List<Object> colorList = config.getList("colors.pie");
+</source>
+        <p>
+          Alternatively, you can specify a list of values in your properties file by using
+          the same key on several lines:
+        </p>
+<source>
+# chart colors
+colors.pie = #FF0000;
+colors.pie = #00FF00;
+colors.pie = #0000FF;
+</source>
+        <p>
+          All of the features related to list handling described for
+          <code>AbstractConfiguration</code> also apply to properties files,
+          including changing the list delimiter or disabling list handling at
+          all.
+        </p>
+      </subsection>
+
+      <subsection name="Saving">
+        <p>
+          To save your configuration, just call the <code>save()</code> method:
+        </p>
+<source>
+PropertiesConfiguration config = new PropertiesConfiguration("usergui.properties");
+config.setProperty("colors.background", "#000000);
+config.save();
+</source>
+        <p>
+          You can also save a copy of the configuration to another file:
+        </p>
+<source>
+PropertiesConfiguration config = new PropertiesConfiguration("usergui.properties");
+config.setProperty("colors.background", "#000000);
+config.save("usergui.backup.properties);
+</source>
+        <p>
+          More information about saving properties files (and file-based
+          configurations in general) can be found in the section about
+          <a href="howto_filebased.html">File-based Configurations</a>.
+        </p>
+      </subsection>
+
+      <subsection name="Special Characters and Escaping">
+        <p>
+          If you need a special character in a property like a line feed, a tabulation or
+          an unicode character, you can specify it with the same escaped notation used for
+          Java Strings. The list separator ("," by default), can also be escaped:
+        </p>
+        <source><![CDATA[
+key = This \n string \t contains \, escaped \\ characters \u0020
+]]></source>
+      <p>
+        When dealing with lists of elements that contain backslash characters
+        (e.g. file paths on Windows systems) escaping rules can become pretty
+        complex. The first thing to keep in mind is that in order to get a
+        single backslash, you have to write two:
+      </p>
+      <source><![CDATA[
+config.dir = C:\\Temp\\
+      ]]></source>
+      <p>
+        This issue is not specific to Commons Configuration, but is related to
+        the standard format for properties files. Refer to the Javadocs of the
+        <code>load()</code> method of <code>java.util.Properties</code> for more
+        information. Now if you want to define a list with file paths, you may
+        be tempted to write the following:
+      </p>
+      <source><![CDATA[
+# Wrong way to define a list of directories
+config.dirs = C:\\Temp\\,D:\\data\\
+      ]]></source>
+      <p>
+        As the comment indicates, this will not work. The trailing backslash of
+        the first directory is interpreted as escape character for the list
+        delimiter. So instead of a list with two elements only a single value
+        of the property is defined - clearly not what was desired. To get a
+        correct list the trailing backslash has to be escaped. This is achieved
+        by duplicating it (yes, in a properties file that means that we now need
+        4 backslashes):
+      </p>
+      <source><![CDATA[
+# Correct way to define a list of directories
+config.dirs = C:\\Temp\\\\,D:\\data\\
+      ]]></source>
+      <p>
+        So a sequence of 4 backslashes in the value of a property is interpreted
+        as an escaped backslash and eventually results in a single backslash.
+        This creates another problem when a properties file should refer to the
+        names of network shares. Typically these names start with two
+        backslashes, so the obvious way to define such a property is as follows:
+      </p>
+      <source><![CDATA[
+# Wrong way to define a list of network shares
+config.dirs = \\\\share1,\\\\share2
+      ]]></source>
+      <p>
+        Unfortunately, this will not work because the shares contain the reserved
+        sequence of 4 backslashes. So when reading the value of the
+        <em>config.dirs</em> property a list with two elements is returned
+        starting only with a single backslash. To fix the problem the sequence
+        for escaping a backslash has to be duplicated - we are now at 8
+        backslashes:
+      </p>
+      <source><![CDATA[
+# Correct way to define a list of network shares
+config.dirs = \\\\\\\\share1,\\\\\\\\share2
+      ]]></source>
+      <p>
+        As becomes obvious, escape sequences can become pretty complex and
+        unreadable. In such situations it is recommended to use the alternative
+        way of defining a list: just use the same key multiple times. In this
+        case no additional escaping of backslashes (beyond the usual duplicating
+        required by properties files) is needed because there is no list
+        delimter character involved. Using this syntax the list of network
+        shares looks like the following:
+      </p>
+      <source><![CDATA[
+# Straightforward way to define a list of network shares
+config.dirs = \\\\share1
+config.dirs = \\\\share2
+      ]]></source>
+      </subsection>
+
+      <subsection name="Layout Objects">
+      <p>
+        Each <code>PropertiesConfiguration</code> object is associated with a
+        <em>Layout object</em>, an instance of the class
+        <code><a href="../apidocs/org/apache/commons/configuration/PropertiesConfigurationLayout.html">
+        PropertiesConfigurationLayout</a></code>. This layout object is
+        responsible for preserving most of the structure of loaded configuration
+        files. This means that things like comments or blank lines in a saved
+        properties file will closely resemble the original properties file
+        (the algorithm is not 100 percent perfect, but for most use cases it
+        should be sufficient).
+      </p>
+      <p>
+        Normally a developer does not have to deal with these layout objects.
+        However, there are some methods that might be of interest if enhanced
+        control over the output of properties files is needed. The following
+        list describes these methods (note that corresponding get methods are
+        of course also provided):
+        <ul>
+        <li><code>setComment()</code><br/>
+        With this method a comment can be set for a specified property. When
+        storing the configuration the comment is output before the property,
+        followed by a line break. The comment can span multiple lines; in this
+        case the newline character "\n" must be used as line
+        separator.</li>
+        <li><code>setHeaderComment()</code><br/>
+        With <code>setHeaderComment()</code> a global comment can be set for the
+        properties file. This comment is written at the very start of the file,
+        followed by an empty line.</li>
+        <li><code>setBlancLinesBefore()</code><br/>
+        This methods allows defining the number of empty lines to be written
+        before the specified property. It can be used, for instance, to
+        devide the properties file into multiple logical sections.</li>
+        <li><code>setSingleLine()</code><br/>
+        If a property has multiple values, with <code>setSingleLine()</code> it
+        can be specified that all these values should be written into a single
+        line separated by the default list separator. It is also possible to
+        write multiple definitions for this property (i.e. multiple lines of the
+        form <code>property = value1</code>, <code>property = value2</code> etc.).
+        This is supported by <code>PropertiesConfiguration</code>, but will
+        probably not work when processing the properties file with other tools.
+        </li>
+        <li><code>setForceSingleLine()</code><br/>
+        This is similar to <code>setSingleLine()</code>, but sets a global
+        single line flag. If set to <b>true</b>, all properties with multiple
+        values are always written on a single line.</li>
+        <li><code>setGlobalSeparator()</code><br/>
+        Sometimes it may be necessary to define the properties separator, i.e.
+        the string that separates the property key from the value. This can be
+        done using <code>setGlobalSeparator()</code>. Here an arbitrary string
+        can be specified that will be used as separator. (Note: In order to
+        produce valid properties files only the characters <code>=</code> and
+        <code>:</code> should be used as separators (with or without leading or
+        trailing whitespace), but the method does not enforce this.</li>
+        <li><code>setSeparator()</code><br/>
+        This method is similar to <code>setGlobalSeparator()</code>, but
+        allows setting the property separator for a specific property.</li>
+        <li><code>setLineSeparator()</code><br/>
+        Using this method the line separator can be specified. Per default the
+        platform-specific line separator is used (e.g. <code>\n</code> on unix).
+        </li>
+        </ul>
+        The default settings of <code>PropertiesConfigurationLayout</code> are
+        chosen in a way that most of the original layout of a properties file
+        is retained. With the methods listed above specific layout restrictions
+        can be enforced.
+      </p>
+      </subsection>
+
+      <subsection name="Custom properties readers and writers">
+      <p>
+        There are situations when more control over the process of reading and
+        writing properties file is needed. For instance, an application might
+        have to deal with some legacy properties file in a specific format,
+        which is not supported out of the box by
+        <code>PropertiesConfiguration</code>, but must not be modified. In these
+        cases it is possible to inject a custom reader and writer for
+        properties files.
+      </p>
+      <p>
+        Per default properties files are read and written by the nested classes
+        <code>PropertiesReader</code> and <code>PropertiesWriter</code>
+        (defined within <code>PropertiesConfiguration</code>). These classes are
+        regular reader and writer classes (both are derived from typical base
+        classes of the <code>java.io</code> package) that provide some
+        additional methods making dealing with properties files more
+        convenient. Custom implementations of properties readers and writers
+        must extend these base classes.
+      </p>
+      <p>
+        For installing a custom properties reader or writer
+        <code>PropertiesConfiguration</code> provides the <code>IOFactory</code>
+        interface (which is also defined as a nested class). An object
+        implementing this interface is stored in each
+        <code>PropertiesConfiguration</code> instance. Whenever a properties
+        file is to be read or written (i.e. when one of the <code>load()</code>
+        or <code>save()</code> methods is called), the <code>IOFactory</code>
+        object is asked for creating the properties reader or writer to be
+        used.
+      </p>
+      <p>
+        The <code>IOFactory</code> interface is pretty simple; it defines one
+        method for creating a properties reader and another one for creating a
+        properties writer. A default implementation called
+        <code>DefaultIOFactory</code> is also available and is used by
+        <code>PropertiesConfiguration</code> when no specific
+        <code>IOFactory</code> is set. To make this discussion more concrete
+        we provide an example of how to inject a custom properties reader. The
+        use case is that we have to load a properties file that contains keys
+        with whitespace, which is not supported by
+        <code>PropertiesConfiguration</code> per default. A fragment from such
+        a properties file could look as follows:
+      </p>
+        <source><![CDATA[
+Background Color = #800080
+Foreground Color = #000080
+]]></source>
+      <p>
+        The first step is to create a custom properties reader implementation
+        that can deal with such properties. The class is derived from
+        <code>PropertiesConfiguration.PropertiesReader</code> and overrides the
+        <code>parseProperty()</code> method:
+      </p>
+        <source><![CDATA[
+public class WhitespacePropertiesReader extends PropertiesConfiguration.PropertiesReader
+{
+    public WhitespacePropertiesReader(Reader in, char delimiter)
+    {
+        super(in, delimiter);
+    }
+    
+    /**
+     * Special algorithm for parsing properties keys with whitespace. This
+     * method is called for each non-comment line read from the properties
+     * file.
+     */
+    protected void parseProperty(String line)
+    {
+        // simply split the line at the first '=' character
+        // (this should be more robust in production code)
+        int pos = line.indexOf('=');
+        String key = line.substring(0, pos).trim();
+        String value = line.substring(pos + 1).trim();
+
+        // now store the key and the value of the property
+        initPropertyName(key);
+        initPropertyValue(value);
+    }
+}
+]]></source>
+      <p>
+        Notice the calls to the methods <code>initPropertyName()</code> and
+        <code>initPropertyValue()</code>. Here the results of the parsing
+        operation are stored. The next step is to provide a specialized
+        implementation of the <code>IOFactory</code> interface that returns
+        the new properties reader class. As we only want to replace the
+        properties reader (and use the standard writer), we can derive our
+        implementation from <code>DefaultIOFactory</code> and thus only have
+        to override the <code>createPropertiesReader()</code> method.
+      </p>
+        <source><![CDATA[
+public class WhitespaceIOFactory extends PropertiesConfiguration.DefaultIOFactory
+{
+    /**
+     * Return our special properties reader.
+     */
+    public PropertiesReader createPropertiesReader(Reader in, char delimiter)
+    {
+        return new WhitespacePropertiesReader(in, delimiter);
+    }
+}
+]]></source>
+      <p>
+        Finally an instance of our new <code>IOFactory</code> implementation
+        has to be created and passed to the <code>PropertiesConfiguration</code>
+        object. This must be done before the <code>load()</code> method is
+        called. So we cannot use one of the constructors that load the data.
+        The code for setting up the configuration object could look as follows:
+      </p>
+        <source><![CDATA[
+PropertiesConfiguration config = new PropertiesConfiguration();
+config.setIOFactory(new WhitespaceIOFactory());
+config.setFile(...);  // set the file to be loaded
+config.load();
+]]></source>
+      </subsection>
+    </section>
+
+  </body>
+</document>
diff --git a/src/site/xdoc/userguide/howto_utilities.xml b/src/site/xdoc/userguide/howto_utilities.xml
new file mode 100644
index 0000000..df124b0
--- /dev/null
+++ b/src/site/xdoc/userguide/howto_utilities.xml
@@ -0,0 +1,279 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<document>
+
+ <properties>
+  <title>Utility classes and Tips and Tricks Howto</title>
+  <author email="oheger at apache.org">Oliver Heger</author>
+ </properties>
+
+<body>
+	<section name="Utility classes and Tips and Tricks">
+    <p>
+      In this section some utility classes will be introduced that can be used
+      to make handling of configuration objects easier. These classes already
+      provide solutions for some often occurring problems. We will list these
+      problems in no specific order and show how they can be solved with
+      classes provided by <em>Commons Configuration</em>.
+    </p>
+
+    <subsection name="Copy a configuration">
+	<p>
+      Often it is required to copy the data of one <code>Configuration</code>
+      object into another one. For this purpose the
+      <code><a href="../apidocs/org/apache/commons/configuration/AbstractConfiguration.html">
+      AbstractConfiguration</a></code> class (which serves as the base class for
+      most of the configuration implementations shipped with this library)
+      provides two methods implementing a basic copy operation:
+      <ul>
+        <li><code>append()</code> takes the configuration to be copied
+        as argument and adds all of its properties to the current configuration.</li>
+        <li><code>copy()</code> is very similar to <code>append()</code>. The
+        difference is that properties that already exist in the target
+        configuration are replaced by the properties of the source configuration.
+        </li>
+      </ul>
+    </p>
+    <p>
+      These methods work fine if the target configuration is not a hierarchical
+      configuration. If a hierarchical configuration is to be copied into
+      another one, the methods are not able to handle the hierarchical
+      structure; so the resulting configuration will contain all of the
+      properties of the source configuration, but the specific parent-child
+      relations will probably be lost. If a hierarchical configuration needs to
+      be copied, there are the following options:
+      <ul>
+        <li>The <code>clone()</code> method can be used to create a copy of a
+        hierarchical configuration. This also works for non-hierarchical
+        configurations. Most of the configuration implementations provided by
+        <em>Commons Configurations</em> support cloning. The
+        <code>cloneConfiguration()</code> method of
+        <code><a href="../apidocs/org/apache/commons/configuration/ConfigurationUtils.html">
+        ConfigurationUtils</a></code> can be used for creating a copy of an
+        arbitrary <code>Configuration</code> object. This method checks whether
+        the passed in configuration implements the <code>Cloneable</code>
+        interface and, if so, invokes its <code>clone()</code> method.</li>
+        <li>Most hierarchical configurations have a constructor, which takes
+        another hierarchical configuration as argument. This constructor
+        copies the content of the specified configuration into the newly created
+        object.</li>
+      </ul>
+    </p>
+    </subsection>
+
+    <subsection name="Converting a flat configuration into a hierarchical one">
+    <p>
+      <a href="howto_xml.html">Hierarchical configurations</a> provide some
+      enhanced features that are not available for "flat"
+      configurations. For instance they support more sophisticated query
+      facilities. Because of that it may be sometimes useful to transform an
+      ordinary configuration into a hierarchical one. The following code
+      fragment shows how this can be done:
+    </p>
+    <source><![CDATA[
+// Create a flat configuration
+PropertiesConfiguration flatConfig = new PropertiesConfiguration();
+flatConfig.load(...);
+HierarchicalConfiguration hc =
+  ConfigurationUtils.convertToHierarchical(flatConfig);
+]]></source>
+    <p>
+      The <code>convertToHierarchical()</code> method of
+      <code><a href="../apidocs/org/apache/commons/configuration/ConfigurationUtils.html">
+      ConfigurationUtils</a></code> checks whether the passed in object
+      is already a hierarchical configuration. If this is the case, it is
+      returned unchanged. Otherwise a new <code>HierarchicalConfiguration</code>
+      object is created, and the properties of the source configuration are
+      copied into it.
+    </p>
+    <p>
+      Sometimes a flat configuration contains keys with special characters
+      that are not compatible with the expression engine of a hierarchical
+      configuration. For instance, a properties configuration could have the
+      following property:
+    </p>
+    <source><![CDATA[
+test(xy)=true
+]]></source>
+    <p>
+      When processing this property during conversion the default expression
+      engine of the resulting hierarchical configuration will interpret the
+      brackets as an index marker and try to convert the string between the
+      brackets into a number. In this example this fails with a
+      <code>NumberFormatException</code>! The cause for this problem is that the
+      property key contains characters with a special meaning for the default
+      expression engine.
+    </p>
+    <p>
+      To solve this problem, it is possible to specify an alternative expression
+      engine that will be used for the conversion. For instance, if you know that
+      your property keys can contain brackets, you could use an instance of
+      <code>DefaultExpressionEngine</code> that is configured with a different
+      index marker. This could look as follows:
+    </p>
+    <source><![CDATA[
+DefaultExpressionEngine engineConvert = new DefaultExpressionEngine();
+engineConvert.setIndexStart("[");
+engineConvert.setIndexEnd("]");
+HierarchicalConfiguration hc =
+  ConfigurationUtils.convertToHierarchical(flatConfig, engineConvert);
+]]></source>
+    <p>
+      In this example an expression engine is constructed that uses square
+      brackets as index markers. Therefore normal brackets do not have a
+      special meaning and thus are no more problematic during conversion.
+    </p>
+    <p>
+      <em>Note:</em> When using a <a href="howto_combinedconfiguration.html">
+      CombinedConfiguration</a> flat configurations contained in the combined
+      configuration are also converted into hierarchical configurations using
+      the methods discussed here. The <code>CombinedConfiguration</code> class
+      defines the method <code>setConversionExpressionEngine()</code>, which
+      can be called to specify an expression engine to be used during this
+      conversion. The expression engine passed to this method will be
+      propagated to ConfigurationUtils.convertToHierarchical().
+    </p>
+    </subsection>
+
+    <subsection name="Converting between properties and configurations">
+    <p>
+      When working with the JDK the <code>java.util.Properties</code> class is
+      typically used for storing configuration data. If <em>Commons
+      Configuration</em> is to be integrated in such an application, there may
+      be the requirement of converting from <code>Properties</code> objects to
+      <code>Configuration</code> objects and vice versa. For this purpose an
+      utility class can be used:
+      <code><a href="../apidocs/org/apache/commons/configuration/ConfigurationConverter.html">
+      ConfigurationConverter</a></code>.
+    </p>
+    <p>
+      Usage of this class is pretty simple. It provides some static utility
+      methods that perform different conversions. Below you can see some
+      examples. In this fragment we assume that we have a method
+      <code>processConfiguration()</code> that is called from older parts of an
+      application that are not aware of the <em>Commons Configuration</em> API.
+      So they pass in a <code>Properties</code> object and expect one as
+      return value. Inside the method a temporary <code>Configuration</code>
+      object is created and used.
+    </p>
+    <source><![CDATA[
+/**
+ * Does some processing of properties.
+ * @param props the source properties
+ * @return the processed properties
+ */
+Properties processConfiguration(Properties props)
+{
+    // Create a configuration for the properties for easy access
+    Configuration config = ConfigurationConverter.getConfiguration(props);
+    
+    // Now use the Configuration API for manipulating the configuration data
+    ...
+    
+    // Return a Properties object with the results
+    return ConfigurationConverter.getProperties(config);
+}
+]]></source>
+    <p>
+      Please refer to the Javadocs of
+      <code><a href="../apidocs/org/apache/commons/configuration/ConfigurationConverter.html">
+      ConfigurationConverter</a></code> to learn more about the available
+      conversion methods and their limitations.
+    </p>
+    </subsection>
+
+    <subsection name="Interpolation of all variables">
+    <p>
+      Another issue with the integration of <em>Commons Configuration</em> with
+      native Java applications can be variables: Configuration implementations
+      are able to detect variables like <code>${myReference}</code> or
+      <code>${sys:java.version}</code> in the values of their properties and
+      substitute them by their current values (see the section
+      <a href="howto_basicfeatures.html#Variable_Interpolation">Variable
+      Interpolation</a> for more details). External components probably do not
+      know how to handle such placeholders when processing configuration files
+      written by <em>Commons Configuration</em>.
+    </p>
+    <p>
+      <code><a href="../apidocs/org/apache/commons/configuration/AbstractConfiguration.html">
+      AbstractConfiguration</a></code> provides the method
+      <code>interpolatedConfiguration()</code>. This method creates a clone of
+      the current configuration and then performs interpolation on all of its
+      properties. So the result of this method is a configuration object with
+      basically the same content as the original configuration, but with all
+      variables replaced by their actual values (as far as this was possible).
+      The following code fragment shows how a
+      <code><a href="../apidocs/org/apache/commons/configuration/PropertiesConfiguration.html">
+      PropertiesConfiguration</a></code> object can be saved in a way that the
+      resulting properties file does not contain any variables:
+    </p>
+    <source><![CDATA[
+// Load a properties file (which may contain variables)
+PropertiesConfiguration config = new PropertiesConfiguration("config.properties");
+
+// Perform interpolation on all variables
+PropertiesConfiguration extConfig =
+    (PropertiesConfiguration) config.interpolatedConfiguration();
+
+// Save the interpolated configuration (no more variables)
+extConfig.save("external_config.properties");
+]]></source>
+    </subsection>
+
+    <subsection name="Handling of runtime exceptions">
+    <p>
+      Section <a href="howto_events.html#Error_listeners">Error listeners</a>
+      introduces a way of dealing with runtime exceptions that can occur on
+      accessing configuration properties by registering an event listener. If
+      you do not want to provide a special error handler, but only need to
+      propagate the exception that caused the error event, you can make use of
+      a convenience method of the
+      <code><a href="../apidocs/org/apache/commons/configuration/ConfigurationUtils.html">
+      ConfigurationUtils</a></code> class: <code>enableRuntimeExceptions()</code>
+      registers a special error listener at the passed in configuration that
+      will throw a
+      <code><a href="../apidocs/org/apache/commons/configuration/ConfigurationRuntimeException.html">
+      ConfigurationRuntimeException</a></code> exception for each received
+      error event. The following code fragment shows an example of using this
+      method:
+    </p>
+    <source><![CDATA[
+JNDIConfiguration config = new JNDIConfiguration();
+ConfigurationUtils.enableRuntimeExceptions(config);
+
+// This may now throw a ConfigurationRuntimeException
+String value = config.getString("myKey");
+]]></source>
+    <p>
+      <code>enableRuntimeExceptions()</code> can be called for all
+      <code>Configuration</code> implementations that are derived from
+      <code><a href="../apidocs/org/apache/commons/configuration/event/EventSource.html">
+      EventSource</a></code> (which is the case for almost all configuration
+      classes provided by this library). Of course the affected implementation
+      must support the mechanism of error events, otherwise the registered
+      listener will not be triggered. In
+      <a href="howto_events.html#Error_listeners">Error listeners</a> more
+      information can be found.
+    </p>
+    </subsection>
+
+    </section>
+</body>
+
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/userguide/howto_xml.xml b/src/site/xdoc/userguide/howto_xml.xml
new file mode 100644
index 0000000..67b6b15
--- /dev/null
+++ b/src/site/xdoc/userguide/howto_xml.xml
@@ -0,0 +1,1163 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<document>
+
+ <properties>
+  <title>Hierarchical configurations and XML Howto</title>
+  <author email="oheger at apache.org">Oliver Heger</author>
+ </properties>
+
+<body>
+	<section name="Using Hierarchical Configurations">
+		<p>
+ 	 		This section explains how to use hierarchical
+    		and structured XML datasets.
+    	</p>
+    </section>
+
+	<section name="Hierarchical properties">
+		<p>
+            Many sources of configuration data have a hierarchical or tree-like
+            nature. They can represent data that is structured in many ways.
+            Such configuration sources are represented by classes derived from
+            <a href="../apidocs/org/apache/commons/configuration/HierarchicalConfiguration.html">
+            <code>HierarchicalConfiguration</code></a>.
+        </p>
+        <p>
+            Prominent examples of hierarchical configuration sources are XML
+            documents. They can be read and written using the
+            <a href="../apidocs/org/apache/commons/configuration/XMLConfiguration.html">
+            <code>XMLConfiguration</code></a> class. This section explains how
+            to deal with such structured data and demonstrates the enhanced query
+            facilities supported by <code>HierarchicalConfiguration</code>. We
+            use XML documents as examples for structured configuration sources,
+            but the information provided here (especially the rules for accessing
+            properties) applies to other hierarchical configurations as well.
+            Examples for other hierarchical configuration classes are
+            <ul>
+            <li><a href="../apidocs/org/apache/commons/configuration/CombinedConfiguration.html">
+            <code>CombinedConfiguration</code></a></li>
+            <li><a href="../apidocs/org/apache/commons/configuration/HierarchicalINIConfiguration.html">
+            <code>HierarchicalINIConfiguration</code></a></li>
+            <li><a href="../apidocs/org/apache/commons/configuration/plist/PropertyListConfiguration.html">
+            <code>PropertyListConfiguration</code></a></li>
+            </ul>
+		</p>
+        <subsection name="Accessing properties in hierarchical configurations">
+            <p>
+                We will start with a simple XML document to show some basics
+                about accessing properties. The following file named
+                <code>gui.xml</code> is used as example document:
+            </p>
+   			<source><![CDATA[
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<gui-definition>
+  <colors>
+    <background>#808080</background>
+    <text>#000000</text>
+    <header>#008000</header>
+    <link normal="#000080" visited="#800080"/>
+    <default>${colors.header}</default>
+  </colors>
+  <rowsPerPage>15</rowsPerPage>
+  <buttons>
+    <name>OK,Cancel,Help</name>
+  </buttons>
+  <numberFormat pattern="###\,###.##"/>
+</gui-definition>
+]]></source>
+			<p>
+				(As becomes obvious, this tutorial does not bother with good
+				design of XML documents, the example file should rather 
+				demonstrate the different ways of accessing properties.)
+				To access the data stored in this document it must be loaded
+                by <code>XMLConfiguration</code>. Like other 
+                <a href="howto_filebased.html">file based</a>
+                configuration classes <code>XMLConfiguration</code> supports
+                many ways of specifying the file to process. One way is to
+                pass the file name to the constructor as shown in the following
+                code fragment:
+			</p>
+   			<source><![CDATA[
+try
+{
+    XMLConfiguration config = new XMLConfiguration("tables.xml");
+    // do something with config
+}
+catch(ConfigurationException cex)
+{
+    // something went wrong, e.g. the file was not found
+}
+]]></source>
+			<p>
+				If no exception was thrown, the properties defined in the
+                XML document are now available in the configuration object.
+                Other hierarchical configuration classes that operate on files
+                have corresponding constructors and methods for loading their data.
+                The following fragment shows how the properties can be accessed:
+			</p>
+   			<source><![CDATA[
+String backColor = config.getString("colors.background");
+String textColor = config.getString("colors.text");
+String linkNormal = config.getString("colors.link[@normal]");
+String defColor = config.getString("colors.default");
+int rowsPerPage = config.getInt("rowsPerPage");
+List<Object> buttons = config.getList("buttons.name");
+]]></source>
+			<p>
+				This listing demonstrates some important points about constructing
+				keys for accessing properties in hierarchical configuration sources and about
+                features of <code>HierarchicalConfiguration</code> in general:
+				<ul>
+					<li>
+						Nested elements are accessed using a dot notation. In
+						the example document there is an element
+						<code><text></code> in the body of the
+						<code><color></code> element. The corresponding
+						key is <code>color.text</code>.
+					</li>
+					<li>
+						The root element is ignored when constructing keys. In
+						the example you do not write
+						<code>gui-definition.color.text</code>, but only
+						<code>color.text</code>.
+					</li>
+					<li>
+						Attributes of XML elements are accessed in a XPath like
+						notation.
+					</li>
+                    <li>
+                        Interpolation can be used as in <code>PropertiesConfiguration</code>.
+                        Here the <code><default></code> element in the
+                        <code>colors</code> section refers to another color.
+                    </li>
+                    <li>
+                        Lists of properties can be defined in a short form using
+                        the delimiter character (which is the comma by default).
+                        In this example the <code>buttons.name</code> property
+                        has the three values <em>OK</em>, <em>Cancel</em>, and
+                        <em>Help</em>, so it is queried using the <code>getList()</code>
+                        method. This works in attributes, too. Using the static
+                        <code>setDefaultDelimiter()</code> method of
+                        <code>AbstractConfiguration</code> you can globally
+                        define a different delimiter character or -
+                        by setting the delimiter to 0 - disabling this mechanism
+                        completely. Placing a backslash before a delimiter
+                        character will escape it. This is demonstrated in the
+                        <code>pattern</code> attribute of the <code>numberFormat</code>
+                        element.
+                    </li>
+				</ul>
+			</p>
+            <p>
+                In the next section will show how data in a more complex XML
+                document can be processed.
+            </p>
+        </subsection>
+		<subsection name="Complex hierarchical structures">
+			<p>
+				Consider the following scenario: An application operates on
+				database tables and wants to load a definition of the database
+				schema from its configuration. A XML document provides this
+				information. It could look as follows:
+			</p>
+   			<source><![CDATA[
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+
+<database>
+  <tables>
+    <table tableType="system">
+      <name>users</name>
+      <fields>
+        <field>
+          <name>uid</name>
+          <type>long</type>
+        </field>
+        <field>
+          <name>uname</name>
+          <type>java.lang.String</type>
+        </field>
+        <field>
+          <name>firstName</name>
+          <type>java.lang.String</type>
+        </field>
+        <field>
+          <name>lastName</name>
+          <type>java.lang.String</type>
+        </field>
+        <field>
+          <name>email</name>
+          <type>java.lang.String</type>
+        </field>
+      </fields>
+    </table>
+    <table tableType="application">
+      <name>documents</name>
+      <fields>
+        <field>
+          <name>docid</name>
+          <type>long</type>
+        </field>
+        <field>
+          <name>name</name>
+          <type>java.lang.String</type>
+        </field>
+        <field>
+          <name>creationDate</name>
+          <type>java.util.Date</type>
+        </field>
+        <field>
+          <name>authorID</name>
+          <type>long</type>
+        </field>
+        <field>
+          <name>version</name>
+          <type>int</type>
+        </field>
+      </fields>
+    </table>
+  </tables>
+</database>
+]]></source>
+			<p>
+				This XML is quite self explanatory; there is an arbitrary number
+				of table elements, each of it has a name and a list of fields.
+				A field in turn consists of a name and a data type. This
+                XML document (let's call it <code>tables.xml</code>) can be
+                loaded in exactly the same way as the simple document in the
+                section before.
+			</p>
+			<p>
+				When we now want to access some of the properties we face a
+                problem: the syntax for	constructing configuration keys we
+                learned so far is not powerful enough to access all of the data
+                stored in the tables document.
+			</p>
+			<p>
+				Because the document contains a list of tables some properties
+				are defined more than once. E.g. the configuration key
+				<code>tables.table.name</code> refers to a <code>name</code>
+				element inside a <code>table</code> element inside a
+				<code>tables</code> element. This constellation happens to
+				occur twice in the tables document.
+			</p>
+			<p>
+				Multiple definitions of a property do not cause problems and are
+				supported by all classes of Configuration. If such a property
+				is queried using <code>getProperty()</code>, the method
+				recognizes that there are multiple values for that property and
+				returns a collection with all these values. So we could write
+			</p>
+   			<source><![CDATA[
+Object prop = config.getProperty("tables.table.name");
+if(prop instanceof Collection)
+{
+	System.out.println("Number of tables: " + ((Collection<?>) prop).size());
+}
+]]></source>
+			<p>
+				An alternative to this code would be the <code>getList()</code>
+				method of <code>Configuration</code>. If a property is known to
+				have multiple values (as is the table name property in this example),
+				<code>getList()</code> allows retrieving all values at once.
+				<b>Note:</b> it is legal to call <code>getString()</code>
+				or one of the other getter methods on a property with multiple
+				values; it returns the first element of the list.
+			</p>
+		</subsection>
+		<subsection name="Accessing structured properties">
+			<p>
+				Okay, we can obtain a list with the names of all defined
+				tables. In the same way we can retrieve a list with the names
+				of all table fields: just pass the key
+				<code>tables.table.fields.field.name</code> to the
+				<code>getList()</code> method. In our example this list
+				would contain 10 elements, the names of all fields of all tables.
+				This is fine, but how do we know, which field belongs to
+				which table?
+			</p>
+			<p>
+				When working with such hierarchical structures the configuration keys
+				used to query properties can have an extended syntax. All components
+				of a key can be appended by a numerical value in parentheses that
+				determines the index of the affected property. So if we have two
+				<code>table</code> elements we can exactly specify, which one we
+				want to address by appending the corresponding index. This is
+				explained best by some examples:
+			</p>
+			<p>
+				We will now provide some configuration keys and show the results
+				of a <code>getProperty()</code> call with these keys as arguments.
+				<dl>
+					<dt><code>tables.table(0).name</code></dt>
+					<dd>
+						Returns the name of the first table (all indices are 0 based),
+						in this example the string <em>users</em>.
+					</dd>
+					<dt><code>tables.table(0)[@tableType]</code></dt>
+					<dd>
+						Returns the value of the tableType attribute of the first
+						table (<em>system</em>).
+					</dd>
+					<dt><code>tables.table(1).name</code></dt>
+					<dd>
+						Analogous to the first example returns the name of the
+						second table (<em>documents</em>).
+					</dd>
+					<dt><code>tables.table(2).name</code></dt>
+					<dd>
+						Here the name of a third table is queried, but because there
+						are only two tables result is <b>null</b>. The fact that a
+						<b>null</b> value is returned for invalid indices can be used
+						to find out how many values are defined for a certain property:
+						just increment the index in a loop as long as valid objects
+						are returned.
+					</dd>
+					<dt><code>tables.table(1).fields.field.name</code></dt>
+					<dd>
+						Returns a collection with the names of all fields that
+						belong to the second table. With such kind of keys it is
+						now possible to find out, which fields belong to which table.
+					</dd>
+					<dt><code>tables.table(1).fields.field(2).name</code></dt>
+					<dd>
+						The additional index after field selects a certain field.
+						This expression represents the name of the third field in
+						the second table (<em>creationDate</em>).
+					</dd>
+					<dt><code>tables.table.fields.field(0).type</code></dt>
+					<dd>
+						This key may be a bit unusual but nevertheless completely
+						valid. It selects the data types of the first fields in all
+						tables. So here a collection would be returned with the
+						values [<em>long, long</em>].
+					</dd>
+				</dl>
+			</p>
+			<p>
+				These examples should make the usage of indices quite clear.
+				Because each configuration key can contain an arbitrary number
+				of indices it is possible to navigate through complex structures of
+				hierarchical configurations; each property can be uniquely identified.
+			</p>
+            <p>
+                Sometimes dealing with long property keys may become inconvenient,
+                especially if always the same properties are accessed. For this
+                case <code>HierarchicalConfiguration</code> provides a short cut
+                with the <code>configurationAt()</code> method. This method can
+                be passed a key that selects exactly one node of the hierarchy
+                of nodes contained in a hierarchical configuration. Then a new
+                hierarchical configuration will be returned whose root node is
+                the selected node. So all property keys passed into that
+                configuration should be relative to the new root node. For
+                instance, if we are only interested in information about the
+                first database table, we could do something like that:
+            </p>
+   			<source><![CDATA[
+HierarchicalConfiguration sub = config.configurationAt("tables.table(0)");
+String tableName = sub.getString("name");  // only need to provide relative path
+List<Object> fieldNames = sub.getList("fields.field.name");
+]]></source>
+            <p>
+                For dealing with complex list-like structures there is another
+                short cut. Often it will be necessary to iterate over all items
+                in the list and access their (sub) properties. A good example are
+                the fields of the tables in our demo configuration. When you want
+                to process all fields of a table (e.g. for constructing a
+                <code>CREATE TABLE</code> statement), you will need all information
+                stored for them in the configuration. An option would be to use
+                the <code>getList()</code> method to fetch the required data one
+                by one:
+            </p>
+   			<source><![CDATA[
+List<Object> fieldNames = config.getList("tables.table(0).fields.field.name");
+List<Object> fieldTypes = config.getList("tables.table(0).fields.field.type");
+List<Object> ... // further calls for other data that might be stored in the config
+]]></source>
+            <p>
+                But this is not very readable and will fail if not all field
+                elements contain the same set of data (for instance the
+                <code>type</code> property may be optional, then the list for
+                the types can contain less elements than the other lists). A
+                solution to these problems is the <code>configurationsAt()</code>
+                method, a close relative to the <code>configurationAt()</code>
+                method covered above. This method evaluates the passed in key and
+                collects all configuration nodes that match this criterion. Then
+                for each node a <code>HierarchicalConfiguration</code> object is
+                created with this node as root node. A list with these configuration
+                objects is returned. As the following example shows this comes in
+                very handy when processing list-like structures:
+            </p>
+   			<source><![CDATA[
+List<HierarchicalConfiguration> fields = 
+    config.configurationsAt("tables.table(0).fields.field");
+for(HierarchicalConfiguration sub : fields)
+{
+    // sub contains all data about a single field
+    String fieldName = sub.getString("name");
+    String fieldType = sub.getString("type");
+    ...
+]]></source>
+        <p>
+          The configurations returned by the <code>configurationAt()</code> and
+          <code>configurationsAt()</code> method are in fact instances of the
+          <a href="../apidocs/org/apache/commons/configuration/SubnodeConfiguration.html">
+          <code>SubnodeConfiguration</code></a> class. The API documentation of
+          this class contains more information about its features and
+          limitations.
+        </p>
+		</subsection>
+		<subsection name="Adding new properties">
+			<p>
+				So far we have learned how to use indices to avoid ambiguities when
+				querying properties. The same problem occurs when adding new
+				properties to a structured configuration. As an example let's
+				assume we want to add a new field to the second table. New properties
+				can be added to a configuration using the <code>addProperty()</code>
+				method. Of course, we have to exactly specify where in the tree like structure new
+				data is to be inserted. A statement like
+			</p>
+   			<source><![CDATA[
+// Warning: This might cause trouble!
+config.addProperty("tables.table.fields.field.name", "size");
+]]></source>
+			<p>
+				would not be sufficient because it does not contain all needed
+				information. How is such a statement processed by the
+				<code>addProperty()</code> method?
+			</p>
+			<p>
+				<code>addProperty()</code> splits the provided key into its
+				single parts and navigates through the properties tree along the
+				corresponding element names. In this example it will start at the
+				root element and then find the <code>tables</code> element. The
+				next key part to be processed is <code>table</code>, but here a
+				problem occurs: the configuration contains two <code>table</code>
+				properties below the <code>tables</code> element. To get rid off
+				this ambiguity an index can be specified at this position in the
+				key that makes clear, which of the two properties should be
+				followed. <code>tables.table(1).fields.field.name</code> e.g.
+				would select the second <code>table</code> property. If an index
+				is missing, <code>addProperty()</code> always follows the last
+				available element. In our example this would be the second
+				<code>table</code>, too.
+			</p>
+			<p>
+				The following parts of the key are processed in exactly the same
+				manner. Under the selected <code>table</code> property there is
+				exactly one <code>fields</code> property, so this step is not
+				problematic at all. In the next step the <code>field</code> part
+				has to be processed. At the actual position in the properties tree
+				there are multiple <code>field</code> (sub) properties. So we here
+				have the same situation as for the <code>table</code> part.
+				Because no explicit index is defined the last <code>field</code>
+				property is selected. The last part of the key passed to
+				<code>addProperty()</code> (<code>name</code> in this example)
+				will always be added as new property at the position that has
+				been reached in the former processing steps. So in our example
+				the last <code>field</code> property of the second table would
+				be given a new <code>name</code> sub property and the resulting
+				structure would look like the following listing:
+			</p>
+   			<source><![CDATA[
+	...
+    <table tableType="application">
+      <name>documents</name>
+      <fields>
+        <field>
+          <name>docid</name>
+          <type>long</type>
+        </field>
+        <field>
+          <name>name</name>
+          <type>java.lang.String</type>
+        </field>
+        <field>
+          <name>creationDate</name>
+          <type>java.util.Date</type>
+        </field>
+        <field>
+          <name>authorID</name>
+          <type>long</type>
+        </field>
+        <field>
+          <name>version</name>
+          <name>size</name>    <== Newly added property
+          <type>int</type>
+        </field>
+      </fields>
+    </table>
+  </tables>
+</database>
+]]></source>
+			<p>
+				This result is obviously not what was desired, but it demonstrates
+				how <code>addProperty()</code> works: the method follows an
+				existing branch in the properties tree and adds new leaves to it.
+				(If the passed in key does not match a branch in the existing tree,
+				a new branch will be added. E.g. if we pass the key
+				<code>tables.table.data.first.test</code>, the existing tree can be
+				navigated until the <code>data</code> part of the key. From here a
+				new branch is started with the remaining parts <code>data</code>,
+				<code>first</code> and <code>test</code>.)
+			</p>
+			<p>
+				If we want a different behavior, we must explicitely tell
+				<code>addProperty()</code> what to do. In our example with the
+				new field our intension was to create a new branch for the
+				<code>field</code> part in the key, so that a new <code>field</code>
+				property is added to the structure rather than adding sub properties
+				to the last existing <code>field</code> property. This can be
+				achieved by specifying the special index <code>(-1)</code> at the
+				corresponding position in the key as shown below:
+			</p>
+   			<source><![CDATA[
+config.addProperty("tables.table(1).fields.field(-1).name", "size");
+config.addProperty("tables.table(1).fields.field.type", "int");
+]]></source>
+			<p>
+				The first line in this fragment specifies that a new branch is
+				to be created for the <code>field</code> property (index -1).
+				In the second line no index is specified for the field, so the
+				last one is used - which happens to be the field that has just
+				been created. So these two statements add a fully defined field
+				to the second table. This is the default pattern for adding new
+				properties or whole hierarchies of properties: first create a new
+				branch in the properties tree and then populate its sub properties.
+				As an additional example let's add a complete new table definition
+				to our example configuration:
+			</p>
+   			<source><![CDATA[
+// Add a new table element and define the name
+config.addProperty("tables.table(-1).name", "versions");
+
+// Add a new field to the new table
+// (an index for the table is not necessary because the latest is used)
+config.addProperty("tables.table.fields.field(-1).name", "id");
+config.addProperty("tables.table.fields.field.type", "int");
+
+// Add another field to the new table
+config.addProperty("tables.table.fields.field(-1).name", "date");
+config.addProperty("tables.table.fields.field.type", "java.sql.Date");
+...
+]]></source>
+			<p>
+				For more information about adding properties to a hierarchical
+				configuration also have a look at the javadocs for
+				<code>HierarchicalConfiguration</code>.
+			</p>
+		</subsection>
+		<subsection name="Escaping special characters">
+            <p>
+                Some characters in property keys or values require a special
+                treatment.
+            </p>
+			<p>
+                Per default the dot character is used as delimiter by most
+                configuration classes (we will learn how to change this for
+                hierarchical configurations in a later section). In some
+                configuration formats however, dots can be contained in the
+                names of properties. For instance, in XML the dot is a legal
+                character that can occur in any tag. The same is true for the names
+                of properties in windows ini files. So the following XML
+                document is completely valid:
+			</p>
+   			<source><![CDATA[
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+
+<configuration>
+  <test.value>42</test.value>
+  <test.complex>
+    <test.sub.element>many dots</test.sub.element>
+  </test.complex>
+</configuration>
+]]></source>
+			<p>
+                This XML document can be loaded by <code>XMLConfiguration</code>
+                without trouble, but when we want to access certain properties
+                we face a problem: The configuration claims that it does not
+                store any values for the properties with the keys
+                <code>test.value</code> or <code>test.complex.test.sub.element</code>!
+            </p>
+            <p>
+                Of course, it is the dot character contained in the property
+                names, which causes this problem. A dot is always interpreted
+                as a delimiter between elements. So given the property key
+                <code>test.value</code> the configuration would look for an
+                element named <code>test</code> and then for a sub element
+                with the name <code>value</code>. To change this behavior it is
+                possible to escape a dot character, thus telling the configuration
+                that it is really part of an element name. This is simply done
+                by duplicating the dot. So the following statements will return
+                the desired property values:
+            </p>
+   			<source><![CDATA[
+int testVal = config.getInt("test..value");
+String complex = config.getString("test..complex.test..sub..element");
+]]></source>
+            <p>
+                Note the duplicated dots whereever the dot does not act as
+                delimiter. This way it is possible to access properties containing
+                dots in arbitrary combination. However, as you can see, the
+                escaping can be confusing sometimes. So if you have a choice,
+                you should avoid dots in the tag names of your XML configuration
+                files or other configuration sources.
+            </p>
+            <p>
+                Another source of problems is related to list delimiter characters
+                in the values of properties. Like other configuration classes
+                <code>XMLConfiguration</code> implements
+                <a href="howto_basicfeatures.html#List_handling">list handling</a>.
+                This means that the values of XML elements and attributes are
+                checked whether they contain a list delimiter character. If this
+                is the case, the value is split, and a list property is created.
+                Per default this feature is enabled. Have a look at the
+                following example:
+            </p>
+   			<source><![CDATA[
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+
+<configuration>
+  <pi>3,1415</pi>
+</configuration>
+]]></source>
+            <p>
+                Here we use the comma as delimiter for fraction digits (as is
+                standard for some languages). However, the configuration will
+                interpret the comma as list delimiter character and assign the
+                property <em>pi</em> the two values 3 and 1415. This was not
+                desired.
+            </p>
+            <p>
+                XML has a natural way of defining list properties by simply
+                repeating elements. So defining multiple values of a property in
+                a single element or attribute is a rather untypical use case.
+                Unfortunately, early versions of Commons Configuration had list
+                delimiter splitting enabled per default. Later it became obvious
+                that this feature can cause serious problems related to the
+                interpretation of property values and the escaping of delimiter
+                characters. For reasons of backwards compatibility we have to
+                stick to this approach in the 1.x series though.
+            </p>
+            <p>
+                In the next major release the handling of lists will propably be
+                reworked. Therefore it is recommended not to use this feature.
+                You are save if you disable it immediately after the creation of
+                an <code>XMLConfiguration</code> object (and before a file is
+                loaded). This can be achieved as follows:
+            </p>
+   			<source><![CDATA[
+XMLConfiguration config = new XMLConfiguration();
+config.setDelimiterParsingDisabled(true);
+config.setAttributeSplittingDisabled(true);
+config.load("config.xml");
+]]></source>
+        </subsection>
+	</section>
+
+    <section name="Expression engines">
+        <p>
+            In the previous chapters we saw many examples about how properties
+            in a <code>XMLConfiguration</code> object (or more general in a
+            <code>HierarchicalConfiguration</code> object, because this is the
+            base class, which implements this functionality) can be queried or
+            modified using a special syntax for the property keys. Well, this
+            was not the full truth. Actually, property keys are not processed
+            by the configuration object itself, but are delegated to a helper
+            object, a so called <em>Expression engine</em>.
+        </p>
+        <p>
+            The separation of the task of interpreting property keys into a
+            helper object is a typical application of the <em>Strategy</em>
+            design pattern. In this case it also has the advantage that it
+            becomes possible to plug in different expression engines into a
+            <code>HierarchicalConfiguration</code> object. So by providing
+            different implementations of the
+            <a href="../apidocs/org/apache/commons/configuration/tree/ExpressionEngine.html">
+            <code>ExpressionEngine</code></a>
+            interface hierarchical configurations can support alternative
+            expression languages for accessing their data.
+        </p>
+        <p>
+            Before we discuss the available expression engines that ship
+            with Commons Configuration, it should be explained how an
+            expression engine can be associated with a configuration object.
+            <a href="../apidocs/org/apache/commons/configuration/HierarchicalConfiguration.html">
+            <code>HierarchicalConfiguration</code></a> and all derived classes
+            provide a <code>setExpressionEngine()</code> method, which expects
+            an implementation of the <code>ExpressionEngine</code> interface as
+            argument. After this method was called, the configuration object will
+            use the passed expression engine, which means that all property keys
+            passed to methods like <code>getProperty()</code>,
+            <code>getString()</code>, or <code>addProperty()</code> must
+            conform to the syntax supported by this engine. Property keys
+            returned by the <code>getKeys()</code> method will follow this
+            syntax, too.
+        </p>
+        <p>
+            In addition to instance specific expression engines that change the
+            behavior of single configuration objects it is also possible to set
+            a global expression engine. This engine is shared between all
+            hierarchical configuration objects, for which no specific expression
+            engine was set. The global expression engine can be set using the
+            static <code>setDefaultExpressionEngine()</code> method of
+            <code>HierarchicalConfiguration</code>. By invoking this method with
+            a custom expression engine the syntax of all hierarchical configuration
+            objects can be altered at once.
+        </p>
+        
+        <subsection name="The default expression engine">
+            <p>
+                The syntax described so far for property keys of hierarchical
+                configurations is implemented by a specific implementation of the
+                <a href="../apidocs/org/apache/commons/configuration/tree/ExpressionEngine.html">
+                <code>ExpressionEngine</code></a> interface called
+                <a href="../apidocs/org/apache/commons/configuration/tree/DefaultExpressionEngine.html">
+                <code>DefaultExpressionEngine</code></a>. An instance of this class
+                is installed as the global expression engine in
+                <code>HierarchicalConfiguration</code>. So all newly created
+                instances of this class will make use of this engine (which is
+                the reason that our examples above worked).
+            </p>
+            <p>
+                After reading the examples of property keys provided so far in
+                this document you should have a sound understanding regarding
+                the features and the syntax supported by the
+                <code>DefaultExpressionEngine</code> class. But it can do a
+                little bit more for you: it defines a bunch of properties,
+                which can be used to customize most tokens that can appear in a
+                valid property key. You prefer curly brackets over paranthesis
+                as index markers? You find the duplicated dot as escaped
+                property delimiter counter-intuitive? Well, simply go ahead and
+                change it! The following example shows how the syntax of a
+                <code>DefaultExpressionEngine</code> object is modified. Then
+                this object is set as the global expression engine, so that from
+                now on all hierarchical configuration objects will take up this
+                new syntax:
+            </p>
+            <source><![CDATA[
+DefaultExpressionEngine engine = new DefaultExpressionEngine();
+
+// Use a slash as property delimiter
+engine.setPropertyDelimiter("/");
+// Indices should be provided in curly brackets
+engine.setIndexStart("{");
+engine.setIndexEnd("}");
+// For attributes use simply a @
+engine.setAttributeStart("@");
+engine.setAttributeEnd(null);
+// A Backslash is used for escaping property delimiters
+engine.setEscapedDelimiter("\\/");
+
+// Now install this engine as the global engine
+HierarchicalConfiguration.setDefaultExpressionEngine(engine);
+
+// Access properties using the new syntax
+HierarchicalConfiguration config = ...
+String tableName = config.getString("tables/table{0}/name");
+String tableType = config.getString("tables/table{0}@type");
+         ]]></source>
+            <p>
+                <em>Tip:</em> Sometimes when processing an XML document you
+                don't want to distinguish between attributes and "normal"
+                child nodes. You can achieve this by setting the
+                <code>AttributeEnd</code> property to <b>null</b> and the
+                <code>AttributeStart</code> property to the same value as the
+                <code>PropertyDelimiter</code> property. Then the syntax for
+                accessing attributes is the same as the syntax for other
+                properties:
+            </p>
+            <source><![CDATA[
+DefaultExpressionEngine engine = new DefaultExpressionEngine();
+engine.setAttributeEnd(null);
+engine.setAttributeStart(engine.getPropertyDelimiter());
+...
+Object value = config.getProperty("tables.table(0).name");
+// name can either be a child node of table or an attribute
+         ]]></source>
+        </subsection>
+        
+        <subsection name="The XPATH expression engine">
+            <p>
+                The expression language provided by the <code>DefaultExpressionEngine</code>
+                class is powerful enough to address all properties in a
+                hierarchical configuration, but it is not always convenient to
+                use. Especially if list structures are involved, it is often
+                necessary to iterate through the whole list to find a certain
+                element.
+            </p>
+            <p>
+                Think about our example configuration that stores information about
+                database tables. A use case could be to load all fields that belong
+                to the "users" table. If you knew the index of this
+                table, you could simply build a property key like
+                <code>tables.table(<index>).fields.field.name</code>,
+                but how do you find out the correct index? When using the
+                default expression engine, the only solution to this problem is
+                to iterate over all tables until you find the "users"
+                table.
+            </p>
+            <p>
+                Life would be much easier if an expression language could be used,
+                which would directly support queries of such kind. In the XML
+                world, the XPATH syntax has grown popular as a powerful means
+                of querying structured data. In XPATH a query that selects all
+                field names of the "users" table would look something
+                like <code>tables/table[@name='users']/fields/name</code> (here
+                we assume that the table's name is modelled as an attribute).
+                This is not only much simpler than an iteration over all tables,
+                but also much more readable: it is quite obvious, which fields
+                are selected by this query.
+            </p>
+            <p>
+                Given the power of XPATH it is no wonder that we got many
+                user requests to add XPATH support to Commons Configuration.
+                Well, here is it!
+            </p>
+            <p>
+                For enabling XPATH syntax for property keys you need the
+                <a href="../apidocs/org/apache/commons/configuration/tree/xpath/XPathExpressionEngine.html">
+                <code>XPathExpressionEngine</code></a> class. This class
+                implements the <code>ExpressionEngine</code> interface and can
+                be plugged into a <code>HierarchicalConfiguration</code> object
+                using the <code>setExpressionEngine()</code> method. It is also
+                possible to set an instance of this class as the global
+                expression engine, so that all hierarchical configuration
+                objects make use of XPATH syntax. The following code fragment
+                shows how XPATH support can be enabled for a configuration
+                object:
+            </p>
+            <source><![CDATA[
+HierarchicalConfiguration config = ...
+config.setExpressionEngine(new XPathExpressionEngine());
+
+// Now we can use XPATH queries:
+List<Object> fields = config.getList("tables/table[1]/fields/name");
+         ]]></source>
+            <p>
+                XPATH expressions are not only used for selecting properties
+                (i.e. for the several getter methods), but also for adding new
+                properties. For this purpose the keys passed into the
+                <code>addProperty()</code> method must conform to a special
+                syntax. They consist of two parts: the first part is an
+                arbitrary XPATH expression that selects the node where the new
+                property is to be added to, the second part defines the new
+                element to be added. Both parts are separated by whitespace.
+            </p>
+            <p>
+                Okay, let's make an example. Say, we want to add a <code>type</code>
+                property under the first table (as a sibling to the <code>name</code>
+                element). Then the first part of our key will have to select
+                the first table element, the second part will simply be
+                <code>type</code>, i.e. the name of the new property:
+            </p>
+            <source><![CDATA[
+config.addProperty("tables/table[1] type", "system");
+         ]]></source>
+            <p>
+                (Note that indices in XPATH are 1-based, while in the default
+                expression language they are 0-based.) In this example the part
+                <code>tables/table[1]</code> selects the target element of the
+                add operation. This element must exist and must be unique, otherwise an exception
+                will be thrown. <code>type</code> is the name of the new element
+                that will be added. If instead of a normal element an attribute
+                should be added, the example becomes
+            </p>
+            <source><![CDATA[
+config.addProperty("tables/table[1] @type", "system");
+         ]]></source>
+            <p>
+                It is possible to add complete paths at once. Then the single
+                elements in the new path are separated by "/"
+                characters. The following example shows how data about a new
+                table can be added to the configuration. Here we use full paths:
+            </p>
+            <source><![CDATA[
+// Add new table "tasks" with name element and type attribute
+config.addProperty("tables table/name", "tasks");
+// last() selects the last element of this name,
+// which is the newest table element
+config.addProperty("tables/table[last()] @type", "system");
+
+// Now add fields
+config.addProperty("tables/table[last()] fields/field/name", "taskid");
+config.addProperty("tables/table[last()]/fields/field[last()] type", "int");
+config.addProperty("tables/table[last()]/fields field/name", "name");
+config.addProperty("tables/table[last()]/fields field/name", "startDate");
+...
+         ]]></source>
+            <p>
+                The first line of this example adds the path <code>table/name</code>
+                to the <code>tables</code> element, i.e. a new <code>table</code>
+                element will be created and added as last child to the
+                <code>tables</code> element. Then a new <code>name</code> element
+                is added as child to the new <code>table</code> element. To this
+                element the value "tasks" is assigned. The next line
+                adds a <code>type</code> attribute to the new table element. To
+                obtain the correct <code>table</code> element, to which the
+                attribute must be added, the XPATH function <code>last()</code>
+                is used; this function selects the last element with a given
+                name, which in this case is the new <code>table</code> element.
+                The following lines all use the same approach to construct a new
+                element hierarchy: At first complete new branches are added
+                (<code>fields/field/name</code>), then to the newly created
+                elements further children are added.
+            </p>
+            <p>
+                There is one gotcha with these keys described so far: they do
+                not work with the <code>setProperty()</code> method! This is
+                because <code>setProperty()</code> has to check whether the
+                passed in key already exists; therefore it needs a key which can
+                be interpreted by query methods. If you want to use
+                <code>setProperty()</code>, you can pass in regular keys (i.e.
+                without a whitespace separator). The method then tries to figure
+                out which part of the key already exists in the configuration
+                and adds new nodes as necessary. In principle such regular keys
+                can also be used with <code>addProperty()</code>. However, they
+                do not contain sufficient information to decide where new nodes
+                should be added.
+            </p>
+            <p>
+                To make this clearer let's go back to the example with the
+                tables. Consider that there is a configuration which already
+                contains information about some database tables. In order to add
+                a new table element in the configuration
+                <code>addProperty()</code> could be used as follows:
+            </p>
+            <source><![CDATA[
+config.addProperty("tables/table/name", "documents");
+         ]]></source>
+            <p>
+                In the configuration a <code><tables></code> element
+                already exists, also <code><table></code> and
+                <code><name></code> elements. How should the expression
+                engine know where new node structures are to be added? The
+                solution to this problem is to provide this information in the
+                key by stating:
+            </p>
+            <source><![CDATA[
+config.addProperty("tables table/name", "documents");
+         ]]></source>
+            <p>
+                Now it is clear that new nodes should be added as children of
+                the  <code><tables></code> element. More information about
+                keys and how they play together with <code>addProperty()</code>
+                and <code>setProperty()</code> can be found in the Javadocs for
+                <a href="../apidocs/org/apache/commons/configuration/tree/xpath/XPathExpressionEngine.html">
+                <code>XPathExpressionEngine</code></a>.
+            </p>
+            <p>
+                <em>Note:</em> XPATH support is implemented through
+                <a href="http://commons.apache.org/jxpath">Commons JXPath</a>.
+                So when making use of this feature, be sure you include the
+                commons-jxpath jar in your classpath.
+            </p>
+            <p>
+                In this tutorial we don't want to describe XPATH syntax and
+                expressions in detail. Please refer to corresponding documentation.
+                It is important to mention that by embedding Commons JXPath the
+                full extent of the XPATH 1.0 standard can be used for constructing
+                property keys.
+            </p>
+        </subsection>
+    </section>
+
+    <section name="Validation of XML configuration files">
+        <p>
+            XML parsers provide support for validation of XML documents to ensure that they
+            conform to a certain DTD or XML Schema. This feature can be useful for
+            configuration files, too. <code>XMLConfiguration</code> allows this feature
+            to be enabled when files are loaded.
+        </p>
+        <subsection name="Validation using a DTD">
+        <p>
+            The easiest way to turn on validation is to simply set the
+            <code>validating</code> property to true as shown in the
+            following example:
+        </p>
+        <source><![CDATA[
+XMLConfiguration config = new XMLConfiguration();
+config.setFileName("myconfig.xml");
+config.setValidating(true);
+
+// This will throw a ConfigurationException if the XML document does not
+// conform to its DTD.
+config.load();
+]]></source>
+        <p>
+            Setting the <code>validating</code> flag to true will cause
+            <code>XMLConfiguration</code> to use a validating XML parser. At this parser
+            a custom <code>ErrorHandler</code> will be registered, which throws
+            exceptions on simple and fatal parsing errors.
+        </p>
+        </subsection>
+        <subsection name="Validation using a Schema">
+        <p>
+            XML Parsers also provide support for validating XML documents using an
+            XML Schema. XMLConfiguration provides a simple mechanism for enabling
+            this by setting the <code>schemaValidation</code> flag to true. This
+            will also set the <code>validating</code> flag to true so both do not
+            need to be set. The XML Parser will then use the schema defined in the
+            XML document to validate it. Enabling schema validation will also
+            enable the parser's namespace support.
+        </p>
+        <p>
+        <source><![CDATA[
+XMLConfiguration config = new XMLConfiguration();
+config.setFileName("myconfig.xml");
+config.setSchemaValidation(true);
+
+// This will throw a ConfigurationException if the XML document does not
+// conform to its Schema.
+config.load();
+]]></source>
+        </p>
+        </subsection>
+        <subsection name="Default Entity Resolution">
+        <p>
+            There is also some support for dealing with DTD files. Often the
+            DTD of an XML document is stored locally so that it can be quickly
+            accessed. However the <code>DOCTYPE</code> declaration of the document
+            points to a location on the web as in the following example:
+        </p>
+        <source><![CDATA[
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE web-app
+  PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
+  "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
+]]></source>
+        <p>
+            When working with XML documents directly you would use an
+            <code>EntityResolver</code> in such a case. The task of such an
+            entity resolver is to point the XML parser to the location of the
+            file referred to by the declaration. So in our example the entity
+            resolver would load the DTD file from a local cache instead of
+            retrieving it from the internet.
+        </p>
+        <p>
+            <code>XMLConfiguration</code> provides a simple default implementation of
+            an <code>EntityResolver</code>. This implementation is initialized
+            by calling the <code>registerEntityId()</code> method with the
+            public IDs of the entities to be retrieved and their corresponding
+            local URLs. This method has to be called before the configuration
+            is loaded. To continue our example, consider that the DTD file for
+            our example document is stored on the class path. We can register it
+            at <code>XMLConfiguration</code> using the following code:
+        </p>
+        <source><![CDATA[
+XMLConfiguration config = new XMLConfiguration();
+// load the URL to the DTD file from class path
+URL dtdURL = getClass().getResource("web-app_2.2.dtd");
+// register it at the configuration
+config.registerEntityId("-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN",
+    dtdURL);
+config.setValidating(true);  // enable validation
+config.setFileName("web.xml");
+config.load();
+]]></source>
+        <p>
+            This basically tells the XML configuration to use the specified
+            URL when it encounters the given public ID. Note that the call to
+            <code>registerEntityId()</code> has to be performed before the
+            configuration is loaded. So you cannot use one of the constructors
+            that directly load the configuration.
+        </p>
+        </subsection>
+        <subsection name="Enhanced Entity Resolution">
+        <p>
+            While the default entity resolver can be used under certain circumstances,
+            it does not work well when using the DefaultConfigurationBuilder.
+            Furthermore, in many circumstances the programmatic nature of
+            registering entities will tie the application tightly to the
+            XML content. In addition, because it only works with the public id it
+            cannot support XML documents using an XML Schema.
+        </p>
+        <p>
+            <a href="http://xml.apache.org/commons/components/resolver/resolver-article.html#s.whats.wrong">XML
+            Entity and URI Resolvers</a> describes using a set of catalog files to
+            resolve enitities. Commons Configuration provides support for
+            this Catalog Resolver through its own CatalogResolver class.
+        </p>
+         <source><![CDATA[
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<Employees xmlns="http://commons.apache.org/employee"
+           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+           xsi:schemaLocation="http://commons.apache.org/employee http://commons.apache.org/sample.xsd">
+  <Employee>
+    <SSN>555121211</SSN>
+    <Name>John Doe</Name>
+    <DateOfBirth>1975-05-15</DateOfBirth>
+    <EmployeeType>Exempt</EmployeeType>
+    <Salary>100000</Salary>
+  </Employee>
+</Employees>]]></source>
+        <p>
+            The XML sample above is an XML document using a default namespace of
+            http://commons.apache.org/employee. The schemaLocation allows a set
+            of namespaces and hints to the location of their corresponding
+            schemas. When processing the document the parser will pass the hint,
+            in this case http://commons.apache.org/sample.xsd, to the entity resolver
+            as the system id. More information on using schema locations can be found
+            at <a href="http://www.w3.org/TR/xmlschema-0/#schemaLocation">schemaLocation</a>.
+        </p>
+        <p>
+            The example that follows shows how to use the CatalogResolver when
+            processing an XMLConfiguration. It should be noted that by using the
+            setEntityResolver method any EntityResolver may be used, not just those
+            provided by Commons Configuration.
+        </p>
+        <source><![CDATA[
+CatalogResolver resolver = new CatalogResolver();
+resolver.setCatalogFiles("local/catalog.xml","http://test.org/catalogs/catalog1.xml");
+XMLConfiguration config = new XMLConfiguration();
+config.setEntityResolver(resolver);
+config.setSchemaValidation(true);  // enable schema validation
+config.setFileName("config.xml");
+config.load();
+]]></source>
+        </subsection>
+        <subsection name="Extending Validation and Entity Resolution">
+        <p>
+            The mechanisms provided with Commons Configuration will hopefully be
+            sufficient in most cases, however there will certainly be circumstances
+            where they are not. XMLConfiguration provides two extension mechanisms
+            that should provide applications with all the flexibility they may
+            need. The first, registering a custom Entity Resolver has already been
+            discussed in the preceeding section. The second is that XMLConfiguration
+            provides a generic way of setting up the XML parser to use: A preconfigured
+            <code>DocumentBuilder</code> object can be passed to the
+            <code>setDocumentBuilder()</code> method.
+        </p>
+        <p>
+            So an application can create a <code>DocumentBuilder</code> object
+            and initialize it according to its special needs. Then this
+            object must be passed to the <code>XMLConfiguration</code> instance
+            before invocation of the <code>load()</code> method. When loading
+            a configuration file, the passed in <code>DocumentBuilder</code> will
+            be used instead of the default one. <em>Note:</em> If a custom
+            <code>DocumentBuilder</code> is used, the default implementation of
+            the <code>EntityResolver</code> interface is disabled. This means
+            that the <code>registerEntityId()</code> method has no effect in
+            this mode.
+        </p>
+        </subsection>
+    </section>
+</body>
+
+</document>
\ No newline at end of file
diff --git a/src/site/xdoc/userguide/overview.xml b/src/site/xdoc/userguide/overview.xml
new file mode 100644
index 0000000..a6e4d1e
--- /dev/null
+++ b/src/site/xdoc/userguide/overview.xml
@@ -0,0 +1,213 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<document>
+  <properties>
+    <title>Configuration Overview</title>
+    <author email="epugh at upstate.com">Eric Pugh</author>
+    <author email="smanux at lfjr.net">Emmanuel Bourg</author>
+  </properties>
+  <body>
+
+    <section name="Using Configuration">
+      <p>
+        Commons Configuration allows you to access configuration properties from
+        a variety of different sources. No matter if they are stored in a properties file,
+        a XML document, or a JNDI tree, they can all be accessed in the same way
+        through the generic <code><a href="../apidocs/org/apache/commons/configuration/Configuration.html">Configuration</a></code>
+        interface.
+      </p>
+      <p>
+        Another strength of Commons Configuration is its ability to mix configurations
+        from heterogeneous sources and treat them like a single logic configuration.
+        This section will introduce you to the different configurations
+        available and will show you how to combine them.
+      </p>
+
+      <subsection name="Configuration Sources">
+      <p>
+        Currently there are quite a number of different sources of Configuration objects. But,
+        by just using a Configuration object versus a specific type like XMLConfiguration or
+        JNDIConfiguration, you are sheltered from the mechanics of actually retrieving the
+        configuration values. These various sources include:
+        <ul>
+          <li>
+              <strong>PropertiesConfiguration</strong>
+              Loads configuration values from a properties file.
+          </li>
+          <li>
+              <strong>XMLConfiguration</strong>
+              Takes values from an XML document.
+          </li>
+          <li>
+              <strong>INIConfiguration</strong>
+              Loads the values from a .ini file as used by Windows.
+          </li>
+          <li>
+              <strong>PropertyListConfiguration</strong>
+              Loads values from an OpenStep .plist file. XMLPropertyListConfiguration is also
+              available to read the XML variant used by Mac OS X.
+          </li>
+          <li>
+              <strong>JNDIConfiguration</strong>
+              Using a key in the JNDI tree, can retrieve values as configuration properties.
+          </li>
+          <li>
+              <strong>BaseConfiguration</strong>
+              An in-memory method of populating a Configuration object.
+          </li>
+          <li>
+              <strong>HierarchicalConfiguration</strong>
+              An in-memory Configuration object that is able to deal with complex
+              structured data.
+          </li>
+          <li>
+              <strong>SystemConfiguration</strong>
+              A configuration using the system properties
+          </li>
+          <li>
+              <strong>ConfigurationConverter</strong>
+              Takes a java.util.Properties or an org.apache.commons.collections.ExtendedProperties
+              and converts it to a Configuration object.
+          </li>
+       </ul>
+
+      </p>
+      </subsection>
+
+      <subsection name="Mixing Configuration Sources">
+      <p>
+        Often you want to provide a base set of configuration values, but allow the user to easily
+        override them for their specific environment.  Well one way is to hard code the default
+        values into your code, and have then provide a property file that overrides this.  However,
+        this is a very rigid way of doing things. Instead, with the <code>CompositeConfiguration</code>
+        you can provide many different ways of setting up a configuration. You can either do it
+        manually:
+      </p>
+
+<source>
+CompositeConfiguration config = new CompositeConfiguration();
+config.addConfiguration(new SystemConfiguration());
+config.addConfiguration(new PropertiesConfiguration("application.properties"));
+</source>
+
+      <p>or via the <code>ConfigurationFactory</code> class:</p>
+
+<source>
+ConfigurationFactory factory = new ConfigurationFactory("config.xml");
+Configuration config = factory.getConfiguration();
+</source>
+
+      <p>
+        The <code>config.xml</code> file used in the example above is a configuration descriptor,
+        it specifies the Configuration objects to load. Here is an example of descriptor:
+      </p>
+
+<source><![CDATA[
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+
+<configuration>
+  <system/>
+  <properties fileName="application.properties"/>
+</configuration>
+]]></source>
+
+      <p>
+        What this says is that we are loading up all system properties, as well as the properties
+        file <code>application.properties</code>. The order of precedence is first to last. So in
+        the above example, if a property is not found in the system properties, it'll be searched
+        in the properties file. This allows you to set up default values in a properties file, and
+        override them with the system properties.
+      </p>
+      </subsection>
+      
+      <subsection name="The Configuration interface">
+      <p>
+        All the classes in this package that represent different kinds of configuration
+        sources share a single interface:
+        <code><a href="../apidocs/org/apache/commons/configuration/Configuration.html">Configuration</a></code>.
+        This interface allows you to access and manipulate configuration properties
+        in a generic way.
+      </p>
+      <p>
+        A major part of the methods defined in the <code>Configuration</code>
+        interface deals with retrieving properties of different data types. All
+        these methods take a key as an argument that points to the desired
+        property. This is a string value whose exact meaning depends on the
+        concrete <code>Configuration</code> implementation used. They try to
+        find the property specified by the passed in key and convert it to their
+        target type; this converted value will be returned. There are also
+        overloaded variants of all methods that allow to specify a default value,
+        which will be returned if the property cannot be found. The following
+        data types are supported:
+        <ul>
+          <li>BigDecimal</li>
+          <li>BigInteger</li>
+          <li>boolean</li>
+          <li>byte</li>
+          <li>double</li>
+          <li>float</li>
+          <li>int</li>
+          <li>long</li>
+          <li>short</li>
+          <li>String</li>
+        </ul>
+        The names of these methods start with <code>get</code> followed by their
+        data type. The <code>getString()</code> method for instance will return
+        String values, <code>getInt()</code> will operate on integers.
+      </p>
+      <p>
+        Properties can have multiple values, so it is also possible to query a
+        list containing all of the available values. This is done using the
+        <code>getList()</code> method.
+      </p>
+      <p>
+        For manipulating properties or their values the following methods can
+        be used:
+        <dl>
+          <dt><code>addProperty()</code></dt>
+          <dd>Adds a new property to the configuration. If this property already
+          exists, another value is added to it (so it becomes a multi-valued
+          property).</dd>
+          <dt><code>clearProperty()</code></dt>
+          <dd>Removes the specified property from the configuration.</dd>
+          <dt><code>setProperty()</code></dt>
+          <dd>Overwrites the value of the specified property. This is the same
+          as removing the property and then calling <code>addProperty()</code>
+          with the new property value.</dd>
+          <dt><code>clear()</code></dt>
+          <dd>Wipes out the whole configuration</dd>
+        </dl>
+      </p>
+      </subsection>
+
+      <subsection name="Threading issues">
+      <p>
+        The most concrete implementations of the <code>Configuration</code>
+        interface that are shipped with this library are not thread-safe.
+        They can be accessed concurrently in a read-only manner. However if one
+        thread modifies a configuration object, manual synchronization has to be
+        performed to ensure correctness of data. Notes about the thread
+        safety of conrete implementation classes can be found in the Javadocs
+        for these classes.
+      </p>
+      </subsection>
+    </section>
+
+  </body>
+</document>
diff --git a/src/site/xdoc/userguide/user_guide.xml b/src/site/xdoc/userguide/user_guide.xml
new file mode 100644
index 0000000..811efbe
--- /dev/null
+++ b/src/site/xdoc/userguide/user_guide.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- $Id: user_guide.xml 1156502 2011-08-11 06:23:25Z oheger $ -->
+
+<document>
+
+ <properties>
+  <title>Commons Configuration User's Guide</title>
+ </properties>
+
+<body>
+
+    <section name="About this document">
+		<p>
+ 	 		This document describes the features of the Commons Configuration
+            component starting with the very basics and up to the more advanced
+            topics. If you read it in a linear way, you should get a sound
+            understanding of the provided classes and the possibilities they
+            offer. But you can also skip sections and jump directly to the topics
+            you are most interested in.
+    	</p>
+    </section>
+
+	<section name="Table of contents">
+    <ul>
+      <li><a href="overview.html#Using_Configuration">Using Configuration</a></li>
+      <ul>
+        <li><a href="overview.html#Configuration_Sources">Configuration Sources</a></li>
+        <li><a href="overview.html#Mixing_Configuration_Sources">Mixing Configuration Sources</a></li>
+        <li><a href="overview.html#The_Configuration_interface">The Configuration interface</a></li>
+        <li><a href="overview.html#Threading_issues">Threading issues</a></li>
+      </ul>
+      <li><a href="howto_basicfeatures.html#Basic_features_and_AbstractConfiguration">Basic features and AbstractConfiguration</a></li>
+      <ul>
+        <li><a href="howto_basicfeatures.html#Handling_of_missing_properties">Handling of missing properties</a></li>
+        <li><a href="howto_basicfeatures.html#List_handling">List handling</a></li>
+        <li><a href="howto_basicfeatures.html#Variable_Interpolation">Variable Interpolation</a></li>
+        <li><a href="howto_basicfeatures.html#Customizing_interpolation">Customizing interpolation</a></li>
+      </ul>
+      <li><a href="howto_properties.html#Properties_files">Properties files</a></li>
+      <ul>
+        <li><a href="howto_properties.html#Using_PropertiesConfiguration">Using PropertiesConfiguration</a></li>
+        <li><a href="howto_properties.html#Includes">Includes</a></li>
+        <li><a href="howto_properties.html#Lists_and_arrays">Lists and arrays</a></li>
+        <li><a href="howto_properties.html#Saving">Saving</a></li>
+        <li><a href="howto_properties.html#Special_Characters_and_Escaping">Special Characters and Escaping</a></li>
+        <li><a href="howto_properties.html#Layout_Objects">Layout Objects</a></li>
+        <li><a href="howto_properties.html#Custom_properties_readers_and_writers">Custom properties readers and writers</a></li>
+      </ul>
+      <li><a href="howto_filebased.html#File-based_Configurations">File-based Configurations</a></li>
+      <ul>
+        <li><a href="howto_filebased.html#Specifying_the_file">Specifying the file</a></li>
+        <li><a href="howto_filebased.html#Loading">Loading</a></li>
+        <li><a href="howto_filebased.html#Saving">Saving</a></li>
+        <li><a href="howto_filebased.html#Automatic_Saving">Automatic Saving</a></li>
+        <li><a href="howto_filebased.html#Automatic_Reloading">Automatic Reloading</a></li>
+      </ul>
+      <li><a href="howto_xml.html#Hierarchical_properties">Hierarchical properties</a></li>
+      <ul>
+        <li><a href="howto_xml.html#Accessing_properties_in_hierarchical_configurations">Accessing properties in hierarchical configurations</a></li>
+        <li><a href="howto_xml.html#Complex_hierarchical_structures">Complex hierarchical structures</a></li>
+        <li><a href="howto_xml.html#Accessing_structured_properties">Accessing structured properties</a></li>
+        <li><a href="howto_xml.html#Adding_new_properties">Adding new properties</a></li>
+        <li><a href="howto_xml.html#Escaping_special_characters">Escaping dot characters in property names</a></li>
+        <li><a href="howto_xml.html#Expression_engines">Expression engines</a></li>
+        <ul>
+          <li><a href="howto_xml.html#The_default_expression_engine">The default expression engine</a></li>
+          <li><a href="howto_xml.html#The_XPATH_expression_engine">The XPATH expression engine</a></li>
+        </ul>
+        <li><a href="howto_xml.html#Validation_of_XML_configuration_files">Validation of XML configuration files</a></li>
+      </ul>
+      <li><a href="howto_compositeconfiguration.html#Composite_Configuration_Details">Composite Configuration Details</a></li>
+      <ul>
+        <li><a href="howto_compositeconfiguration.html#Setting_Up_Defaults">Setting Up Defaults</a></li>
+        <li><a href="howto_compositeconfiguration.html#Saving_Changes">Saving Changes</a></li>
+      </ul>
+      <li><a href="howto_combinedconfiguration.html#Combined_Configuration">Combined Configuration</a></li>
+      <ul>
+        <li><a href="howto_combinedconfiguration.html#How_it_works">How it works</a></li>
+        <li><a href="howto_combinedconfiguration.html#Node_combiners">Node combiners</a></li>
+        <li><a href="howto_combinedconfiguration.html#Constructing_a_CombinedConfiguration">Constructing a CombinedConfiguration</a></li>
+        <li><a href="howto_combinedconfiguration.html#Dealing_with_changes">Dealing with changes</a></li>
+      </ul>
+      <li><a href="howto_beans.html#Declaring_and_Creating_Beans">Declaring and Creating Beans</a></li>
+      <ul>
+        <li><a href="howto_beans.html#Basic_Concepts">Basic Concepts</a></li>
+        <li><a href="howto_beans.html#An_Example">An Example</a></li>
+        <li><a href="howto_beans.html#Extending_the_Basic_Mechanism">Extending the Basic Mechanism</a></li>
+      </ul>
+      <li><a href="howto_configurationbuilder.html#Using_DefaultConfigurationBuilder">Using DefaultConfigurationBuilder</a></li>
+      <ul>
+        <li><a href="howto_configurationbuilder.html#The_configuration_definition_file">The configuration definition file</a></li>
+        <li><a href="howto_configurationbuilder.html#Setting_up_a_DefaultConfigurationBuilder">Setting up a ConfigurationFactory</a></li>
+        <li><a href="howto_configurationbuilder.html#Overriding_properties">Overriding properties</a></li>
+        <li><a href="howto_configurationbuilder.html#Optional_configuration_sources">Optional configuration sources</a></li>
+        <li><a href="howto_configurationbuilder.html#Union_configuration">Union configuration</a></li>
+        <li><a href="howto_configurationbuilder.html#Configuration_definition_file_reference">Configuration definition file reference</a></li>
+        <li><a href="howto_configurationbuilder.html#An_example">An example</a></li>
+        <li><a href="howto_configurationbuilder.html#Extending_the_configuration_definition_file_format">Extending the configuration definition file format</a></li>
+      </ul>
+      <li><a href="howto_multitenant.html#Multi-tenant Configurations">Multi-tenant Configurations</a></li>
+      <ul>
+        <li><a href="howto_multitenant.html#MultiFileHierarchicalConfiguration">MultiFileHierarchicalConfiguration</a></li>
+        <li><a href="howto_multitenant.html#DynamicCombinedConfiguration">DynamicCombinedConfiguration</a></li>
+        <li><a href="howto_multitenant.html#Sample Configuration">Sample Configuration</a></li>
+        <li><a href="howto_multitenant.html#PatternSubtreeConfigurationWrapper">PatternSubtreeConfigurationWrapper</a></li>
+      </ul>
+      <li><a href="howto_events.html#Configuration_Events">Configuration Events</a></li>
+      <ul>
+        <li><a href="howto_events.html#Configuration_listeners">Configuration listeners</a></li>
+        <li><a href="howto_events.html#An_example">An example</a></li>
+        <li><a href="howto_events.html#Error_listeners">Error listeners</a></li>
+      </ul>
+      <li><a href="howto_utilities.html#Utility_classes_and_Tips_and_Tricks">Utility classes and Tips and Tricks</a></li>
+      <ul>
+        <li><a href="howto_utilities.html#Copy_a_configuration">Copy a configuration</a></li>
+        <li><a href="howto_utilities.html#Converting_a_flat_configuration_into_a_hierarchical_one">Converting a flat configuration into a hierarchical one</a></li>
+        <li><a href="howto_utilities.html#Converting_between_properties_and_configurations">Converting between properties and configurations</a></li>
+        <li><a href="howto_utilities.html#Interpolation_of_all_variables">Interpolation of all variables</a></li>
+        <li><a href="howto_utilities.html#Handling_of_runtime_exceptions">Handling of runtime exceptions</a></li>
+      </ul>
+      <li><a href="howto_filesystems.html#File_Systems">File Systems</a></li>
+      <ul>
+        <li><a href="howto_filesystems.html#File_Systems#Configuration">Configuration</a></li>
+        <li><a href="howto_filesystems.html#File_Systems#File_Options_Provider">File Options Provider</a></li>
+        <li><a href="howto_filesystems.html#File_Systems#File_Reloading_Strategy">File Reloading Strategy</a></li>
+      </ul>
+    </ul>
+    </section>
+
+</body>
+
+</document>
\ No newline at end of file
diff --git a/src/test/java/org/apache/commons/configuration/BaseNonStringProperties.java b/src/test/java/org/apache/commons/configuration/BaseNonStringProperties.java
new file mode 100644
index 0000000..522cee8
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/BaseNonStringProperties.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+/**
+ * Test if non-string properties are handled correctly.
+ *
+ * @version $Id: BaseNonStringProperties.java 1302002 2012-03-17 20:43:46Z sebb $
+ */
+public abstract class BaseNonStringProperties
+{
+
+    protected NonStringTestHolder nonStringTestHolder = new NonStringTestHolder();
+
+    protected Configuration conf;
+
+    @Test
+    public void testBoolean() throws Exception
+    {
+        nonStringTestHolder.testBoolean();
+    }
+
+    @Test
+    public void testBooleanDefaultValue() throws Exception
+    {
+        nonStringTestHolder.testBooleanDefaultValue();
+    }
+
+    @Test
+    public void testBooleanArrayValue() throws Exception
+    {
+        boolean booleanValue = conf.getBoolean("test.boolean");
+        assertTrue(booleanValue);
+        assertEquals(2, conf.getList("test.boolean.array").size());
+    }
+
+    @Test
+    public void testByte() throws Exception
+    {
+        nonStringTestHolder.testByte();
+    }
+
+    @Test
+    public void testByteArrayValue() throws Exception
+    {
+        byte testValue = 10;
+        byte byteValue = conf.getByte("test.byte");
+        assertEquals(testValue, byteValue);
+        assertEquals(2, conf.getList("test.byte.array").size());
+    }
+
+    @Test
+    public void testDouble() throws Exception
+    {
+        nonStringTestHolder.testDouble();
+    }
+
+    @Test
+    public void testDoubleDefaultValue() throws Exception
+    {
+        nonStringTestHolder.testDoubleDefaultValue();
+    }
+
+    @Test
+    public void testDoubleArrayValue() throws Exception
+    {
+        double testValue = 10.25;
+        double doubleValue = conf.getDouble("test.double");
+        assertEquals(testValue, doubleValue, 0.01);
+        assertEquals(2, conf.getList("test.double.array").size());
+    }
+
+    @Test
+    public void testFloat() throws Exception
+    {
+        nonStringTestHolder.testFloat();
+    }
+
+    @Test
+    public void testFloatDefaultValue() throws Exception
+    {
+        nonStringTestHolder.testFloatDefaultValue();
+
+    }
+
+    @Test
+    public void testFloatArrayValue() throws Exception
+    {
+        float testValue = (float) 20.25;
+        float floatValue = conf.getFloat("test.float");
+        assertEquals(testValue, floatValue, 0.01);
+        assertEquals(2, conf.getList("test.float.array").size());
+    }
+
+    @Test
+    public void testInteger() throws Exception
+    {
+        nonStringTestHolder.testInteger();
+    }
+
+    @Test
+    public void testIntegerDefaultValue() throws Exception
+    {
+        nonStringTestHolder.testIntegerDefaultValue();
+    }
+
+    @Test
+    public void testIntegerArrayValue() throws Exception
+    {
+        int intValue = conf.getInt("test.integer");
+        assertEquals(10, intValue);
+        assertEquals(2, conf.getList("test.integer.array").size());
+    }
+
+    @Test
+    public void testLong() throws Exception
+    {
+        nonStringTestHolder.testLong();
+    }
+
+    @Test
+    public void testLongDefaultValue() throws Exception
+    {
+        nonStringTestHolder.testLongDefaultValue();
+    }
+
+    @Test
+    public void testLongArrayValue() throws Exception
+    {
+        long longValue = conf.getLong("test.long");
+        assertEquals(1000000, longValue);
+        assertEquals(2, conf.getList("test.long.array").size());
+    }
+
+    @Test
+    public void testShort() throws Exception
+    {
+        nonStringTestHolder.testShort();
+    }
+
+    @Test
+    public void testShortDefaultValue() throws Exception
+    {
+        nonStringTestHolder.testShortDefaultValue();
+    }
+
+    @Test
+    public void testShortArrayValue() throws Exception
+    {
+        short shortValue = conf.getShort("test.short");
+        assertEquals(1, shortValue);
+        assertEquals(2, conf.getList("test.short.array").size());
+    }
+
+    @Test
+    public void testListMissing() throws Exception
+    {
+        nonStringTestHolder.testListMissing();
+    }
+
+    @Test
+    public void testSubset() throws Exception
+    {
+        nonStringTestHolder.testSubset();
+    }
+
+    @Test
+    public void testIsEmpty() throws Exception
+    {
+        nonStringTestHolder.testIsEmpty();
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/ConfigurationAssert.java b/src/test/java/org/apache/commons/configuration/ConfigurationAssert.java
new file mode 100644
index 0000000..d15a6b5
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/ConfigurationAssert.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Iterator;
+
+import junit.framework.Assert;
+
+/**
+ * Assertions on configurations for the unit tests. This class also provides
+ * access to test files.
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: ConfigurationAssert.java 1221893 2011-12-21 21:34:38Z oheger $
+ */
+public class ConfigurationAssert
+{
+    /** Constant for the name of the directory with the test files. */
+    public static final String TEST_DIR_NAME = "target/test-classes";
+
+    /** Constant for the name of the directory with the output files. */
+    public static final String OUT_DIR_NAME = "target";
+
+    /** The directory with the test files. */
+    public static final File TEST_DIR = new File(TEST_DIR_NAME);
+
+    /** The directory with the output files. */
+    public static final File OUT_DIR = new File(OUT_DIR_NAME);
+
+    /**
+     * Checks the content of a configuration.
+     *
+     * @param expected the expected properties
+     * @param actual the configuration to check
+     */
+    public static void assertEquals(Configuration expected, Configuration actual)
+    {
+        // check that the actual configuration contains all the properties of the expected configuration
+        for (Iterator<String> it = expected.getKeys(); it.hasNext();)
+        {
+            String key = it.next();
+            Assert.assertTrue("The actual configuration doesn't contain the expected key '" + key + "'", actual.containsKey(key));
+            Assert.assertEquals("Value of the '" + key + "' property", expected.getProperty(key), actual.getProperty(key));
+        }
+
+        // check that the actual configuration has no extra properties
+        for (Iterator<String> it = actual.getKeys(); it.hasNext();)
+        {
+            String key = it.next();
+            Assert.assertTrue("The actual configuration contains an extra key '" + key + "'", expected.containsKey(key));
+        }
+    }
+
+    /**
+     * Returns a {@code File} object for the specified test file.
+     *
+     * @param name the name of the test file
+     * @return a {@code File} object pointing to that test file
+     */
+    public static File getTestFile(String name)
+    {
+        return new File(TEST_DIR, name);
+    }
+
+    /**
+     * Returns a {@code File} object for the specified out file.
+     *
+     * @param name the name of the out file
+     * @return a {@code File} object pointing to that out file
+     */
+    public static File getOutFile(String name)
+    {
+        return new File(OUT_DIR, name);
+    }
+
+    /**
+     * Returns a URL pointing to the specified test file. If the URL cannot be
+     * constructed, a runtime exception is thrown.
+     *
+     * @param name the name of the test file
+     * @return the corresponding URL
+     */
+    public static URL getTestURL(String name)
+    {
+        return urlFromFile(getTestFile(name));
+    }
+
+    /**
+     * Returns a URL pointing to the specified output file. If the URL cannot be
+     * constructed, a runtime exception is thrown.
+     *
+     * @param name the name of the output file
+     * @return the corresponding URL
+     */
+    public static URL getOutURL(String name)
+    {
+        return urlFromFile(getOutFile(name));
+    }
+
+    /**
+     * Helper method for converting a file to a URL.
+     *
+     * @param file the file
+     * @return the corresponding URL
+     * @throws ConfigurationRuntimeException if the URL cannot be constructed
+     */
+    private static URL urlFromFile(File file)
+    {
+        try
+        {
+            return file.toURI().toURL();
+        }
+        catch (MalformedURLException mex)
+        {
+            throw new ConfigurationRuntimeException(mex);
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/ConfigurationErrorListenerImpl.java b/src/test/java/org/apache/commons/configuration/ConfigurationErrorListenerImpl.java
new file mode 100644
index 0000000..588779e
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/ConfigurationErrorListenerImpl.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.commons.configuration.event.ConfigurationErrorEvent;
+import org.apache.commons.configuration.event.ConfigurationErrorListener;
+
+/**
+ * An implementation of the {@code ConfigurationErrorListener} interface
+ * that can be used in unit tests. This implementation just records received
+ * events and allows to test whether expected errors occurred.
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: ConfigurationErrorListenerImpl.java 1222446 2011-12-22 20:57:32Z oheger $
+ */
+public class ConfigurationErrorListenerImpl implements
+        ConfigurationErrorListener
+{
+    /** Stores the last received error event. */
+    private ConfigurationErrorEvent event;
+
+    /** Stores the number of calls to configurationError(). */
+    private int errorCount;
+
+    /**
+     * An error event is received. Updates the internal counter and stores the
+     * event.
+     *
+     * @param event the error event
+     */
+    public void configurationError(ConfigurationErrorEvent event)
+    {
+        this.event = event;
+        errorCount++;
+    }
+
+    /**
+     * Returns the last received error event.
+     *
+     * @return the last error event (may be <b>null</b>)
+     */
+    public ConfigurationErrorEvent getLastEvent()
+    {
+        return event;
+    }
+
+    /**
+     * Returns the number of received error events.
+     *
+     * @return the number of error events
+     */
+    public int getErrorCount()
+    {
+        return errorCount;
+    }
+
+    /**
+     * Checks whether no error event was received.
+     */
+    public void verify()
+    {
+        assertEquals("Error events received", 0, errorCount);
+    }
+
+    /**
+     * Checks whether an expected error event was received. This is a
+     * convenience method for checking whether exactly one event of a certain
+     * type was received.
+     *
+     * @param type the type of the event
+     * @param propName the name of the property
+     * @param propValue the value of the property
+     */
+    public void verify(int type, String propName, Object propValue)
+    {
+        assertEquals("Wrong number of error events", 1, errorCount);
+        assertEquals("Wrong event type", type, event.getType());
+        assertTrue("Wrong property name", (propName == null) ? event
+                .getPropertyName() == null : propName.equals(event
+                .getPropertyName()));
+        assertTrue("Wrong property value", (propValue == null) ? event
+                .getPropertyValue() == null : propValue.equals(event
+                .getPropertyValue()));
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/DatabaseConfigurationTestHelper.java b/src/test/java/org/apache/commons/configuration/DatabaseConfigurationTestHelper.java
new file mode 100644
index 0000000..dc8c820
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/DatabaseConfigurationTestHelper.java
@@ -0,0 +1,230 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.sql.Connection;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.configuration.test.HsqlDB;
+import org.apache.commons.dbcp.BasicDataSource;
+import org.dbunit.database.DatabaseConnection;
+import org.dbunit.database.IDatabaseConnection;
+import org.dbunit.dataset.IDataSet;
+import org.dbunit.dataset.xml.XmlDataSet;
+import org.dbunit.operation.DatabaseOperation;
+
+/**
+ * A helper class for performing tests for {@link DatabaseConfiguration}. This
+ * class maintains an in-process database that stores configuration data and can
+ * be accessed from a {@link DatabaseConfiguration} instance. Constants for
+ * table and column names and database connection settings are provided, too.
+ *
+ * @version $Id: DatabaseConfigurationTestHelper.java 1222447 2011-12-22 20:59:44Z oheger $
+ */
+public class DatabaseConfigurationTestHelper
+{
+    /** Constant for the JDBC driver class. */
+    public final String DATABASE_DRIVER = "org.hsqldb.jdbcDriver";
+
+    /** Constant for the connection URL. */
+    public final String DATABASE_URL = "jdbc:hsqldb:mem:testdb";
+
+    /** Constant for the DB user name. */
+    public final String DATABASE_USERNAME = "sa";
+
+    /** Constant for the DB password. */
+    public final String DATABASE_PASSWORD = "";
+
+    /** Constant for the configuration table. */
+    public static final String TABLE = "configuration";
+
+    /** Constant for the multi configuration table. */
+    public static final String TABLE_MULTI = "configurations";
+
+    /** Constant for the column with the keys. */
+    public static final String COL_KEY = "key";
+
+    /** Constant for the column with the values. */
+    public static final String COL_VALUE = "value";
+
+    /** Constant for the column with the configuration name. */
+    public static final String COL_NAME = "name";
+
+    /** Constant for the name of the test configuration. */
+    public static final String CONFIG_NAME = "test";
+
+    /** Stores the in-process database. */
+    private HsqlDB hsqlDB;
+
+    /** The data source. */
+    private DataSource datasource;
+
+    /**
+     * The auto-commit mode for the connections created by the managed data
+     * source.
+     */
+    private boolean autoCommit = true;
+
+    /**
+     * Returns the auto-commit mode of the connections created by the managed
+     * data source.
+     *
+     * @return the auto-commit mode
+     */
+    public boolean isAutoCommit()
+    {
+        return autoCommit;
+    }
+
+    /**
+     * Sets the auto-commit mode of the connections created by the managed data
+     * source.
+     *
+     * @param autoCommit the auto-commit mode
+     */
+    public void setAutoCommit(boolean autoCommit)
+    {
+        this.autoCommit = autoCommit;
+    }
+
+    /**
+     * Initializes this helper object. This method can be called from a
+     * {@code setUp()} method of a unit test class. It creates the database
+     * instance if necessary.
+     *
+     * @throws Exception if an error occurs
+     */
+    public void setUp() throws Exception
+    {
+        File script = ConfigurationAssert.getTestFile("testdb.script");
+        hsqlDB = new HsqlDB(DATABASE_URL, DATABASE_DRIVER, script.getAbsolutePath());
+    }
+
+    /**
+     * Frees the resources used by this helper class. This method can be called
+     * by a {@code tearDown()} method of a unit test class.
+     *
+     * @throws Exception if an error occurs
+     */
+    public void tearDown() throws Exception
+    {
+        if (datasource != null)
+        {
+            datasource.getConnection().close();
+        }
+        hsqlDB.close();
+    }
+
+    /**
+     * Creates a database configuration with default values.
+     *
+     * @return the configuration
+     */
+    public DatabaseConfiguration setUpConfig()
+    {
+        return new DatabaseConfiguration(getDatasource(), TABLE, COL_KEY,
+                COL_VALUE, !isAutoCommit());
+    }
+
+    /**
+     * Creates a database configuration that supports multiple configurations in
+     * a table with default values.
+     *
+     * @return the configuration
+     */
+    public DatabaseConfiguration setUpMultiConfig()
+    {
+        return setUpMultiConfig(CONFIG_NAME);
+    }
+
+    /**
+     * Creates a database configuration that supports multiple configurations in
+     * a table and sets the specified configuration name.
+     *
+     * @param configName the name of the configuration
+     * @return the configuration
+     */
+    public DatabaseConfiguration setUpMultiConfig(String configName)
+    {
+        return new DatabaseConfiguration(getDatasource(), TABLE_MULTI,
+                COL_NAME, COL_KEY, COL_VALUE, configName, !isAutoCommit());
+    }
+
+    /**
+     * Returns the {@code DataSource} managed by this class. The data
+     * source is created on first access.
+     *
+     * @return the {@code DataSource}
+     */
+    public DataSource getDatasource()
+    {
+        if (datasource == null)
+        {
+            try
+            {
+                datasource = setUpDataSource();
+            }
+            catch (Exception ex)
+            {
+                throw new ConfigurationRuntimeException(
+                        "Could not create data source", ex);
+            }
+        }
+        return datasource;
+    }
+
+    /**
+     * Creates the internal data source. This method also initializes the
+     * database.
+     *
+     * @return the data source
+     * @throws Exception if an error occurs
+     */
+    private DataSource setUpDataSource() throws Exception
+    {
+        BasicDataSource ds = new BasicDataSource();
+        ds.setDriverClassName(DATABASE_DRIVER);
+        ds.setUrl(DATABASE_URL);
+        ds.setUsername(DATABASE_USERNAME);
+        ds.setPassword(DATABASE_PASSWORD);
+        ds.setDefaultAutoCommit(isAutoCommit());
+
+        // prepare the database
+        Connection conn = ds.getConnection();
+        IDatabaseConnection connection = new DatabaseConnection(conn);
+        IDataSet dataSet = new XmlDataSet(new FileInputStream(
+                ConfigurationAssert.getTestFile("dataset.xml")));
+
+        try
+        {
+            DatabaseOperation.CLEAN_INSERT.execute(connection, dataSet);
+        }
+        finally
+        {
+            if (!isAutoCommit())
+            {
+                conn.commit();
+            }
+            connection.close();
+        }
+
+        return ds;
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/FileURLStreamHandler.java b/src/test/java/org/apache/commons/configuration/FileURLStreamHandler.java
new file mode 100644
index 0000000..cbb36e5
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/FileURLStreamHandler.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+/**
+ * A custom URLStreamHandler to test loading and saving configurations to non
+ * standard URLs. This handler acts like a file handler with write support.
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: FileURLStreamHandler.java 1222450 2011-12-22 21:01:33Z oheger $
+ */
+public class FileURLStreamHandler extends URLStreamHandler
+{
+    @Override
+    protected URLConnection openConnection(URL u) throws IOException
+    {
+        final File file = new File(u.getFile());
+
+        return new URLConnection(u) {
+
+            @Override
+            public void connect() throws IOException
+            {
+            }
+
+            @Override
+            public InputStream getInputStream() throws IOException
+            {
+                return new FileInputStream(file);
+            }
+
+            @Override
+            public OutputStream getOutputStream() throws IOException
+            {
+                return new FileOutputStream(file);
+            }
+        };
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/InterpolationTestHelper.java b/src/test/java/org/apache/commons/configuration/InterpolationTestHelper.java
new file mode 100644
index 0000000..1b87ef9
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/InterpolationTestHelper.java
@@ -0,0 +1,257 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.awt.event.KeyEvent;
+import java.util.List;
+
+import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
+import org.apache.commons.lang.text.StrLookup;
+
+/**
+ * A helper class that defines a bunch of tests related to variable
+ * interpolation. It can be used for running these tests on different
+ * configuration implementations.
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: InterpolationTestHelper.java 1222452 2011-12-22 21:06:17Z oheger $
+ */
+public class InterpolationTestHelper
+{
+    /**
+     * Tests basic interpolation facilities of the specified configuration.
+     *
+     * @param config the configuration to test
+     */
+    public static void testInterpolation(Configuration config)
+    {
+        config.setProperty("applicationRoot", "/home/applicationRoot");
+        config.setProperty("db", "${applicationRoot}/db/hypersonic");
+        String unInterpolatedValue = "${applicationRoot2}/db/hypersonic";
+        config.setProperty("dbFailedInterpolate", unInterpolatedValue);
+        String dbProp = "/home/applicationRoot/db/hypersonic";
+
+        assertEquals("Checking interpolated variable", dbProp, config
+                .getString("db"));
+        assertEquals("lookup fails, leave variable as is", config
+                .getString("dbFailedInterpolate"), unInterpolatedValue);
+
+        config.setProperty("arrayInt", "${applicationRoot}/1");
+        String[] arrayInt = config.getStringArray("arrayInt");
+        assertEquals("check first entry was interpolated",
+                "/home/applicationRoot/1", arrayInt[0]);
+
+        config.addProperty("path", "/temp,C:\\Temp,/usr/local/tmp");
+        config.setProperty("path.current", "${path}");
+        assertEquals("Interpolation with multi-valued property",
+                "/temp", config.getString("path.current"));
+    }
+
+    /**
+     * Tests an interpolation over multiple levels (i.e. the replacement of a
+     * variable is another variable and so on).
+     *
+     * @param config the configuration to test
+     */
+    public static void testMultipleInterpolation(Configuration config)
+    {
+        config.setProperty("test.base-level", "/base-level");
+        config
+                .setProperty("test.first-level",
+                        "${test.base-level}/first-level");
+        config.setProperty("test.second-level",
+                "${test.first-level}/second-level");
+        config.setProperty("test.third-level",
+                "${test.second-level}/third-level");
+
+        String expectedValue = "/base-level/first-level/second-level/third-level";
+
+        assertEquals(config.getString("test.third-level"),
+                        expectedValue);
+    }
+
+    /**
+     * Tests an invalid interpolation that results in an infinite loop. This
+     * loop should be detected and an exception should be thrown.
+     *
+     * @param config the configuration to test
+     */
+    public static void testInterpolationLoop(Configuration config)
+    {
+        config.setProperty("test.a", "${test.b}");
+        config.setProperty("test.b", "${test.a}");
+
+        try
+        {
+            config.getString("test.a");
+            fail("IllegalStateException should have been thrown for looped property references");
+        }
+        catch (IllegalStateException e)
+        {
+            // ok
+        }
+
+    }
+
+    /**
+     * Tests interpolation when a subset configuration is involved.
+     *
+     * @param config the configuration to test
+     */
+    public static void testInterpolationSubset(Configuration config)
+    {
+        config.addProperty("test.a", new Integer(42));
+        config.addProperty("test.b", "${test.a}");
+        assertEquals("Wrong interpolated value", 42, config
+                .getInt("test.b"));
+        Configuration subset = config.subset("test");
+        assertEquals("Wrong string property", "42", subset
+                .getString("b"));
+        assertEquals("Wrong int property", 42, subset.getInt("b"));
+    }
+
+    /**
+     * Tests interpolation when the referred property is not found.
+     *
+     * @param config the configuration to test
+     */
+    public static void testInterpolationUnknownProperty(Configuration config)
+    {
+        config.addProperty("test.interpol", "${unknown.property}");
+        assertEquals("Wrong interpolated unknown property",
+                "${unknown.property}", config.getString("test.interpol"));
+    }
+
+    /**
+     * Tests interpolation of system properties.
+     *
+     * @param config the configuration to test
+     */
+    public static void testInterpolationSystemProperties(Configuration config)
+    {
+        String[] sysProperties =
+        { "java.version", "java.vendor", "os.name", "java.class.path" };
+        for (int i = 0; i < sysProperties.length; i++)
+        {
+            config.addProperty("prop" + i, "${sys:" + sysProperties[i] + "}");
+        }
+
+        for (int i = 0; i < sysProperties.length; i++)
+        {
+            assertEquals("Wrong value for system property "
+                    + sysProperties[i], System.getProperty(sysProperties[i]),
+                    config.getString("prop" + i));
+        }
+    }
+
+    /**
+     * Tests interpolation of constant values.
+     *
+     * @param config the configuration to test
+     */
+    public static void testInterpolationConstants(Configuration config)
+    {
+        config.addProperty("key.code",
+                "${const:java.awt.event.KeyEvent.VK_CANCEL}");
+        assertEquals("Wrong value of constant variable",
+                KeyEvent.VK_CANCEL, config.getInt("key.code"));
+        assertEquals("Wrong value when fetching constant from cache",
+                KeyEvent.VK_CANCEL, config.getInt("key.code"));
+    }
+
+    /**
+     * Tests whether a variable can be escaped, so that it won't be
+     * interpolated.
+     *
+     * @param config the configuration to test
+     */
+    public static void testInterpolationEscaped(Configuration config)
+    {
+        config.addProperty("var", "x");
+        config.addProperty("escVar", "Use the variable $${${var}}.");
+        assertEquals("Wrong escaped variable", "Use the variable ${x}.",
+                config.getString("escVar"));
+    }
+
+    /**
+     * Tests accessing and manipulating the interpolator object.
+     *
+     * @param config the configuration to test
+     */
+    public static void testGetInterpolator(AbstractConfiguration config)
+    {
+        config.addProperty("var", "${echo:testVar}");
+        ConfigurationInterpolator interpol = config.getInterpolator();
+        interpol.registerLookup("echo", new StrLookup()
+        {
+            @Override
+            public String lookup(String varName)
+            {
+                return "Value of variable " + varName;
+            }
+        });
+        assertEquals("Wrong value of echo variable",
+                "Value of variable testVar", config.getString("var"));
+    }
+
+    /**
+     * Tests obtaining a configuration with all variables replaced by their
+     * actual values.
+     *
+     * @param config the configuration to test
+     * @return the interpolated configuration
+     */
+    public static Configuration testInterpolatedConfiguration(
+            AbstractConfiguration config)
+    {
+        config.setProperty("applicationRoot", "/home/applicationRoot");
+        config.setProperty("db", "${applicationRoot}/db/hypersonic");
+        config.setProperty("inttest.interpol", "${unknown.property}");
+        config.setProperty("intkey.code",
+                "${const:java.awt.event.KeyEvent.VK_CANCEL}");
+        config.setProperty("inttest.sysprop", "${sys:java.version}");
+        config.setProperty("inttest.numvalue", "3\\,1415");
+        config.setProperty("inttest.value", "${inttest.numvalue}");
+        config.setProperty("inttest.list", "${db}");
+        config.addProperty("inttest.list", "${inttest.value}");
+
+        Configuration c = config.interpolatedConfiguration();
+        assertEquals("Property not replaced",
+                "/home/applicationRoot/db/hypersonic", c.getProperty("db"));
+        assertEquals("Const variable not replaced", KeyEvent.VK_CANCEL,
+                c.getInt("intkey.code"));
+        assertEquals("Sys property not replaced", System
+                .getProperty("java.version"), c.getProperty("inttest.sysprop"));
+        assertEquals("Delimiter value not replaced", "3,1415", c
+                .getProperty("inttest.value"));
+        List<?> lst = (List<?>) c.getProperty("inttest.list");
+        assertEquals("Wrong number of list elements", 2, lst.size());
+        assertEquals("List element 0 not replaced",
+                "/home/applicationRoot/db/hypersonic", lst.get(0));
+        assertEquals("List element 1 not replaced", "3,1415", lst
+                        .get(1));
+        assertEquals("Unresolvable variable not found",
+                "${unknown.property}", c.getProperty("inttest.interpol"));
+
+        return c;
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/Logging.java b/src/test/java/org/apache/commons/configuration/Logging.java
new file mode 100644
index 0000000..22f853d
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/Logging.java
@@ -0,0 +1,271 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import org.apache.commons.logging.impl.Log4JLogger;
+import org.apache.log4j.Priority;
+import org.apache.log4j.Level;
+import org.apache.log4j.Appender;
+import org.apache.log4j.PatternLayout;
+import org.apache.log4j.ConsoleAppender;
+
+/**
+ * Configures logging for tests.
+ *
+ * When running with Maven do -Dmaven.surefire.debug="LogLevel=level" to set the
+ * Log Level to the desired value.
+ */
+public class Logging extends Log4JLogger
+{
+    /**
+     * The fully qualified name of the Log4JLogger class.
+     */
+    private static final String FQCN = Logging.class.getName();
+
+    private static Priority traceLevel;
+
+    static
+    {
+        // Releases of log4j1.2 >= 1.2.12 have Priority.TRACE available, earlier
+        // versions do not. If TRACE is not available, then we have to map
+        // calls to Log.trace(...) onto the DEBUG level.
+
+        try
+        {
+            traceLevel = (Priority) Level.class.getDeclaredField("TRACE").get(null);
+        }
+        catch (Exception ex)
+        {
+            // ok, trace not available
+            traceLevel = Priority.DEBUG;
+        }
+
+        String level = System.getProperty("LogLevel");
+        if (level != null)
+        {
+            org.apache.log4j.Logger log = org.apache.log4j.Logger.getRootLogger();
+            log.setLevel(Level.toLevel(level));
+            Appender appender = new ConsoleAppender(new PatternLayout("%p %l - %m%n"), ConsoleAppender.SYSTEM_OUT);
+            log.addAppender(appender);
+        }
+    }
+
+    public Logging()
+    {
+        super();
+    }
+
+
+    /**
+     * Base constructor.
+     */
+    public Logging(String name)
+    {
+        super(name);
+    }
+
+    /**
+     * For use with a log4j factory.
+     */
+    public Logging(org.apache.log4j.Logger logger)
+    {
+        super(logger);
+    }
+
+    // ---------------------------------------------------------
+    // Implementation
+    //
+    // Note that in the methods below the Priority class is used to define
+    // levels even though the Level class is supported in 1.2. This is done
+    // so that at compile time the call definitely resolves to a call to
+    // a method that takes a Priority rather than one that takes a Level.
+    //
+    // The Category class (and hence its subclass Logging) in version 1.2 only
+    // has methods that take Priority objects. The Category class (and hence
+    // Logging class) in version 1.3 has methods that take both Priority and
+    // Level objects. This means that if we use Level here, and compile
+    // against log4j 1.3 then calls would be bound to the versions of
+    // methods taking Level objects and then would fail to run against
+    // version 1.2 of log4j.
+    // ---------------------------------------------------------
+
+
+    /**
+     * Logs a message with <code>org.apache.log4j.Priority.TRACE</code>.
+     * When using a log4j version that does not support the <code>TRACE</code>
+     * level, the message will be logged at the <code>DEBUG</code> level.
+     *
+     * @param message to log
+     * @see org.apache.commons.logging.Log#trace(Object)
+     */
+    @Override
+    public void trace(Object message)
+    {
+        getLogger().log(FQCN, traceLevel, message, null);
+    }
+
+
+    /**
+     * Logs a message with <code>org.apache.log4j.Priority.TRACE</code>.
+     * When using a log4j version that does not support the <code>TRACE</code>
+     * level, the message will be logged at the <code>DEBUG</code> level.
+     *
+     * @param message to log
+     * @param t       log this cause
+     * @see org.apache.commons.logging.Log#trace(Object, Throwable)
+     */
+    @Override
+    public void trace(Object message, Throwable t)
+    {
+        getLogger().log(FQCN, traceLevel, message, t);
+    }
+
+
+    /**
+     * Logs a message with <code>org.apache.log4j.Priority.DEBUG</code>.
+     *
+     * @param message to log
+     * @see org.apache.commons.logging.Log#debug(Object)
+     */
+    @Override
+    public void debug(Object message)
+    {
+        getLogger().log(FQCN, Priority.DEBUG, message, null);
+    }
+
+    /**
+     * Logs a message with <code>org.apache.log4j.Priority.DEBUG</code>.
+     *
+     * @param message to log
+     * @param t       log this cause
+     * @see org.apache.commons.logging.Log#debug(Object, Throwable)
+     */
+    @Override
+    public void debug(Object message, Throwable t)
+    {
+        getLogger().log(FQCN, Priority.DEBUG, message, t);
+    }
+
+
+    /**
+     * Logs a message with <code>org.apache.log4j.Priority.INFO</code>.
+     *
+     * @param message to log
+     * @see org.apache.commons.logging.Log#info(Object)
+     */
+    @Override
+    public void info(Object message)
+    {
+        getLogger().log(FQCN, Priority.INFO, message, null);
+    }
+
+
+    /**
+     * Logs a message with <code>org.apache.log4j.Priority.INFO</code>.
+     *
+     * @param message to log
+     * @param t       log this cause
+     * @see org.apache.commons.logging.Log#info(Object, Throwable)
+     */
+    @Override
+    public void info(Object message, Throwable t)
+    {
+        getLogger().log(FQCN, Priority.INFO, message, t);
+    }
+
+
+    /**
+     * Logs a message with <code>org.apache.log4j.Priority.WARN</code>.
+     *
+     * @param message to log
+     * @see org.apache.commons.logging.Log#warn(Object)
+     */
+    @Override
+    public void warn(Object message)
+    {
+        getLogger().log(FQCN, Priority.WARN, message, null);
+    }
+
+
+    /**
+     * Logs a message with <code>org.apache.log4j.Priority.WARN</code>.
+     *
+     * @param message to log
+     * @param t       log this cause
+     * @see org.apache.commons.logging.Log#warn(Object, Throwable)
+     */
+    @Override
+    public void warn(Object message, Throwable t)
+    {
+        getLogger().log(FQCN, Priority.WARN, message, t);
+    }
+
+
+    /**
+     * Logs a message with <code>org.apache.log4j.Priority.ERROR</code>.
+     *
+     * @param message to log
+     * @see org.apache.commons.logging.Log#error(Object)
+     */
+    @Override
+    public void error(Object message)
+    {
+        getLogger().log(FQCN, Priority.ERROR, message, null);
+    }
+
+
+    /**
+     * Logs a message with <code>org.apache.log4j.Priority.ERROR</code>.
+     *
+     * @param message to log
+     * @param t       log this cause
+     * @see org.apache.commons.logging.Log#error(Object, Throwable)
+     */
+    @Override
+    public void error(Object message, Throwable t)
+    {
+        getLogger().log(FQCN, Priority.ERROR, message, t);
+    }
+
+
+    /**
+     * Logs a message with <code>org.apache.log4j.Priority.FATAL</code>.
+     *
+     * @param message to log
+     * @see org.apache.commons.logging.Log#fatal(Object)
+     */
+    @Override
+    public void fatal(Object message)
+    {
+        getLogger().log(FQCN, Priority.FATAL, message, null);
+    }
+
+
+    /**
+     * Logs a message with <code>org.apache.log4j.Priority.FATAL</code>.
+     *
+     * @param message to log
+     * @param t       log this cause
+     * @see org.apache.commons.logging.Log#fatal(Object, Throwable)
+     */
+    @Override
+    public void fatal(Object message, Throwable t)
+    {
+        getLogger().log(FQCN, Priority.FATAL, message, t);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/configuration/MockInitialContextFactory.java b/src/test/java/org/apache/commons/configuration/MockInitialContextFactory.java
new file mode 100644
index 0000000..6553db0
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/MockInitialContextFactory.java
@@ -0,0 +1,241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import java.util.Hashtable;
+
+import javax.naming.Context;
+import javax.naming.NameClassPair;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.spi.InitialContextFactory;
+
+import com.mockobjects.dynamic.C;
+import com.mockobjects.dynamic.Mock;
+
+/**
+ * A mock implementation of the {@code InitialContextFactory} interface.
+ * This implementation will return a mock context that contains some test data.
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: MockInitialContextFactory.java 1222455 2011-12-22 21:10:10Z oheger $
+ */
+public class MockInitialContextFactory implements InitialContextFactory
+{
+    /**
+     * Constant for the use cycles environment property. If this property is
+     * present in the environment, a cyclic context will be created.
+     */
+    public static final String PROP_CYCLES = "useCycles";
+
+    /** Constant for the lookup method. */
+    private static final String METHOD_LOOKUP = "lookup";
+
+    /** Constant for the list method. */
+    private static final String METHOD_LIST = "list";
+
+    /** Constant for the close method.*/
+    private static final String METHOD_CLOSE = "close";
+
+    /** Constant for the name of the missing property. */
+    private static final String MISSING_PROP = "/missing";
+
+    /** Constant for the name of the prefix. */
+    private static final String PREFIX = "test/";
+
+    /** An array with the names of the supported properties. */
+    private static final String[] PROP_NAMES =
+    { "key", "key2", "short", "boolean", "byte", "double", "float", "integer",
+            "long", "onlyinjndi" };
+
+    /** An array with the values of the supported properties. */
+    private static final String[] PROP_VALUES =
+    { "jndivalue", "jndivalue2", "1", "true", "10", "10.25", "20.25", "10",
+            "1000000", "true" };
+
+    /** An array with properties that are requested, but are not in the context. */
+    private static final String[] MISSING_NAMES =
+    { "missing/list", "test/imaginarykey", "foo/bar" };
+
+    /**
+     * Creates a {@code Context} object that is backed by a mock object.
+     * The mock context can be queried for the values of certain test
+     * properties. It also supports listing the contained (sub) properties.
+     *
+     * @param env the environment
+     * @return the context mock
+     */
+    public Context getInitialContext(@SuppressWarnings("rawtypes") Hashtable env) throws NamingException
+    {
+        boolean useCycles = env.containsKey(PROP_CYCLES);
+
+        Mock mockTopCtx = createCtxMock(PREFIX);
+        Mock mockCycleCtx = createCtxMock("");
+        Mock mockPrfxCtx = createCtxMock("");
+        Mock mockBaseCtx = new Mock(Context.class);
+        mockBaseCtx.matchAndReturn(METHOD_LOOKUP, C.eq(""), mockTopCtx.proxy());
+        mockBaseCtx.matchAndReturn(METHOD_LOOKUP, C.eq("test"), mockPrfxCtx
+                .proxy());
+        mockTopCtx.matchAndReturn(METHOD_LOOKUP, C.eq("test"), mockPrfxCtx
+                .proxy());
+        mockPrfxCtx.matchAndReturn(METHOD_LIST, C.eq(""), createEnumMock(
+                mockPrfxCtx, PROP_NAMES, PROP_VALUES).proxy());
+
+        if (useCycles)
+        {
+            mockTopCtx.matchAndReturn(METHOD_LOOKUP, C.eq("cycle"),
+                    mockCycleCtx.proxy());
+            mockTopCtx.matchAndReturn(METHOD_LIST, C.eq(""), createEnumMock(
+                    mockTopCtx, new String[]
+                    { "test", "cycle" }, new Object[]
+                    { mockPrfxCtx.proxy(), mockCycleCtx.proxy() }).proxy());
+            Mock mockEnum = createEnumMock(mockCycleCtx, PROP_NAMES,
+                    PROP_VALUES, false);
+            addEnumPair(mockEnum, "cycleCtx", mockCycleCtx.proxy());
+            closeEnum(mockEnum);
+            mockCycleCtx
+                    .matchAndReturn(METHOD_LIST, C.eq(""), mockEnum.proxy());
+            mockCycleCtx.matchAndReturn(METHOD_LOOKUP, C.eq("cycleCtx"),
+                    mockCycleCtx.proxy());
+        }
+        else
+        {
+            mockTopCtx.matchAndReturn(METHOD_LIST, C.eq(""), createEnumMock(
+                    mockTopCtx, new String[]
+                    { "test" }, new Object[]
+                    { mockPrfxCtx.proxy() }).proxy());
+        }
+        return (Context) mockBaseCtx.proxy();
+    }
+
+    /**
+     * Creates a mock for a Context with the specified prefix.
+     *
+     * @param prefix the prefix
+     * @return the mock for the context
+     */
+    private Mock createCtxMock(String prefix)
+    {
+        Mock mockCtx = new Mock(Context.class);
+        for (int i = 0; i < PROP_NAMES.length; i++)
+        {
+            bind(mockCtx, prefix + PROP_NAMES[i], PROP_VALUES[i]);
+            String errProp = (prefix.length() > 0) ? PROP_NAMES[i] : PREFIX
+                    + PROP_NAMES[i];
+            bindError(mockCtx, errProp);
+        }
+        for (int i = 0; i < MISSING_NAMES.length; i++)
+        {
+            bindError(mockCtx, MISSING_NAMES[i]);
+        }
+        mockCtx.matchAndReturn("hashCode", System.identityHashCode(mockCtx.proxy()));
+
+        return mockCtx;
+    }
+
+    /**
+     * Binds a property value to the mock context.
+     *
+     * @param mockCtx the context
+     * @param name the name of the property
+     * @param value the value of the property
+     */
+    private void bind(Mock mockCtx, String name, String value)
+    {
+        mockCtx.matchAndReturn(METHOD_LOOKUP, C.eq(name), value);
+        bindError(mockCtx, name + MISSING_PROP);
+    }
+
+    /**
+     * Configures the mock to expect a call for a non existing property.
+     *
+     * @param mockCtx the mock
+     * @param name the name of the property
+     */
+    private void bindError(Mock mockCtx, String name)
+    {
+        mockCtx.matchAndThrow(METHOD_LOOKUP, C.eq(name),
+                new NameNotFoundException("unknown property"));
+    }
+
+    /**
+     * Creates and initializes a mock for a naming enumeration.
+     *
+     * @param mockCtx the mock representing the context
+     * @param names the names contained in the iteration
+     * @param values the corresponding values
+     * @param close a flag whether the enumeration should expect to be closed
+     * @return the mock for the enumeration
+     */
+    private Mock createEnumMock(Mock mockCtx, String[] names, Object[] values,
+            boolean close)
+    {
+        Mock mockEnum = new Mock(NamingEnumeration.class);
+        for (int i = 0; i < names.length; i++)
+        {
+            addEnumPair(mockEnum, names[i], values[i]);
+        }
+        if (close)
+        {
+            closeEnum(mockEnum);
+        }
+        return mockEnum;
+    }
+
+    /**
+     * Creates and initializes a mock for a naming enumeration that expects to
+     * be closed. This is a shortcut of createEnumMock(mockCtx, names, values,
+     * true);
+     *
+     * @param mockCtx the mock representing the context
+     * @param names the names contained in the iteration
+     * @param values the corresponding values
+     * @return the mock for the enumeration
+     */
+    private Mock createEnumMock(Mock mockCtx, String[] names, Object[] values)
+    {
+        return createEnumMock(mockCtx, names, values, true);
+    }
+
+    /**
+     * Adds a new name-and-value pair to an enum mock.
+     *
+     * @param mockEnum the enum mock
+     * @param name the name
+     * @param value the value
+     */
+    private void addEnumPair(Mock mockEnum, String name, Object value)
+    {
+        NameClassPair ncp = new NameClassPair(name, value.getClass().getName());
+        mockEnum.expectAndReturn("hasMore", true);
+        mockEnum.expectAndReturn("next", ncp);
+    }
+
+    /**
+     * Closes an enumeration mock.
+     *
+     * @param mockEnum the mock
+     */
+    private void closeEnum(Mock mockEnum)
+    {
+        mockEnum.expectAndReturn("hasMore", false);
+        mockEnum.expect(METHOD_CLOSE);
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/NonCloneableConfiguration.java b/src/test/java/org/apache/commons/configuration/NonCloneableConfiguration.java
new file mode 100644
index 0000000..9e0c83b
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/NonCloneableConfiguration.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import java.util.Iterator;
+
+/**
+ * A specialized configuration implementation that does not support cloning.
+ * This class is only used in some test cases for testing implementations of
+ * clone() methods. It does not make much sense otherwise; all methods are just
+ * dummies.
+ *
+ * @version $Id: NonCloneableConfiguration.java 1222456 2011-12-22 21:11:39Z oheger $
+ */
+public class NonCloneableConfiguration extends AbstractConfiguration
+{
+    /**
+     * Dummy implementation of this method.
+     */
+    @Override
+    protected void addPropertyDirect(String key, Object value)
+    {
+    }
+
+    /**
+     * Dummy implementation of this method.
+     */
+    public boolean isEmpty()
+    {
+        return true;
+    }
+
+    /**
+     * Dummy implementation of this method.
+     */
+    public boolean containsKey(String key)
+    {
+        return false;
+    }
+
+    /**
+     * Dummy implementation of this method.
+     */
+    public Iterator<String> getKeys()
+    {
+        return null;
+    }
+
+    /**
+     * Dummy implementation of this method.
+     */
+    public Object getProperty(String key)
+    {
+        return null;
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/NonStringTestHolder.java b/src/test/java/org/apache/commons/configuration/NonStringTestHolder.java
new file mode 100644
index 0000000..6862b0f
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/NonStringTestHolder.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.util.Iterator;
+import java.util.List;
+
+import junit.framework.Assert;
+
+/**
+ * Pulling out the calls to do the tests so both JUnit and Cactus tests
+ * can share.
+ *
+ * @version $Id: NonStringTestHolder.java 1302002 2012-03-17 20:43:46Z sebb $
+ */
+public class NonStringTestHolder
+{
+    private Configuration configuration;
+
+    public void setConfiguration(Configuration configuration)
+    {
+        this.configuration = configuration;
+    }
+
+    public void testBoolean() throws Exception
+    {
+        boolean booleanValue = configuration.getBoolean("test.boolean");
+        Assert.assertTrue(booleanValue);
+        Assert.assertEquals(1, configuration.getList("test.boolean").size());
+    }
+
+    public void testBooleanDefaultValue() throws Exception
+    {
+        boolean booleanValue = configuration.getBoolean("test.boolean.missing", true);
+        Assert.assertTrue(booleanValue);
+
+        Boolean booleanObject = configuration.getBoolean("test.boolean.missing", new Boolean(true));
+        Assert.assertEquals(new Boolean(true), booleanObject);
+    }
+
+    public void testByte() throws Exception
+    {
+        byte testValue = 10;
+        byte byteValue = configuration.getByte("test.byte");
+        Assert.assertEquals(testValue, byteValue);
+        Assert.assertEquals(1, configuration.getList("test.byte").size());
+    }
+
+    public void testDouble() throws Exception
+    {
+        double testValue = 10.25;
+        double doubleValue = configuration.getDouble("test.double");
+        Assert.assertEquals(testValue, doubleValue, 0.01);
+        Assert.assertEquals(1, configuration.getList("test.double").size());
+    }
+
+    public void testDoubleDefaultValue() throws Exception
+    {
+        double testValue = 10.25;
+        double doubleValue = configuration.getDouble("test.double.missing", 10.25);
+
+        Assert.assertEquals(testValue, doubleValue, 0.01);
+    }
+
+    public void testFloat() throws Exception
+    {
+        float testValue = (float) 20.25;
+        float floatValue = configuration.getFloat("test.float");
+        Assert.assertEquals(testValue, floatValue, 0.01);
+        Assert.assertEquals(1, configuration.getList("test.float").size());
+    }
+
+    public void testFloatDefaultValue() throws Exception
+    {
+        float testValue = (float) 20.25;
+        float floatValue = configuration.getFloat("test.float.missing", testValue);
+        Assert.assertEquals(testValue, floatValue, 0.01);
+    }
+
+    public void testInteger() throws Exception
+    {
+        int intValue = configuration.getInt("test.integer");
+        Assert.assertEquals(10, intValue);
+        Assert.assertEquals(1, configuration.getList("test.integer").size());
+    }
+
+    public void testIntegerDefaultValue() throws Exception
+    {
+        int intValue = configuration.getInt("test.integer.missing", 10);
+        Assert.assertEquals(10, intValue);
+    }
+
+    public void testLong() throws Exception
+    {
+        long longValue = configuration.getLong("test.long");
+        Assert.assertEquals(1000000, longValue);
+        Assert.assertEquals(1, configuration.getList("test.long").size());
+    }
+    public void testLongDefaultValue() throws Exception
+    {
+        long longValue = configuration.getLong("test.long.missing", 1000000);
+        Assert.assertEquals(1000000, longValue);
+    }
+
+    public void testShort() throws Exception
+    {
+        short shortValue = configuration.getShort("test.short");
+        Assert.assertEquals(1, shortValue);
+        Assert.assertEquals(1, configuration.getList("test.short").size());
+    }
+
+    public void testShortDefaultValue() throws Exception
+    {
+        short shortValue = configuration.getShort("test.short.missing", (short) 1);
+        Assert.assertEquals(1, shortValue);
+    }
+
+    public void testListMissing() throws Exception
+    {
+        List<?> list = configuration.getList("missing.list");
+        Assert.assertTrue("'missing.list' is not empty", list.isEmpty());
+    }
+
+    public void testSubset() throws Exception
+    {
+        Configuration subset = configuration.subset("test");
+
+        // search the "short" key in the subset using the key iterator
+        boolean foundKeyValue = false;
+        Iterator<String> it = subset.getKeys();
+        while (it.hasNext() && !foundKeyValue)
+        {
+            String key = it.next();
+            foundKeyValue = "short".equals(key);
+        }
+
+        Assert.assertTrue("'short' key not found in the subset key iterator", foundKeyValue);
+    }
+
+    public void testIsEmpty() throws Exception
+    {
+        Assert.assertTrue("Configuration should not be empty", !configuration.isEmpty());
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestAbstractConfiguration.java b/src/test/java/org/apache/commons/configuration/TestAbstractConfiguration.java
new file mode 100644
index 0000000..d18d1d7
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestAbstractConfiguration.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import junitx.framework.ListAssert;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.junit.Test;
+
+/**
+ * Abstract TestCase for implementations of {@link AbstractConfiguration}.
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: TestAbstractConfiguration.java 1222465 2011-12-22 21:32:56Z oheger $
+ */
+public abstract class TestAbstractConfiguration
+{
+    /**
+     * Return an abstract configuration with the following data:<br>
+     * <pre>
+     * key1 = value1
+     * key2 = value2
+     * list = value1, value2
+     * listesc = value1\\,value2
+     * </pre>
+     */
+    protected abstract AbstractConfiguration getConfiguration();
+
+    /**
+     * Return an empty configuration.
+     */
+    protected abstract AbstractConfiguration getEmptyConfiguration();
+
+    @Test
+    public void testGetProperty()
+    {
+        Configuration config = getConfiguration();
+        assertEquals("key1", "value1", config.getProperty("key1"));
+        assertEquals("key2", "value2", config.getProperty("key2"));
+        assertNull("key3", config.getProperty("key3"));
+    }
+
+    @Test
+    public void testList()
+    {
+        Configuration config = getConfiguration();
+
+        List<?> list = config.getList("list");
+        assertNotNull("list not found", config.getProperty("list"));
+        assertEquals("list size", 2, list.size());
+        assertTrue("'value1' is not in the list", list.contains("value1"));
+        assertTrue("'value2' is not in the list", list.contains("value2"));
+    }
+
+    /**
+     * Tests whether the escape character for list delimiters is recocknized and
+     * removed.
+     */
+    @Test
+    public void testListEscaped()
+    {
+        assertEquals("Wrong value for escaped list", "value1,value2",
+                getConfiguration().getString("listesc"));
+    }
+
+    @Test
+    public void testAddPropertyDirect()
+    {
+        AbstractConfiguration config = getConfiguration();
+        config.addPropertyDirect("key3", "value3");
+        assertEquals("key3", "value3", config.getProperty("key3"));
+
+        config.addPropertyDirect("key3", "value4");
+        config.addPropertyDirect("key3", "value5");
+        List<Object> list = config.getList("key3");
+        assertNotNull("no list found for the 'key3' property", list);
+
+        List<Object> expected = new ArrayList<Object>();
+        expected.add("value3");
+        expected.add("value4");
+        expected.add("value5");
+
+        ListAssert.assertEquals("values for the 'key3' property", expected, list);
+    }
+
+    @Test
+    public void testIsEmpty()
+    {
+        Configuration config = getConfiguration();
+        assertFalse("the configuration is empty", config.isEmpty());
+        assertTrue("the configuration is not empty", getEmptyConfiguration().isEmpty());
+    }
+
+    @Test
+    public void testContainsKey()
+    {
+        Configuration config = getConfiguration();
+        assertTrue("key1 not found", config.containsKey("key1"));
+        assertFalse("key3 found", config.containsKey("key3"));
+    }
+
+    @Test
+    public void testClearProperty()
+    {
+        Configuration config = getConfiguration();
+        config.clearProperty("key2");
+        assertFalse("key2 not cleared", config.containsKey("key2"));
+    }
+
+    @Test
+    public void testGetKeys()
+    {
+        Configuration config = getConfiguration();
+        Iterator<String> keys = config.getKeys();
+
+        List<String> expectedKeys = new ArrayList<String>();
+        expectedKeys.add("key1");
+        expectedKeys.add("key2");
+        expectedKeys.add("list");
+        expectedKeys.add("listesc");
+
+        assertNotNull("null iterator", keys);
+        assertTrue("empty iterator", keys.hasNext());
+
+        List<String> actualKeys = new ArrayList<String>();
+        while (keys.hasNext())
+        {
+            actualKeys.add(keys.next());
+        }
+
+        ListAssert.assertEquals("keys", expectedKeys, actualKeys);
+    }
+
+    /**
+     * Tests accessing the configuration's logger.
+     */
+    @Test
+    public void testSetLogger()
+    {
+        AbstractConfiguration config = getEmptyConfiguration();
+        assertNotNull("Default logger is null", config.getLogger());
+        Log log = LogFactory.getLog(config.getClass());
+        config.setLogger(log);
+        assertSame("Logger was not set", log, config.getLogger());
+    }
+
+    /**
+     * Tests the exception message triggered by the conversion to BigInteger.
+     * This test is related to CONFIGURATION-357.
+     */
+    @Test
+    public void testGetBigIntegerConversion()
+    {
+        Configuration config = getConfiguration();
+        try
+        {
+            config.getBigInteger("key1");
+            fail("No conversion exception thrown!");
+        }
+        catch (ConversionException cex)
+        {
+            assertEquals("Wrong exception message",
+                    "'key1' doesn't map to a BigInteger object", cex
+                            .getMessage());
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestAbstractConfigurationBasicFeatures.java b/src/test/java/org/apache/commons/configuration/TestAbstractConfigurationBasicFeatures.java
new file mode 100644
index 0000000..35da02a
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestAbstractConfigurationBasicFeatures.java
@@ -0,0 +1,529 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.configuration.event.ConfigurationEvent;
+import org.apache.commons.configuration.event.ConfigurationListener;
+import org.junit.Test;
+
+/**
+ * A test class for some of the basic functionality implemented by
+ * AbstractConfiguration.
+ *
+ * @version $Id: TestAbstractConfigurationBasicFeatures.java 1222823 2011-12-23 20:03:10Z oheger $
+ */
+public class TestAbstractConfigurationBasicFeatures
+{
+    /** Constant for the prefix of test keys.*/
+    private static final String KEY_PREFIX = "key";
+
+    /** Constant for the number of properties in tests for copy operations.*/
+    private static final int PROP_COUNT = 12;
+
+    /**
+     * Tests the clear() implementation of AbstractConfiguration if the iterator
+     * returned by getKeys() does not support the remove() operation.
+     */
+    @Test
+    public void testClearIteratorNoRemove()
+    {
+        AbstractConfiguration config = new TestConfigurationImpl(
+                new BaseConfiguration())
+        {
+            // return an iterator that does not support remove operations
+            @Override
+            public Iterator<String> getKeys()
+            {
+                Collection<String> keyCol = new ArrayList<String>();
+                CollectionUtils.addAll(keyCol, getUnderlyingConfiguration()
+                        .getKeys());
+                String[] keys = keyCol.toArray(new String[keyCol.size()]);
+                return Arrays.asList(keys).iterator();
+            }
+        };
+        for (int i = 0; i < 20; i++)
+        {
+            config.addProperty("key" + i, "value" + i);
+        }
+        config.clear();
+        assertTrue("Configuration not empty", config.isEmpty());
+    }
+
+    /**
+     * Tests escaping the variable marker, so that no interpolation will be
+     * performed.
+     */
+    @Test
+    public void testInterpolateEscape()
+    {
+        AbstractConfiguration config = new TestConfigurationImpl(
+                new PropertiesConfiguration());
+        config
+                .addProperty(
+                        "mypath",
+                        "$${DB2UNIVERSAL_JDBC_DRIVER_PATH}/db2jcc.jar\\,$${DB2UNIVERSAL_JDBC_DRIVER_PATH}/db2jcc_license_cu.jar");
+        assertEquals(
+                "Wrong interpolated value",
+                "${DB2UNIVERSAL_JDBC_DRIVER_PATH}/db2jcc.jar,${DB2UNIVERSAL_JDBC_DRIVER_PATH}/db2jcc_license_cu.jar",
+                config.getString("mypath"));
+    }
+
+    /**
+     * Tests adding list properties. The single elements of the list should be
+     * added.
+     */
+    @Test
+    public void testAddPropertyList()
+    {
+        checkAddListProperty(new TestConfigurationImpl(
+                new PropertiesConfiguration()));
+    }
+
+    /**
+     * Tests adding list properties when delimiter parsing is disabled.
+     */
+    @Test
+    public void testAddPropertyListNoDelimiterParsing()
+    {
+        AbstractConfiguration config = new TestConfigurationImpl(
+                new PropertiesConfiguration());
+        config.setDelimiterParsingDisabled(true);
+        checkAddListProperty(config);
+    }
+
+    /**
+     * Helper method for adding properties with multiple values.
+     *
+     * @param config the configuration to be used for testing
+     */
+    private void checkAddListProperty(AbstractConfiguration config)
+    {
+        config.addProperty("test", "value1");
+        Object[] lstValues1 = new Object[]
+        { "value2", "value3" };
+        Object[] lstValues2 = new Object[]
+        { "value4", "value5", "value6" };
+        config.addProperty("test", lstValues1);
+        config.addProperty("test", Arrays.asList(lstValues2));
+        List<Object> lst = config.getList("test");
+        assertEquals("Wrong number of list elements", 6, lst.size());
+        for (int i = 0; i < lst.size(); i++)
+        {
+            assertEquals("Wrong list element at " + i, "value" + (i + 1), lst
+                    .get(i));
+        }
+    }
+
+    /**
+     * Tests the copy() method.
+     */
+    @Test
+    public void testCopy()
+    {
+        AbstractConfiguration config = setUpDestConfig();
+        Configuration srcConfig = setUpSourceConfig();
+        config.copy(srcConfig);
+        for (int i = 0; i < PROP_COUNT; i++)
+        {
+            String key = KEY_PREFIX + i;
+            if (srcConfig.containsKey(key))
+            {
+                assertEquals("Value not replaced: " + key, srcConfig
+                        .getProperty(key), config.getProperty(key));
+            }
+            else
+            {
+                assertEquals("Value modified: " + key, "value" + i, config
+                        .getProperty(key));
+            }
+        }
+    }
+
+    /**
+     * Tests the copy() method when properties with multiple values and escaped
+     * list delimiters are involved.
+     */
+    @Test
+    public void testCopyWithLists()
+    {
+        Configuration srcConfig = setUpSourceConfig();
+        AbstractConfiguration config = setUpDestConfig();
+        config.copy(srcConfig);
+        checkListProperties(config);
+    }
+
+    /**
+     * Tests the events generated by a copy() operation.
+     */
+    @Test
+    public void testCopyEvents()
+    {
+        AbstractConfiguration config = setUpDestConfig();
+        Configuration srcConfig = setUpSourceConfig();
+        CollectingConfigurationListener l = new CollectingConfigurationListener();
+        config.addConfigurationListener(l);
+        config.copy(srcConfig);
+        checkCopyEvents(l, srcConfig, AbstractConfiguration.EVENT_SET_PROPERTY);
+    }
+
+    /**
+     * Tests copying a null configuration. This should be a noop.
+     */
+    @Test
+    public void testCopyNull()
+    {
+        AbstractConfiguration config = setUpDestConfig();
+        config.copy(null);
+        ConfigurationAssert.assertEquals(setUpDestConfig(), config);
+    }
+
+    /**
+     * Tests the append() method.
+     */
+    @Test
+    public void testAppend()
+    {
+        AbstractConfiguration config = setUpDestConfig();
+        Configuration srcConfig = setUpSourceConfig();
+        config.append(srcConfig);
+        for (int i = 0; i < PROP_COUNT; i++)
+        {
+            String key = KEY_PREFIX + i;
+            if (srcConfig.containsKey(key))
+            {
+                List<Object> values = config.getList(key);
+                assertEquals("Value not added: " + key, 2, values.size());
+                assertEquals("Wrong value 1 for " + key, "value" + i, values
+                        .get(0));
+                assertEquals("Wrong value 2 for " + key, "src" + i, values
+                        .get(1));
+            }
+            else
+            {
+                assertEquals("Value modified: " + key, "value" + i, config
+                        .getProperty(key));
+            }
+        }
+    }
+
+    /**
+     * Tests the append() method when properties with multiple values and
+     * escaped list delimiters are involved.
+     */
+    @Test
+    public void testAppendWithLists()
+    {
+        AbstractConfiguration config = setUpDestConfig();
+        config.append(setUpSourceConfig());
+        checkListProperties(config);
+    }
+
+    /**
+     * Tests the events generated by an append() operation.
+     */
+    @Test
+    public void testAppendEvents()
+    {
+        AbstractConfiguration config = setUpDestConfig();
+        Configuration srcConfig = setUpSourceConfig();
+        CollectingConfigurationListener l = new CollectingConfigurationListener();
+        config.addConfigurationListener(l);
+        config.append(srcConfig);
+        checkCopyEvents(l, srcConfig, AbstractConfiguration.EVENT_ADD_PROPERTY);
+    }
+
+    /**
+     * Tests appending a null configuration. This should be a noop.
+     */
+    @Test
+    public void testAppendNull()
+    {
+        AbstractConfiguration config = setUpDestConfig();
+        config.append(null);
+        ConfigurationAssert.assertEquals(setUpDestConfig(), config);
+    }
+
+    /**
+     * Tests whether environment variables can be interpolated.
+     */
+    @Test
+    public void testInterpolateEnvironmentVariables()
+    {
+        AbstractConfiguration config = new TestConfigurationImpl(
+                new PropertiesConfiguration());
+        EnvironmentConfiguration envConfig = new EnvironmentConfiguration();
+        Map<String, Object> env = new HashMap<String, Object>();
+        for (Iterator<String> it = envConfig.getKeys(); it.hasNext();)
+        {
+            String key = it.next();
+            String propKey = "envtest." + key;
+            env.put(propKey, envConfig.getString(key));
+            config.addProperty(propKey, "${env:" + key + "}");
+        }
+        assertFalse("No environment properties", env.isEmpty());
+        for (Map.Entry<String, Object> e : env.entrySet())
+        {
+            assertEquals("Wrong value for " + e.getKey(), e.getValue(), config
+                    .getString(e.getKey()));
+        }
+    }
+
+    /**
+     * Tests getList() for single non-string values.
+     */
+    @Test
+    public void testGetListNonString()
+    {
+        checkGetListScalar(Integer.valueOf(42));
+        checkGetListScalar(Long.valueOf(42));
+        checkGetListScalar(Short.valueOf((short) 42));
+        checkGetListScalar(Byte.valueOf((byte) 42));
+        checkGetListScalar(Float.valueOf(42));
+        checkGetListScalar(Double.valueOf(42));
+        checkGetListScalar(Boolean.TRUE);
+}
+
+    /**
+     * Tests getStringArray() for single son-string values.
+     */
+    @Test
+    public void testGetStringArrayNonString()
+    {
+        checkGetStringArrayScalar(Integer.valueOf(42));
+        checkGetStringArrayScalar(Long.valueOf(42));
+        checkGetStringArrayScalar(Short.valueOf((short) 42));
+        checkGetStringArrayScalar(Byte.valueOf((byte) 42));
+        checkGetStringArrayScalar(Float.valueOf(42));
+        checkGetStringArrayScalar(Double.valueOf(42));
+        checkGetStringArrayScalar(Boolean.TRUE);
+    }
+
+    /**
+     * Helper method for checking getList() if the property value is a scalar.
+     * @param value the value of the property
+     */
+    private void checkGetListScalar(Object value)
+    {
+        BaseConfiguration config = new BaseConfiguration();
+        config.addProperty(KEY_PREFIX, value);
+        List<Object> lst = config.getList(KEY_PREFIX);
+        assertEquals("Wrong number of values", 1, lst.size());
+        assertEquals("Wrong value", value.toString(), lst.get(0));
+    }
+
+    /**
+     * Helper method for checking getStringArray() if the property value is a
+     * scalar.
+     *
+     * @param value the value of the property
+     */
+    private void checkGetStringArrayScalar(Object value)
+    {
+        BaseConfiguration config = new BaseConfiguration();
+        config.addProperty(KEY_PREFIX, value);
+        String[] array = config.getStringArray(KEY_PREFIX);
+        assertEquals("Weong number of elements", 1, array.length);
+        assertEquals("Wrong value", value.toString(), array[0]);
+    }
+
+    /**
+     * Tests whether interpolation works in variable names.
+     */
+    @Test
+    public void testNestedVariableInterpolation()
+    {
+        BaseConfiguration config = new BaseConfiguration();
+        config.getSubstitutor().setEnableSubstitutionInVariables(true);
+        config.addProperty("java.version", "1.4");
+        config.addProperty("jre-1.4", "C:\\java\\1.4");
+        config.addProperty("jre.path", "${jre-${java.version}}");
+        assertEquals("Wrong path", "C:\\java\\1.4",
+                config.getString("jre.path"));
+    }
+
+    /**
+     * Creates the source configuration for testing the copy() and append()
+     * methods. This configuration contains keys with an odd index and values
+     * starting with the prefix "src". There are also some list properties.
+     *
+     * @return the source configuration for copy operations
+     */
+    private Configuration setUpSourceConfig()
+    {
+        BaseConfiguration config = new BaseConfiguration();
+        for (int i = 1; i < PROP_COUNT; i += 2)
+        {
+            config.addProperty(KEY_PREFIX + i, "src" + i);
+        }
+        config.addProperty("list1", "1,2,3");
+        config.addProperty("list2", "3\\,1415,9\\,81");
+        return config;
+    }
+
+    /**
+     * Creates the destination configuration for testing the copy() and append()
+     * methods. This configuration contains keys with a running index and
+     * corresponding values starting with the prefix "value".
+     *
+     * @return the destination configuration for copy operations
+     */
+    private AbstractConfiguration setUpDestConfig()
+    {
+        AbstractConfiguration config = new TestConfigurationImpl(
+                new PropertiesConfiguration());
+        for (int i = 0; i < PROP_COUNT; i++)
+        {
+            config.addProperty(KEY_PREFIX + i, "value" + i);
+        }
+        return config;
+    }
+
+    /**
+     * Tests the values of list properties after a copy operation.
+     *
+     * @param config the configuration to test
+     */
+    private void checkListProperties(Configuration config)
+    {
+        List<Object> values = config.getList("list1");
+        assertEquals("Wrong number of elements in list 1", 3, values.size());
+        values = config.getList("list2");
+        assertEquals("Wrong number of elements in list 2", 2, values.size());
+        assertEquals("Wrong value 1", "3,1415", values.get(0));
+        assertEquals("Wrong value 2", "9,81", values.get(1));
+    }
+
+    /**
+     * Tests whether the correct events are received for a copy operation.
+     *
+     * @param l the event listener
+     * @param src the configuration that was copied
+     * @param eventType the expected event type
+     */
+    private void checkCopyEvents(CollectingConfigurationListener l,
+            Configuration src, int eventType)
+    {
+        Map<String, ConfigurationEvent> events = new HashMap<String, ConfigurationEvent>();
+        for (ConfigurationEvent e : l.events)
+        {
+            assertEquals("Wrong event type", eventType, e.getType());
+            assertTrue("Unknown property: " + e.getPropertyName(), src
+                    .containsKey(e.getPropertyName()));
+            assertEquals("Wrong property value for " + e.getPropertyName(), e
+                    .getPropertyValue(), src.getProperty(e.getPropertyName()));
+            if (!e.isBeforeUpdate())
+            {
+                assertTrue("After event without before event", events
+                        .containsKey(e.getPropertyName()));
+            }
+            else
+            {
+                events.put(e.getPropertyName(), e);
+            }
+        }
+
+        for (Iterator<String> it = src.getKeys(); it.hasNext();)
+        {
+            String key = it.next();
+            assertTrue("No event received for key " + key, events
+                    .containsKey(key));
+        }
+    }
+
+    /**
+     * A test configuration implementation. This implementation inherits
+     * directly from AbstractConfiguration. For implementing the required
+     * functionality another implementation of AbstractConfiguration is used;
+     * all methods that need to be implemented delegate to this wrapped
+     * configuration.
+     */
+    static class TestConfigurationImpl extends AbstractConfiguration
+    {
+        /** Stores the underlying configuration. */
+        private AbstractConfiguration config;
+
+        public AbstractConfiguration getUnderlyingConfiguration()
+        {
+            return config;
+        }
+
+        public TestConfigurationImpl(AbstractConfiguration wrappedConfig)
+        {
+            config = wrappedConfig;
+        }
+
+        @Override
+        protected void addPropertyDirect(String key, Object value)
+        {
+            config.addPropertyDirect(key, value);
+        }
+
+        public boolean containsKey(String key)
+        {
+            return config.containsKey(key);
+        }
+
+        public Iterator<String> getKeys()
+        {
+            return config.getKeys();
+        }
+
+        public Object getProperty(String key)
+        {
+            return config.getProperty(key);
+        }
+
+        public boolean isEmpty()
+        {
+            return config.isEmpty();
+        }
+
+        @Override
+        protected void clearPropertyDirect(String key)
+        {
+            config.clearPropertyDirect(key);
+        }
+    }
+
+    /**
+     * An event listener implementation that simply collects all received
+     * configuration events.
+     */
+    static class CollectingConfigurationListener implements
+            ConfigurationListener
+    {
+        List<ConfigurationEvent> events = new ArrayList<ConfigurationEvent>();
+
+        public void configurationChanged(ConfigurationEvent event)
+        {
+            events.add(event);
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestBaseConfiguration.java b/src/test/java/org/apache/commons/configuration/TestBaseConfiguration.java
new file mode 100644
index 0000000..a0c363a
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestBaseConfiguration.java
@@ -0,0 +1,733 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Properties;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import junitx.framework.ListAssert;
+
+import org.apache.commons.configuration.event.ConfigurationEvent;
+import org.apache.commons.configuration.event.ConfigurationListener;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests some basic functions of the BaseConfiguration class. Missing keys will
+ * throw Exceptions
+ *
+ * @version $Id: TestBaseConfiguration.java 1231721 2012-01-15 18:32:07Z oheger $
+ */
+public class TestBaseConfiguration
+{
+    /** Constant for the number key.*/
+    static final String KEY_NUMBER = "number";
+
+    protected BaseConfiguration config = null;
+
+    protected static Class<?> missingElementException = NoSuchElementException.class;
+    protected static Class<?> incompatibleElementException = ConversionException.class;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        config = new BaseConfiguration();
+        config.setThrowExceptionOnMissing(true);
+    }
+
+    @Test
+    public void testThrowExceptionOnMissing()
+    {
+        assertTrue("Throw Exception Property is not set!", config.isThrowExceptionOnMissing());
+    }
+
+    @Test
+    public void testGetProperty()
+    {
+        /* should be empty and return null */
+        assertEquals("This returns null", config.getProperty("foo"), null);
+
+        /* add a real value, and get it two different ways */
+        config.setProperty("number", "1");
+        assertEquals("This returns '1'", config.getProperty("number"), "1");
+        assertEquals("This returns '1'", config.getString("number"), "1");
+    }
+
+    @Test
+    public void testGetByte()
+    {
+        config.setProperty("number", "1");
+        byte oneB = 1;
+        byte twoB = 2;
+        assertEquals("This returns 1(byte)", oneB, config.getByte("number"));
+        assertEquals("This returns 1(byte)", oneB, config.getByte("number", twoB));
+        assertEquals("This returns 2(default byte)", twoB, config.getByte("numberNotInConfig", twoB));
+        assertEquals("This returns 1(Byte)", new Byte(oneB), config.getByte("number", new Byte("2")));
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void testGetByteUnknown()
+    {
+        config.getByte("numberNotInConfig");
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testGetByteIncompatibleType()
+    {
+        config.setProperty("test.empty", "");
+        config.getByte("test.empty");
+    }
+
+    @Test
+    public void testGetShort()
+    {
+        config.setProperty("numberS", "1");
+        short oneS = 1;
+        short twoS = 2;
+        assertEquals("This returns 1(short)", oneS, config.getShort("numberS"));
+        assertEquals("This returns 1(short)", oneS, config.getShort("numberS", twoS));
+        assertEquals("This returns 2(default short)", twoS, config.getShort("numberNotInConfig", twoS));
+        assertEquals("This returns 1(Short)", new Short(oneS), config.getShort("numberS", new Short("2")));
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void testGetShortUnknown()
+    {
+        config.getShort("numberNotInConfig");
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testGetShortIncompatibleType()
+    {
+        config.setProperty("test.empty", "");
+        config.getShort("test.empty");
+    }
+
+    @Test
+    public void testGetLong()
+    {
+        config.setProperty("numberL", "1");
+        long oneL = 1;
+        long twoL = 2;
+        assertEquals("This returns 1(long)", oneL, config.getLong("numberL"));
+        assertEquals("This returns 1(long)", oneL, config.getLong("numberL", twoL));
+        assertEquals("This returns 2(default long)", twoL, config.getLong("numberNotInConfig", twoL));
+        assertEquals("This returns 1(Long)", new Long(oneL), config.getLong("numberL", new Long("2")));
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void testGetLongUnknown()
+    {
+        config.getLong("numberNotInConfig");
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testGetLongIncompatibleTypes()
+    {
+        config.setProperty("test.empty", "");
+        config.getLong("test.empty");
+    }
+
+    @Test
+    public void testGetFloat()
+    {
+        config.setProperty("numberF", "1.0");
+        float oneF = 1;
+        float twoF = 2;
+        assertEquals("This returns 1(float)", oneF, config.getFloat("numberF"), 0);
+        assertEquals("This returns 1(float)", oneF, config.getFloat("numberF", twoF), 0);
+        assertEquals("This returns 2(default float)", twoF, config.getFloat("numberNotInConfig", twoF), 0);
+        assertEquals("This returns 1(Float)", new Float(oneF), config.getFloat("numberF", new Float("2")));
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void testGetFloatUnknown()
+    {
+        config.getFloat("numberNotInConfig");
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testGetFloatIncompatibleType()
+    {
+        config.setProperty("test.empty", "");
+        config.getFloat("test.empty");
+    }
+
+    @Test
+    public void testGetDouble()
+    {
+        config.setProperty("numberD", "1.0");
+        double oneD = 1;
+        double twoD = 2;
+        assertEquals("This returns 1(double)", oneD, config.getDouble("numberD"), 0);
+        assertEquals("This returns 1(double)", oneD, config.getDouble("numberD", twoD), 0);
+        assertEquals("This returns 2(default double)", twoD, config.getDouble("numberNotInConfig", twoD), 0);
+        assertEquals("This returns 1(Double)", new Double(oneD), config.getDouble("numberD", new Double("2")));
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void testGetDoubleUnknown()
+    {
+        config.getDouble("numberNotInConfig");
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testGetDoubleIncompatibleType()
+    {
+        config.setProperty("test.empty", "");
+        config.getDouble("test.empty");
+    }
+
+    @Test
+    public void testGetBigDecimal()
+    {
+        config.setProperty("numberBigD", "123.456");
+        BigDecimal number = new BigDecimal("123.456");
+        BigDecimal defaultValue = new BigDecimal("654.321");
+
+        assertEquals("Existing key", number, config.getBigDecimal("numberBigD"));
+        assertEquals("Existing key with default value", number, config.getBigDecimal("numberBigD", defaultValue));
+        assertEquals("Missing key with default value", defaultValue, config.getBigDecimal("numberNotInConfig", defaultValue));
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void testGetBigDecimalUnknown()
+    {
+        config.getBigDecimal("numberNotInConfig");
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testGetBigDecimalIncompatibleType()
+    {
+        config.setProperty("test.empty", "");
+        config.getBigDecimal("test.empty");
+    }
+
+    @Test
+    public void testGetBigInteger()
+    {
+        config.setProperty("numberBigI", "1234567890");
+        BigInteger number = new BigInteger("1234567890");
+        BigInteger defaultValue = new BigInteger("654321");
+
+        assertEquals("Existing key", number, config.getBigInteger("numberBigI"));
+        assertEquals("Existing key with default value", number, config.getBigInteger("numberBigI", defaultValue));
+        assertEquals("Missing key with default value", defaultValue, config.getBigInteger("numberNotInConfig", defaultValue));
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void testGetBigIntegerUnknown()
+    {
+        config.getBigInteger("numberNotInConfig");
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testGetBigIntegerIncompatibleType()
+    {
+        config.setProperty("test.empty", "");
+        config.getBigInteger("test.empty");
+    }
+
+    @Test
+    public void testGetString()
+    {
+        config.setProperty("testString", "The quick brown fox");
+        String string = "The quick brown fox";
+        String defaultValue = "jumps over the lazy dog";
+
+        assertEquals("Existing key", string, config.getString("testString"));
+        assertEquals("Existing key with default value", string, config.getString("testString", defaultValue));
+        assertEquals("Missing key with default value", defaultValue, config.getString("stringNotInConfig", defaultValue));
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void testGetStringUnknown()
+    {
+        config.getString("stringNotInConfig");
+    }
+
+    @Test
+    public void testGetBoolean()
+    {
+        config.setProperty("boolA", Boolean.TRUE);
+        boolean boolT = true, boolF = false;
+        assertEquals("This returns true", boolT, config.getBoolean("boolA"));
+        assertEquals("This returns true, not the default", boolT, config.getBoolean("boolA", boolF));
+        assertEquals("This returns false(default)", boolF, config.getBoolean("boolNotInConfig", boolF));
+        assertEquals("This returns true(Boolean)", new Boolean(boolT), config.getBoolean("boolA", new Boolean(boolF)));
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void testGetBooleanUnknown()
+    {
+        config.getBoolean("numberNotInConfig");
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testGetBooleanIncompatibleType()
+    {
+        config.setProperty("test.empty", "");
+        config.getBoolean("test.empty");
+    }
+
+    @Test
+    public void testGetList()
+    {
+        config.addProperty("number", "1");
+        config.addProperty("number", "2");
+        List<Object> list = config.getList("number");
+        assertNotNull("The list is null", list);
+        assertEquals("List size", 2, list.size());
+        assertTrue("The number 1 is missing from the list", list.contains("1"));
+        assertTrue("The number 2 is missing from the list", list.contains("2"));
+    }
+
+    /**
+     * Tests that the first scalar of a list is returned.
+     */
+    @Test
+    public void testGetStringForListValue()
+    {
+        config.addProperty("number", "1");
+        config.addProperty("number", "2");
+        assertEquals("Wrong result", "1", config.getString("number"));
+    }
+
+    @Test
+    public void testGetInterpolatedList()
+    {
+        config.addProperty("number", "1");
+        config.addProperty("array", "${number}");
+        config.addProperty("array", "${number}");
+
+        List<String> list = new ArrayList<String>();
+        list.add("1");
+        list.add("1");
+
+        ListAssert.assertEquals("'array' property", list, config.getList("array"));
+    }
+
+    @Test
+    public void testGetInterpolatedPrimitives()
+    {
+        config.addProperty("number", "1");
+        config.addProperty("value", "${number}");
+
+        config.addProperty("boolean", "true");
+        config.addProperty("booleanValue", "${boolean}");
+
+        // primitive types
+        assertEquals("boolean interpolation", true, config.getBoolean("booleanValue"));
+        assertEquals("byte interpolation", 1, config.getByte("value"));
+        assertEquals("short interpolation", 1, config.getShort("value"));
+        assertEquals("int interpolation", 1, config.getInt("value"));
+        assertEquals("long interpolation", 1, config.getLong("value"));
+        assertEquals("float interpolation", 1, config.getFloat("value"), 0);
+        assertEquals("double interpolation", 1, config.getDouble("value"), 0);
+
+        // primitive wrappers
+        assertEquals("Boolean interpolation", Boolean.TRUE, config.getBoolean("booleanValue", null));
+        assertEquals("Byte interpolation", new Byte("1"), config.getByte("value", null));
+        assertEquals("Short interpolation", new Short("1"), config.getShort("value", null));
+        assertEquals("Integer interpolation", new Integer("1"), config.getInteger("value", null));
+        assertEquals("Long interpolation", new Long("1"), config.getLong("value", null));
+        assertEquals("Float interpolation", new Float("1"), config.getFloat("value", null));
+        assertEquals("Double interpolation", new Double("1"), config.getDouble("value", null));
+
+        assertEquals("BigInteger interpolation", new BigInteger("1"), config.getBigInteger("value", null));
+        assertEquals("BigDecimal interpolation", new BigDecimal("1"), config.getBigDecimal("value", null));
+    }
+
+    @Test
+    public void testCommaSeparatedString()
+    {
+        String prop = "hey, that's a test";
+        config.setProperty("prop.string", prop);
+        List<Object> list = config.getList("prop.string");
+        assertEquals("Wrong number of list elements", 2, list.size());
+        assertEquals("Wrong element 1", "hey", list.get(0));
+    }
+
+    @Test
+    public void testCommaSeparatedStringEscaped()
+    {
+        String prop2 = "hey\\, that's a test";
+        config.setProperty("prop.string", prop2);
+        assertEquals("Wrong value", "hey, that's a test", config.getString("prop.string"));
+    }
+
+    @Test
+    public void testAddProperty() throws Exception
+    {
+        Collection<Object> props = new ArrayList<Object>();
+        props.add("one");
+        props.add("two,three,four");
+        props.add(new String[] { "5.1", "5.2", "5.3,5.4", "5.5" });
+        props.add("six");
+        config.addProperty("complex.property", props);
+
+        Object val = config.getProperty("complex.property");
+        assertTrue(val instanceof Collection);
+        Collection<?> col = (Collection<?>) val;
+        assertEquals(10, col.size());
+
+        props = new ArrayList<Object>();
+        props.add("quick");
+        props.add("brown");
+        props.add("fox,jumps");
+        Object[] data = new Object[] {
+                "The", props, "over,the", "lazy", "dog."
+        };
+        config.setProperty("complex.property", data);
+        val = config.getProperty("complex.property");
+        assertTrue(val instanceof Collection);
+        col = (Collection<?>) val;
+        Iterator<?> it = col.iterator();
+        StringTokenizer tok = new StringTokenizer("The quick brown fox jumps over the lazy dog.", " ");
+        while(tok.hasMoreTokens())
+        {
+            assertTrue(it.hasNext());
+            assertEquals(tok.nextToken(), it.next());
+        }
+        assertFalse(it.hasNext());
+
+        config.setProperty("complex.property", null);
+        assertFalse(config.containsKey("complex.property"));
+    }
+
+    @Test
+    public void testPropertyAccess()
+    {
+        config.clearProperty("prop.properties");
+        config.setProperty("prop.properties", "");
+        assertEquals(
+                "This returns an empty Properties object",
+                config.getProperties("prop.properties"),
+                new Properties());
+        config.clearProperty("prop.properties");
+        config.setProperty("prop.properties", "foo=bar, baz=moo, seal=clubber");
+
+        Properties p = new Properties();
+        p.setProperty("foo", "bar");
+        p.setProperty("baz", "moo");
+        p.setProperty("seal", "clubber");
+        assertEquals(
+                "This returns a filled in Properties object",
+                config.getProperties("prop.properties"),
+                p);
+    }
+
+    @Test
+    public void testSubset()
+    {
+        /*
+         * test subset : assure we don't reprocess the data elements
+         * when generating the subset
+         */
+
+        String prop = "hey, that's a test";
+        String prop2 = "hey\\, that's a test";
+        config.setProperty("prop.string", prop2);
+        config.setProperty("property.string", "hello");
+
+        Configuration subEprop = config.subset("prop");
+
+        assertEquals(
+                "Returns the full string",
+                prop,
+                subEprop.getString("string"));
+        assertEquals("Wrong list size", 1, subEprop.getList("string").size());
+
+        Iterator<String> it = subEprop.getKeys();
+        it.next();
+        assertFalse(it.hasNext());
+
+        subEprop = config.subset("prop.");
+        it = subEprop.getKeys();
+        assertFalse(it.hasNext());
+    }
+
+    @Test
+    public void testInterpolation()
+    {
+        InterpolationTestHelper.testInterpolation(config);
+    }
+
+    @Test
+    public void testMultipleInterpolation()
+    {
+        InterpolationTestHelper.testMultipleInterpolation(config);
+    }
+
+    @Test
+    public void testInterpolationLoop()
+    {
+        InterpolationTestHelper.testInterpolationLoop(config);
+    }
+
+    /**
+     * Tests interpolation when a subset configuration is involved.
+     */
+    @Test
+    public void testInterpolationSubset()
+    {
+        InterpolationTestHelper.testInterpolationSubset(config);
+    }
+
+    /**
+     * Tests interpolation when the referred property is not found.
+     */
+    @Test
+    public void testInterpolationUnknownProperty()
+    {
+        InterpolationTestHelper.testInterpolationUnknownProperty(config);
+    }
+
+    /**
+     * Tests interpolation of system properties.
+     */
+    @Test
+    public void testInterpolationSystemProperties()
+    {
+        InterpolationTestHelper.testInterpolationSystemProperties(config);
+    }
+
+    /**
+     * Tests interpolation of constant values.
+     */
+    @Test
+    public void testInterpolationConstants()
+    {
+        InterpolationTestHelper.testInterpolationConstants(config);
+    }
+
+    /**
+     * Tests whether a variable can be escaped, so that it won't be
+     * interpolated.
+     */
+    @Test
+    public void testInterpolationEscaped()
+    {
+        InterpolationTestHelper.testInterpolationEscaped(config);
+    }
+
+    /**
+     * Tests accessing and manipulating the interpolator object.
+     */
+    @Test
+    public void testGetInterpolator()
+    {
+        InterpolationTestHelper.testGetInterpolator(config);
+    }
+
+    /**
+     * Tests obtaining a configuration with all variables replaced by their
+     * actual values.
+     */
+    @Test
+    public void testInterpolatedConfiguration()
+    {
+        InterpolationTestHelper.testInterpolatedConfiguration(config);
+    }
+
+    @Test
+    public void testGetHexadecimalValue()
+    {
+        config.setProperty("number", "0xFF");
+        assertEquals("byte value", (byte) 0xFF, config.getByte("number"));
+
+        config.setProperty("number", "0xFFFF");
+        assertEquals("short value", (short) 0xFFFF, config.getShort("number"));
+
+        config.setProperty("number", "0xFFFFFFFF");
+        assertEquals("int value", 0xFFFFFFFF, config.getInt("number"));
+
+        config.setProperty("number", "0xFFFFFFFFFFFFFFFF");
+        assertEquals("long value", 0xFFFFFFFFFFFFFFFFL, config.getLong("number"));
+
+        assertEquals("long value", 0xFFFFFFFFFFFFFFFFL, config.getBigInteger("number").longValue());
+    }
+
+    @Test
+    public void testGetBinaryValue()
+    {
+        config.setProperty("number", "0b11111111");
+        assertEquals("byte value", (byte) 0xFF, config.getByte("number"));
+
+        config.setProperty("number", "0b1111111111111111");
+        assertEquals("short value", (short) 0xFFFF, config.getShort("number"));
+
+        config.setProperty("number", "0b11111111111111111111111111111111");
+        assertEquals("int value", 0xFFFFFFFF, config.getInt("number"));
+
+        config.setProperty("number", "0b1111111111111111111111111111111111111111111111111111111111111111");
+        assertEquals("long value", 0xFFFFFFFFFFFFFFFFL, config.getLong("number"));
+
+        assertEquals("long value", 0xFFFFFFFFFFFFFFFFL, config.getBigInteger("number").longValue());
+    }
+
+    @Test
+    public void testResolveContainerStore()
+    {
+        AbstractConfiguration config = new BaseConfiguration();
+
+        // array of objects
+        config.addPropertyDirect("array", new String[] { "foo", "bar" });
+
+        assertEquals("first element of the 'array' property", "foo", config.resolveContainerStore("array"));
+
+        // list of objects
+        List<Object> list = new ArrayList<Object>();
+        list.add("foo");
+        list.add("bar");
+        config.addPropertyDirect("list", list);
+
+        assertEquals("first element of the 'list' property", "foo", config.resolveContainerStore("list"));
+
+        // set of objects
+        Set<Object> set = new LinkedHashSet<Object>();
+        set.add("foo");
+        set.add("bar");
+        config.addPropertyDirect("set", set);
+
+        assertEquals("first element of the 'set' property", "foo", config.resolveContainerStore("set"));
+
+        // arrays of primitives
+        config.addPropertyDirect("array.boolean", new boolean[] { true, false });
+        assertEquals("first element of the 'array.boolean' property", true, config.getBoolean("array.boolean"));
+
+        config.addPropertyDirect("array.byte", new byte[] { 1, 2 });
+        assertEquals("first element of the 'array.byte' property", 1, config.getByte("array.byte"));
+
+        config.addPropertyDirect("array.short", new short[] { 1, 2 });
+        assertEquals("first element of the 'array.short' property", 1, config.getShort("array.short"));
+
+        config.addPropertyDirect("array.int", new int[] { 1, 2 });
+        assertEquals("first element of the 'array.int' property", 1, config.getInt("array.int"));
+
+        config.addPropertyDirect("array.long", new long[] { 1, 2 });
+        assertEquals("first element of the 'array.long' property", 1, config.getLong("array.long"));
+
+        config.addPropertyDirect("array.float", new float[] { 1, 2 });
+        assertEquals("first element of the 'array.float' property", 1, config.getFloat("array.float"), 0);
+
+        config.addPropertyDirect("array.double", new double[] { 1, 2 });
+        assertEquals("first element of the 'array.double' property", 1, config.getDouble("array.double"), 0);
+    }
+
+    /**
+     * Tests if conversion between number types is possible.
+     */
+    @Test
+    public void testNumberConversions()
+    {
+        config.setProperty(KEY_NUMBER, new Integer(42));
+        assertEquals("Wrong int returned", 42, config.getInt(KEY_NUMBER));
+        assertEquals("Wrong long returned", 42L, config.getLong(KEY_NUMBER));
+        assertEquals("Wrong byte returned", (byte) 42, config
+                .getByte(KEY_NUMBER));
+        assertEquals("Wrong float returned", 42.0f,
+                config.getFloat(KEY_NUMBER), 0.01f);
+        assertEquals("Wrong double returned", 42.0, config
+                .getDouble(KEY_NUMBER), 0.001);
+
+        assertEquals("Wrong Long returned", new Long(42L), config.getLong(
+                KEY_NUMBER, null));
+        assertEquals("Wrong BigInt returned", new BigInteger("42"), config
+                .getBigInteger(KEY_NUMBER));
+        assertEquals("Wrong DigDecimal returned", new BigDecimal("42"), config
+                .getBigDecimal(KEY_NUMBER));
+    }
+
+    /**
+     * Tests cloning a BaseConfiguration.
+     */
+    @Test
+    public void testClone()
+    {
+        for (int i = 0; i < 10; i++)
+        {
+            config.addProperty("key" + i, new Integer(i));
+        }
+        BaseConfiguration config2 = (BaseConfiguration) config.clone();
+
+        for (Iterator<String> it = config.getKeys(); it.hasNext();)
+        {
+            String key = it.next();
+            assertTrue("Key not found: " + key, config2.containsKey(key));
+            assertEquals("Wrong value for key " + key, config.getProperty(key),
+                    config2.getProperty(key));
+        }
+    }
+
+    /**
+     * Tests whether a cloned configuration is decoupled from its original.
+     */
+    @Test
+    public void testCloneModify()
+    {
+        ConfigurationListener l = new ConfigurationListener()
+        {
+            public void configurationChanged(ConfigurationEvent event)
+            {
+                // just a dummy
+            }
+        };
+        config.addConfigurationListener(l);
+        config.addProperty("original", Boolean.TRUE);
+        BaseConfiguration config2 = (BaseConfiguration) config.clone();
+
+        config2.addProperty("clone", Boolean.TRUE);
+        assertFalse("New key appears in original", config.containsKey("clone"));
+        config2.setProperty("original", Boolean.FALSE);
+        assertTrue("Wrong value of original property", config
+                .getBoolean("original"));
+
+        assertEquals("Event listener was copied", 0, config2
+                .getConfigurationListeners().size());
+    }
+
+    /**
+     * Tests the clone() method if a list property is involved.
+     */
+    @Test
+    public void testCloneListProperty()
+    {
+        final String key = "list";
+        config.addProperty(key, "value1");
+        config.addProperty(key, "value2");
+        BaseConfiguration config2 = (BaseConfiguration) config.clone();
+        config2.addProperty(key, "value3");
+        assertEquals("Wrong number of original properties", 2, config.getList(
+                key).size());
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestBaseConfigurationXMLReader.java b/src/test/java/org/apache/commons/configuration/TestBaseConfigurationXMLReader.java
new file mode 100644
index 0000000..fb35562
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestBaseConfigurationXMLReader.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Iterator;
+
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.sax.SAXSource;
+
+import org.apache.commons.jxpath.JXPathContext;
+import org.junit.Before;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Test class for BaseConfigurationXMLReader.
+ *
+ * @version $Id: TestBaseConfigurationXMLReader.java 1222835 2011-12-23 20:37:22Z oheger $
+ */
+public class TestBaseConfigurationXMLReader
+{
+    private static final String[] CONTINENTS =
+    {
+        "Africa", "America", "Asia", "Australia", "Europe"
+    };
+
+    private BaseConfiguration config;
+    private BaseConfigurationXMLReader configReader;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        config = new BaseConfiguration();
+        config.addProperty("world.continents.continent", Arrays.asList(CONTINENTS));
+        config.addProperty("world.greeting", "Hello");
+        config.addProperty("world.greeting", "Salute");
+        config.addProperty("world.wish", "Peace");
+        config.addProperty("application.mail.smtp", "smtp.mymail.org");
+        config.addProperty("application.mail.pop", "pop3.mymail.org");
+        config.addProperty("application.mail.account.type", "pop3");
+        config.addProperty("application.mail.account.user", "postmaster");
+        config.addProperty("application.mail.account.pwd", "?.-gulp*#");
+        config.addProperty("application.mail.timeout", new Integer(42));
+        config.addProperty("test", Boolean.TRUE);
+
+        configReader = new BaseConfigurationXMLReader(config);
+    }
+
+    @Test
+    public void testParse() throws Exception
+    {
+        checkDocument(configReader, "config");
+    }
+
+    @Test(expected = SAXException.class)
+    public void testParseSAXException() throws IOException, SAXException
+    {
+        configReader.setContentHandler(new TestContentHandler());
+        configReader.parse("systemID");
+    }
+
+    @Test(expected = IOException.class)
+    public void testParseIOException() throws SAXException, IOException
+    {
+        BaseConfigurationXMLReader reader = new BaseConfigurationXMLReader();
+        reader.parse("document");
+    }
+
+    @Test
+    public void testSetRootName() throws Exception
+    {
+        BaseConfigurationXMLReader reader = new BaseConfigurationXMLReader(config);
+        reader.setRootName("apache");
+        checkDocument(reader, "apache");
+    }
+
+    private void checkDocument(BaseConfigurationXMLReader creader,
+    String rootName) throws Exception
+    {
+        SAXSource source = new SAXSource(creader, new InputSource());
+        DOMResult result = new DOMResult();
+        Transformer trans = TransformerFactory.newInstance().newTransformer();
+        trans.transform(source, result);
+        Node root = ((Document) result.getNode()).getDocumentElement();
+        JXPathContext ctx = JXPathContext.newContext(root);
+
+        assertEquals("Wrong root name", rootName, root.getNodeName());
+        assertEquals("Wrong number of children", 3, ctx.selectNodes("/*").size());
+
+        check(ctx, "world/continents/continent", CONTINENTS);
+        check(ctx, "world/greeting", new String[] { "Hello", "Salute" });
+        check(ctx, "world/wish", "Peace");
+        check(ctx, "application/mail/smtp", "smtp.mymail.org");
+        check(ctx, "application/mail/timeout", "42");
+        check(ctx, "application/mail/account/type", "pop3");
+        check(ctx, "application/mail/account/user", "postmaster");
+        check(ctx, "test", "true");
+    }
+
+    /**
+     * Helper method for checking values in the created document.
+     *
+     * @param ctx the JXPath context
+     * @param path the path to be checked
+     * @param values the expected element values
+     */
+    private void check(JXPathContext ctx, String path, String[] values)
+    {
+        Iterator<?> it = ctx.iterate(path);
+        for (int i = 0; i < values.length; i++)
+        {
+            assertTrue("Too few values", it.hasNext());
+            assertEquals("Wrong property value", values[i], it.next());
+        }
+        assertFalse("Too many values", it.hasNext());
+    }
+
+    private void check(JXPathContext ctx, String path, String value)
+    {
+        check(ctx, path, new String[]
+        { value });
+    }
+
+    // A ContentHandler that raises an exception
+    private static class TestContentHandler extends DefaultHandler
+     {
+        @Override
+        public void characters(char[] ch, int start, int length)
+            throws SAXException
+        {
+            throw new SAXException("Test exception during parsing");
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestBaseNullConfiguration.java b/src/test/java/org/apache/commons/configuration/TestBaseNullConfiguration.java
new file mode 100644
index 0000000..e6e7ab6
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestBaseNullConfiguration.java
@@ -0,0 +1,434 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Properties;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests some basic functions of the BaseConfiguration class. Missing keys might
+ * return null.
+ *
+ * @version $Id: TestBaseNullConfiguration.java 1222842 2011-12-23 20:56:58Z oheger $
+ */
+public class TestBaseNullConfiguration
+{
+    protected BaseConfiguration config;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        config = new BaseConfiguration();
+        config.setThrowExceptionOnMissing(false);
+    }
+
+    @Test
+    public void testThrowExceptionOnMissing()
+    {
+        assertFalse("Throw Exception Property is set!", config.isThrowExceptionOnMissing());
+    }
+
+    @Test
+    public void testGetProperty()
+    {
+        /* should be empty and return null */
+        assertEquals("This returns null", config.getProperty("foo"), null);
+
+        /* add a real value, and get it two different ways */
+        config.setProperty("number", "1");
+        assertEquals("This returns '1'", config.getProperty("number"), "1");
+        assertEquals("This returns '1'", config.getString("number"), "1");
+    }
+
+    @Test
+    public void testGetByte()
+    {
+        config.setProperty("number", "1");
+        byte oneB = 1;
+        byte twoB = 2;
+        assertEquals("This returns 1(byte)", oneB, config.getByte("number"));
+        assertEquals("This returns 1(byte)", oneB, config.getByte("number", twoB));
+        assertEquals("This returns 2(default byte)", twoB, config.getByte("numberNotInConfig", twoB));
+        assertEquals("This returns 1(Byte)", new Byte(oneB), config.getByte("number", new Byte("2")));
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void testGetByteUnknown()
+    {
+        config.getByte("numberNotInConfig");
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testGetByteIncompatibleType()
+    {
+        config.setProperty("test.empty", "");
+        config.getByte("test.empty");
+    }
+
+    @Test
+    public void testGetShort()
+    {
+        config.setProperty("numberS", "1");
+        short oneS = 1;
+        short twoS = 2;
+        assertEquals("This returns 1(short)", oneS, config.getShort("numberS"));
+        assertEquals("This returns 1(short)", oneS, config.getShort("numberS", twoS));
+        assertEquals("This returns 2(default short)", twoS, config.getShort("numberNotInConfig", twoS));
+        assertEquals("This returns 1(Short)", new Short(oneS), config.getShort("numberS", new Short("2")));
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void testGetShortUnknown()
+    {
+        config.getShort("numberNotInConfig");
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testGetShortIncompatibleType()
+    {
+        config.setProperty("test.empty", "");
+        config.getShort("test.empty");
+    }
+
+    @Test
+    public void testGetLong()
+    {
+        config.setProperty("numberL", "1");
+        long oneL = 1;
+        long twoL = 2;
+        assertEquals("This returns 1(long)", oneL, config.getLong("numberL"));
+        assertEquals("This returns 1(long)", oneL, config.getLong("numberL", twoL));
+        assertEquals("This returns 2(default long)", twoL, config.getLong("numberNotInConfig", twoL));
+        assertEquals("This returns 1(Long)", new Long(oneL), config.getLong("numberL", new Long("2")));
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void testGetLongUnknown()
+    {
+        config.getLong("numberNotInConfig");
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testGetLongIncompatibleTypes()
+    {
+        config.setProperty("test.empty", "");
+        config.getLong("test.empty");
+    }
+
+    @Test
+    public void testGetFloat()
+    {
+        config.setProperty("numberF", "1.0");
+        float oneF = 1;
+        float twoF = 2;
+        assertEquals("This returns 1(float)", oneF, config.getFloat("numberF"), 0);
+        assertEquals("This returns 1(float)", oneF, config.getFloat("numberF", twoF), 0);
+        assertEquals("This returns 2(default float)", twoF, config.getFloat("numberNotInConfig", twoF), 0);
+        assertEquals("This returns 1(Float)", new Float(oneF), config.getFloat("numberF", new Float("2")));
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void testGetFloatUnknown()
+    {
+        config.getFloat("numberNotInConfig");
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testGetFloatIncompatibleType()
+    {
+        config.setProperty("test.empty", "");
+        config.getFloat("test.empty");
+    }
+
+    @Test
+    public void testGetDouble()
+    {
+        config.setProperty("numberD", "1.0");
+        double oneD = 1;
+        double twoD = 2;
+        assertEquals("This returns 1(double)", oneD, config.getDouble("numberD"), 0);
+        assertEquals("This returns 1(double)", oneD, config.getDouble("numberD", twoD), 0);
+        assertEquals("This returns 2(default double)", twoD, config.getDouble("numberNotInConfig", twoD), 0);
+        assertEquals("This returns 1(Double)", new Double(oneD), config.getDouble("numberD", new Double("2")));
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void testGetDoubleUnknown()
+    {
+        config.getDouble("numberNotInConfig");
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testGetDoubleIncompatibleType()
+    {
+        config.setProperty("test.empty", "");
+        config.getDouble("test.empty");
+    }
+
+    @Test
+    public void testGetBigDecimal()
+    {
+        config.setProperty("numberBigD", "123.456");
+        BigDecimal number = new BigDecimal("123.456");
+        BigDecimal defaultValue = new BigDecimal("654.321");
+
+        assertEquals("Existing key", number, config.getBigDecimal("numberBigD"));
+        assertEquals("Existing key with default value", number, config.getBigDecimal("numberBigD", defaultValue));
+        assertEquals("Missing key with default value", defaultValue, config.getBigDecimal("numberNotInConfig", defaultValue));
+    }
+
+    @Test
+    public void testGetBigDecimalUnknown()
+    {
+        assertNull("Missing Key is not null!", config.getBigDecimal("numberNotInConfig"));
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testGetBigDecimalIncompatibleType()
+    {
+        config.setProperty("test.empty", "");
+        config.getBigDecimal("test.empty");
+    }
+
+    @Test
+    public void testGetBigInteger()
+    {
+        config.setProperty("numberBigI", "1234567890");
+        BigInteger number = new BigInteger("1234567890");
+        BigInteger defaultValue = new BigInteger("654321");
+
+        assertEquals("Existing key", number, config.getBigInteger("numberBigI"));
+        assertEquals("Existing key with default value", number, config.getBigInteger("numberBigI", defaultValue));
+        assertEquals("Missing key with default value", defaultValue, config.getBigInteger("numberNotInConfig", defaultValue));
+    }
+
+    @Test
+    public void testGetBigIntegerUnknown()
+    {
+        assertNull("Missing Key is not null!", config.getBigInteger("numberNotInConfig"));
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testGetBigIntegerIncompatibleType()
+    {
+        config.setProperty("test.empty", "");
+        config.getBigInteger("test.empty");
+    }
+
+
+    @Test
+    public void testGetString()
+    {
+        config.setProperty("testString", "The quick brown fox");
+        String string = new String("The quick brown fox");
+        String defaultValue = new String("jumps over the lazy dog");
+
+        assertEquals("Existing key", string, config.getString("testString"));
+        assertEquals("Existing key with default value", string, config.getString("testString", defaultValue));
+        assertEquals("Missing key with default value", defaultValue, config.getString("stringNotInConfig", defaultValue));
+    }
+
+    @Test
+    public void testGetStringUnknown()
+    {
+        assertNull("Missing Key is not null!", config.getString("stringNotInConfig"));
+    }
+
+    @Test
+    public void testGetBoolean()
+    {
+        config.setProperty("boolA", Boolean.TRUE);
+        boolean boolT = true, boolF = false;
+        assertEquals("This returns true", boolT, config.getBoolean("boolA"));
+        assertEquals("This returns true, not the default", boolT, config.getBoolean("boolA", boolF));
+        assertEquals("This returns false(default)", boolF, config.getBoolean("boolNotInConfig", boolF));
+        assertEquals("This returns true(Boolean)", new Boolean(boolT), config.getBoolean("boolA", new Boolean(boolF)));
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void testGetBooleanUnknown()
+    {
+        config.getBoolean("numberNotInConfig");
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testGetBooleanIncompatibleType()
+    {
+        config.setProperty("test.empty", "");
+        config.getBoolean("test.empty");
+    }
+
+    @Test
+    public void testGetList()
+    {
+        config.addProperty("number", "1");
+        config.addProperty("number", "2");
+        List<Object> list = config.getList("number");
+        assertNotNull("The list is null", list);
+        assertEquals("List size", 2, list.size());
+        assertTrue("The number 1 is missing from the list", list.contains("1"));
+        assertTrue("The number 2 is missing from the list", list.contains("2"));
+    }
+
+    @Test
+    public void testGetListAsScalar()
+    {
+        config.addProperty("number", "1");
+        config.addProperty("number", "2");
+        assertEquals("Wrong value", "1", config.getString("number"));
+    }
+
+    @Test
+    public void testCommaSeparatedString()
+    {
+        String prop = "hey, that's a test";
+        config.setProperty("prop.string", prop);
+        List<Object> list = config.getList("prop.string");
+        assertEquals("Wrong number of elements", 2, list.size());
+        assertEquals("Wrong element 1", "hey", list.get(0));
+    }
+
+    @Test
+    public void testCommaSeparatedStringEscaped()
+    {
+        String prop2 = "hey\\, that's a test";
+        config.clearProperty("prop.string");
+        config.setProperty("prop.string", prop2);
+        assertEquals("Wrong value", "hey, that's a test", config.getString("prop.string"));
+    }
+
+    @Test
+    public void testPropertyAccess()
+    {
+        config.clearProperty("prop.properties");
+        config.setProperty("prop.properties", "");
+        assertEquals(
+            "This returns an empty Properties object",
+            config.getProperties("prop.properties"),
+            new Properties());
+        config.clearProperty("prop.properties");
+        config.setProperty("prop.properties", "foo=bar, baz=moo, seal=clubber");
+
+        Properties p = new Properties();
+        p.setProperty("foo", "bar");
+        p.setProperty("baz", "moo");
+        p.setProperty("seal", "clubber");
+        assertEquals(
+            "This returns a filled in Properties object",
+            config.getProperties("prop.properties"),
+            p);
+    }
+
+    @Test
+    public void testSubset()
+    {
+        /*
+         * test subset : assure we don't reprocess the data elements
+         * when generating the subset
+         */
+
+        String prop = "hey, that's a test";
+        String prop2 = "hey\\, that's a test";
+        config.setProperty("prop.string", prop2);
+        config.setProperty("property.string", "hello");
+
+        Configuration subEprop = config.subset("prop");
+
+        assertEquals(
+            "Returns the full string",
+            prop,
+            subEprop.getString("string"));
+        assertEquals("Wrong list size", 1, subEprop.getList("string").size());
+
+        Iterator<String> it = subEprop.getKeys();
+        it.next();
+        assertFalse(it.hasNext());
+
+        subEprop = config.subset("prop.");
+        it = subEprop.getKeys();
+        assertFalse(it.hasNext());
+    }
+
+    @Test
+    public void testInterpolation() throws Exception
+    {
+        config.setProperty("applicationRoot", "/home/applicationRoot");
+        config.setProperty("db", "${applicationRoot}/db/hypersonic");
+        String unInterpolatedValue = "${applicationRoot2}/db/hypersonic";
+        config.setProperty("dbFailedInterpolate", unInterpolatedValue);
+        String dbProp = "/home/applicationRoot/db/hypersonic";
+
+        //construct a new config, using config as the defaults config for it.
+        BaseConfiguration superProp = config;
+
+        assertEquals(
+            "Checking interpolated variable",dbProp,
+            superProp.getString("db"));
+        assertEquals(
+            "lookup fails, leave variable as is",
+            superProp.getString("dbFailedInterpolate"),
+            unInterpolatedValue);
+
+        superProp.setProperty("arrayInt", "${applicationRoot}/1");
+        String[] arrayInt = superProp.getStringArray("arrayInt");
+        assertEquals(
+            "check first entry was interpolated",
+            "/home/applicationRoot/1",
+            arrayInt[0]);
+    }
+
+    @Test
+    public void testMultipleInterpolation() throws Exception
+    {
+        config.setProperty("test.base-level", "/base-level");
+        config.setProperty("test.first-level", "${test.base-level}/first-level");
+        config.setProperty(
+            "test.second-level",
+            "${test.first-level}/second-level");
+        config.setProperty(
+            "test.third-level",
+            "${test.second-level}/third-level");
+
+        String expectedValue =
+            "/base-level/first-level/second-level/third-level";
+
+        assertEquals(config.getString("test.third-level"), expectedValue);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testInterpolationLoop() throws Exception
+    {
+        config.setProperty("test.a", "${test.b}");
+        config.setProperty("test.b", "${test.a}");
+        config.getString("test.a");
+    }
+}
+
diff --git a/src/test/java/org/apache/commons/configuration/TestCatalogResolver.java b/src/test/java/org/apache/commons/configuration/TestCatalogResolver.java
new file mode 100644
index 0000000..3350d4a
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestCatalogResolver.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.commons.configuration.resolver.CatalogResolver;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for CatalogResolver.
+ *
+ * @version $Id: TestCatalogResolver.java 1223006 2011-12-24 19:41:41Z oheger $
+ */
+public class TestCatalogResolver
+{
+    private static final String CATALOG_FILES = "catalog.xml";
+    private static final String PUBLIC_FILE = "testResolver.xml";
+    private static final String REWRITE_SYSTEM_FILE = "test.properties.xml";
+    private static final String REWRITE_SCHEMA_FILE = "sample.xml";
+
+    private CatalogResolver resolver;
+    private XMLConfiguration config;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        resolver = new CatalogResolver();
+        resolver.setCatalogFiles(CATALOG_FILES);
+        // resolver.setDebug(true);
+        config = new XMLConfiguration();
+        config.setEntityResolver(resolver);
+    }
+
+    @After
+    public void tearDown() throws Exception
+    {
+        resolver = null;
+        config = null;
+    }
+
+    @Test
+    public void testPublic() throws Exception
+    {
+        config.setFileName(PUBLIC_FILE);
+        config.load();
+    }
+
+    @Test
+    public void testRewriteSystem() throws Exception
+    {
+        config.setFileName(REWRITE_SYSTEM_FILE);
+        config.load();
+    }
+
+    /**
+     * Tests that the schema can be resolved and that XMLConfiguration will
+     * validate the file using the schema.
+     * @throws Exception
+     */
+    @Test
+    public void testSchemaResolver() throws Exception
+    {
+        config.setFileName(REWRITE_SCHEMA_FILE);
+        config.setSchemaValidation(true);
+        config.load();
+    }
+
+    @Test
+    public void testDebug() throws Exception
+    {
+        resolver.setDebug(true);
+        // There is no really good way to check this except to do something
+        // that causes debug output.
+    }
+
+    @Test
+    public void testLogger() throws Exception
+    {
+        Log log = LogFactory.getLog(this.getClass());
+        resolver.setLogger(log);
+        assertNotNull("No Logger returned", resolver.getLogger());
+        assertTrue("Incorrect Logger", log == resolver.getLogger());
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestCombinedConfiguration.java b/src/test/java/org/apache/commons/configuration/TestCombinedConfiguration.java
new file mode 100644
index 0000000..79b32a9
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestCombinedConfiguration.java
@@ -0,0 +1,999 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.text.MessageFormat;
+import java.util.Collection;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import junit.framework.Assert;
+
+import org.apache.commons.configuration.event.ConfigurationEvent;
+import org.apache.commons.configuration.event.ConfigurationListener;
+import org.apache.commons.configuration.reloading.FileAlwaysReloadingStrategy;
+import org.apache.commons.configuration.reloading.FileRandomReloadingStrategy;
+import org.apache.commons.configuration.tree.DefaultExpressionEngine;
+import org.apache.commons.configuration.tree.MergeCombiner;
+import org.apache.commons.configuration.tree.NodeCombiner;
+import org.apache.commons.configuration.tree.OverrideCombiner;
+import org.apache.commons.configuration.tree.UnionCombiner;
+import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Test class for CombinedConfiguration.
+ *
+ * @version $Id: TestCombinedConfiguration.java 1327061 2012-04-17 12:18:27Z rgoers $
+ */
+public class TestCombinedConfiguration
+{
+    /** Constant for the name of a sub configuration. */
+    private static final String TEST_NAME = "SUBCONFIG";
+
+    /** Constant for a test key. */
+    private static final String TEST_KEY = "test.value";
+
+    /** Constant for the name of the first child configuration.*/
+    private static final String CHILD1 = TEST_NAME + "1";
+
+    /** Constant for the name of the second child configuration.*/
+    private static final String CHILD2 = TEST_NAME + "2";
+
+    /** Constant for the name of the XML reload test file.*/
+    private static final String RELOAD_XML_NAME = "reload.xml";
+
+    /** Constant for the content of a XML reload test file.*/
+    private static final String RELOAD_XML_CONTENT = "<xml><xmlReload>{0}</xmlReload></xml>";
+
+    /** Constant for the name of the properties reload test file.*/
+    private static final String RELOAD_PROPS_NAME = "reload.properties";
+
+    /** Constant for the content of a properties reload test file.*/
+    private static final String RELOAD_PROPS_CONTENT = "propsReload = {0}";
+
+    /** Helper object for managing temporary files. */
+    @Rule
+    public TemporaryFolder folder = new TemporaryFolder();
+
+    /** The configuration to be tested. */
+    private CombinedConfiguration config;
+
+    /** The test event listener. */
+    private CombinedListener listener;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        config = new CombinedConfiguration();
+        listener = new CombinedListener();
+        config.addConfigurationListener(listener);
+    }
+
+    /**
+     * Tests accessing a newly created combined configuration.
+     */
+    @Test
+    public void testInit()
+    {
+        assertEquals("Already configurations contained", 0, config
+                .getNumberOfConfigurations());
+        assertTrue("Set of names is not empty", config.getConfigurationNames()
+                .isEmpty());
+        assertTrue("Wrong node combiner",
+                config.getNodeCombiner() instanceof UnionCombiner);
+        assertNull("Test config was found", config.getConfiguration(TEST_NAME));
+        assertFalse("Force reload check flag is set", config.isForceReloadCheck());
+    }
+
+    /**
+     * Tests adding a configuration (without further information).
+     */
+    @Test
+    public void testAddConfiguration()
+    {
+        AbstractConfiguration c = setUpTestConfiguration();
+        config.addConfiguration(c);
+        checkAddConfig(c);
+        assertEquals("Wrong number of configs", 1, config
+                .getNumberOfConfigurations());
+        assertTrue("Name list is not empty", config.getConfigurationNames()
+                .isEmpty());
+        assertSame("Added config not found", c, config.getConfiguration(0));
+        assertTrue("Wrong property value", config.getBoolean(TEST_KEY));
+        listener.checkEvent(1, 0);
+    }
+
+    /**
+     * Tests adding a configuration with a name.
+     */
+    @Test
+    public void testAddConfigurationWithName()
+    {
+        AbstractConfiguration c = setUpTestConfiguration();
+        config.addConfiguration(c, TEST_NAME);
+        checkAddConfig(c);
+        assertEquals("Wrong number of configs", 1, config
+                .getNumberOfConfigurations());
+        assertSame("Added config not found", c, config.getConfiguration(0));
+        assertSame("Added config not found by name", c, config
+                .getConfiguration(TEST_NAME));
+        Set<String> names = config.getConfigurationNames();
+        assertEquals("Wrong number of config names", 1, names.size());
+        assertTrue("Name not found", names.contains(TEST_NAME));
+        assertTrue("Wrong property value", config.getBoolean(TEST_KEY));
+        listener.checkEvent(1, 0);
+    }
+
+    /**
+     * Tests adding a configuration with a name when this name already exists.
+     * This should cause an exception.
+     */
+    @Test(expected = ConfigurationRuntimeException.class)
+    public void testAddConfigurationWithNameTwice()
+    {
+        config.addConfiguration(setUpTestConfiguration(), TEST_NAME);
+        config.addConfiguration(setUpTestConfiguration(), TEST_NAME,
+                "prefix");
+    }
+
+    /**
+     * Tests adding a configuration and specifying an at position.
+     */
+    @Test
+    public void testAddConfigurationAt()
+    {
+        AbstractConfiguration c = setUpTestConfiguration();
+        config.addConfiguration(c, null, "my");
+        checkAddConfig(c);
+        assertTrue("Wrong property value", config.getBoolean("my." + TEST_KEY));
+    }
+
+    /**
+     * Tests adding a configuration with a complex at position. Here the at path
+     * contains a dot, which must be escaped.
+     */
+    @Test
+    public void testAddConfigurationComplexAt()
+    {
+        AbstractConfiguration c = setUpTestConfiguration();
+        config.addConfiguration(c, null, "This..is.a.complex");
+        checkAddConfig(c);
+        assertTrue("Wrong property value", config
+                .getBoolean("This..is.a.complex." + TEST_KEY));
+    }
+
+    /**
+     * Checks if a configuration was correctly added to the combined config.
+     *
+     * @param c the config to check
+     */
+    private void checkAddConfig(AbstractConfiguration c)
+    {
+        Collection<ConfigurationListener> listeners = c.getConfigurationListeners();
+        assertEquals("Wrong number of configuration listeners", 1, listeners
+                .size());
+        assertTrue("Combined config is no listener", listeners.contains(config));
+    }
+
+    /**
+     * Tests adding a null configuration. This should cause an exception to be
+     * thrown.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testAddNullConfiguration()
+    {
+        config.addConfiguration(null);
+    }
+
+    /**
+     * Tests accessing properties if no configurations have been added.
+     */
+    @Test
+    public void testAccessPropertyEmpty()
+    {
+        assertFalse("Found a key", config.containsKey(TEST_KEY));
+        assertNull("Key has a value", config.getString("test.comment"));
+        assertTrue("Config is not empty", config.isEmpty());
+    }
+
+    /**
+     * Tests accessing properties if multiple configurations have been added.
+     */
+    @Test
+    public void testAccessPropertyMulti()
+    {
+        config.addConfiguration(setUpTestConfiguration());
+        config.addConfiguration(setUpTestConfiguration(), null, "prefix1");
+        config.addConfiguration(setUpTestConfiguration(), null, "prefix2");
+        assertTrue("Prop1 not found", config.getBoolean(TEST_KEY));
+        assertTrue("Prop 2 not found", config.getBoolean("prefix1." + TEST_KEY));
+        assertTrue("Prop 3 not found", config.getBoolean("prefix2." + TEST_KEY));
+        assertFalse("Configuration is empty", config.isEmpty());
+        listener.checkEvent(3, 0);
+    }
+
+    /**
+     * Tests removing a configuration.
+     */
+    @Test
+    public void testRemoveConfiguration()
+    {
+        AbstractConfiguration c = setUpTestConfiguration();
+        config.addConfiguration(c);
+        checkAddConfig(c);
+        assertTrue("Config could not be removed", config.removeConfiguration(c));
+        checkRemoveConfig(c);
+    }
+
+    /**
+     * Tests removing a configuration by index.
+     */
+    @Test
+    public void testRemoveConfigurationAt()
+    {
+        AbstractConfiguration c = setUpTestConfiguration();
+        config.addConfiguration(c);
+        assertSame("Wrong config removed", c, config.removeConfigurationAt(0));
+        checkRemoveConfig(c);
+    }
+
+    /**
+     * Tests removing a configuration by name.
+     */
+    @Test
+    public void testRemoveConfigurationByName()
+    {
+        AbstractConfiguration c = setUpTestConfiguration();
+        config.addConfiguration(c, TEST_NAME);
+        assertSame("Wrong config removed", c, config
+                .removeConfiguration(TEST_NAME));
+        checkRemoveConfig(c);
+    }
+
+    /**
+     * Tests removing a configuration with a name.
+     */
+    @Test
+    public void testRemoveNamedConfiguration()
+    {
+        AbstractConfiguration c = setUpTestConfiguration();
+        config.addConfiguration(c, TEST_NAME);
+        config.removeConfiguration(c);
+        checkRemoveConfig(c);
+    }
+
+    /**
+     * Tests removing a named configuration by index.
+     */
+    @Test
+    public void testRemoveNamedConfigurationAt()
+    {
+        AbstractConfiguration c = setUpTestConfiguration();
+        config.addConfiguration(c, TEST_NAME);
+        assertSame("Wrong config removed", c, config.removeConfigurationAt(0));
+        checkRemoveConfig(c);
+    }
+
+    /**
+     * Tests removing a configuration that was not added prior.
+     */
+    @Test
+    public void testRemoveNonContainedConfiguration()
+    {
+        assertFalse("Could remove non contained config", config
+                .removeConfiguration(setUpTestConfiguration()));
+        listener.checkEvent(0, 0);
+    }
+
+    /**
+     * Tests removing a configuration by name, which is not contained.
+     */
+    @Test
+    public void testRemoveConfigurationByUnknownName()
+    {
+        assertNull("Could remove configuration by unknown name", config
+                .removeConfiguration("unknownName"));
+        listener.checkEvent(0, 0);
+    }
+
+    /**
+     * Tests whether a configuration was completely removed.
+     *
+     * @param c the removed configuration
+     */
+    private void checkRemoveConfig(AbstractConfiguration c)
+    {
+        assertTrue("Listener was not removed", c.getConfigurationListeners()
+                .isEmpty());
+        assertEquals("Wrong number of contained configs", 0, config
+                .getNumberOfConfigurations());
+        assertTrue("Name was not removed", config.getConfigurationNames()
+                .isEmpty());
+        listener.checkEvent(2, 0);
+    }
+
+    /**
+     * Tests if an update of a contained configuration leeds to an invalidation
+     * of the combined configuration.
+     */
+    @Test
+    public void testUpdateContainedConfiguration()
+    {
+        AbstractConfiguration c = setUpTestConfiguration();
+        config.addConfiguration(c);
+        c.addProperty("test.otherTest", "yes");
+        assertEquals("New property not found", "yes", config
+                .getString("test.otherTest"));
+        listener.checkEvent(2, 0);
+    }
+
+    /**
+     * Tests if setting a node combiner causes an invalidation.
+     */
+    @Test
+    public void testSetNodeCombiner()
+    {
+        NodeCombiner combiner = new UnionCombiner();
+        config.setNodeCombiner(combiner);
+        assertSame("Node combiner was not set", combiner, config
+                .getNodeCombiner());
+        listener.checkEvent(1, 0);
+    }
+
+    /**
+     * Tests setting a null node combiner. This should cause an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetNullNodeCombiner()
+    {
+        config.setNodeCombiner(null);
+    }
+
+    /**
+     * Tests cloning a combined configuration.
+     */
+    @Test
+    public void testClone()
+    {
+        config.addConfiguration(setUpTestConfiguration());
+        config.addConfiguration(setUpTestConfiguration(), TEST_NAME, "conf2");
+        config.addConfiguration(new PropertiesConfiguration(), "props");
+
+        CombinedConfiguration cc2 = (CombinedConfiguration) config.clone();
+        assertEquals("Wrong number of contained configurations", config
+                .getNumberOfConfigurations(), cc2.getNumberOfConfigurations());
+        assertSame("Wrong node combiner", config.getNodeCombiner(), cc2
+                .getNodeCombiner());
+        assertEquals("Wrong number of names", config.getConfigurationNames()
+                .size(), cc2.getConfigurationNames().size());
+        assertTrue("Event listeners were cloned", cc2
+                .getConfigurationListeners().isEmpty());
+
+        StrictConfigurationComparator comp = new StrictConfigurationComparator();
+        for (int i = 0; i < config.getNumberOfConfigurations(); i++)
+        {
+            assertNotSame("Configuration at " + i + " was not cloned", config
+                    .getConfiguration(i), cc2.getConfiguration(i));
+            assertEquals("Wrong config class at " + i, config.getConfiguration(
+                    i).getClass(), cc2.getConfiguration(i).getClass());
+            assertTrue("Configs not equal at " + i, comp.compare(config
+                    .getConfiguration(i), cc2.getConfiguration(i)));
+        }
+
+        assertTrue("Combined configs not equal", comp.compare(config, cc2));
+    }
+
+    /**
+     * Tests if the cloned configuration is decoupled from the original.
+     */
+    @Test
+    public void testCloneModify()
+    {
+        config.addConfiguration(setUpTestConfiguration(), TEST_NAME);
+        CombinedConfiguration cc2 = (CombinedConfiguration) config.clone();
+        assertTrue("Name is missing", cc2.getConfigurationNames().contains(
+                TEST_NAME));
+        cc2.removeConfiguration(TEST_NAME);
+        assertFalse("Names in original changed", config.getConfigurationNames()
+                .isEmpty());
+    }
+
+    /**
+     * Tests clearing a combined configuration. This should remove all contained
+     * configurations.
+     */
+    @Test
+    public void testClear()
+    {
+        config.addConfiguration(setUpTestConfiguration(), TEST_NAME, "test");
+        config.addConfiguration(setUpTestConfiguration());
+
+        config.clear();
+        assertEquals("Still configs contained", 0, config
+                .getNumberOfConfigurations());
+        assertTrue("Still names contained", config.getConfigurationNames()
+                .isEmpty());
+        assertTrue("Config is not empty", config.isEmpty());
+
+        listener.checkEvent(3, 2);
+    }
+
+    /**
+     * Tests if file-based configurations can be reloaded.
+     */
+    @Test
+    public void testReloading() throws Exception
+    {
+        config.setForceReloadCheck(true);
+        File testXmlFile = writeReloadFile(RELOAD_XML_NAME, RELOAD_XML_CONTENT, 0);
+        File testPropsFile = writeReloadFile(RELOAD_PROPS_NAME, RELOAD_PROPS_CONTENT, 0);
+        XMLConfiguration c1 = new XMLConfiguration(testXmlFile);
+        c1.setReloadingStrategy(new FileAlwaysReloadingStrategy());
+        PropertiesConfiguration c2 = new PropertiesConfiguration(testPropsFile);
+        c2.setThrowExceptionOnMissing(true);
+        c2.setReloadingStrategy(new FileAlwaysReloadingStrategy());
+        config.addConfiguration(c1);
+        config.addConfiguration(c2);
+        assertEquals("Wrong xml reload value", 0, config.getInt("xmlReload"));
+        assertEquals("Wrong props reload value", 0, config
+                .getInt("propsReload"));
+
+        writeReloadFile(RELOAD_XML_NAME, RELOAD_XML_CONTENT, 1);
+        assertEquals("XML reload not detected", 1, config.getInt("xmlReload"));
+        config.setForceReloadCheck(false);
+        writeReloadFile(RELOAD_PROPS_NAME, RELOAD_PROPS_CONTENT, 1);
+        assertEquals("Props reload detected though check flag is false", 0, config
+                .getInt("propsReload"));
+    }
+
+    /**
+     * Tests whether the reload check works with a subnode configuration. This
+     * test is related to CONFIGURATION-341.
+     */
+    @Test
+    public void testReloadingSubnodeConfig() throws IOException,
+            ConfigurationException
+    {
+        config.setForceReloadCheck(true);
+        File testXmlFile = writeReloadFile(RELOAD_XML_NAME, RELOAD_XML_CONTENT,
+                0);
+        XMLConfiguration c1 = new XMLConfiguration(testXmlFile);
+        c1.setReloadingStrategy(new FileAlwaysReloadingStrategy());
+        final String prefix = "reloadCheck";
+        config.addConfiguration(c1, CHILD1, prefix);
+        SubnodeConfiguration sub = config.configurationAt(prefix, true);
+        writeReloadFile(RELOAD_XML_NAME, RELOAD_XML_CONTENT, 1);
+        assertEquals("Reload not detected", 1, sub.getInt("xmlReload"));
+    }
+
+    /**
+     * Tests whether reloading works for a combined configuration nested in
+     * another combined configuration.
+     */
+    @Test
+    public void testReloadingNestedCC() throws IOException,
+            ConfigurationException
+    {
+        config.setForceReloadCheck(true);
+        File testXmlFile =
+                writeReloadFile(RELOAD_XML_NAME, RELOAD_XML_CONTENT, 0);
+        File testPropsFile =
+                writeReloadFile(RELOAD_PROPS_NAME, RELOAD_PROPS_CONTENT, 0);
+        XMLConfiguration c1 = new XMLConfiguration(testXmlFile);
+        c1.setReloadingStrategy(new FileAlwaysReloadingStrategy());
+        PropertiesConfiguration c2 = new PropertiesConfiguration(testPropsFile);
+        c2.setReloadingStrategy(new FileAlwaysReloadingStrategy());
+        config.addConfiguration(c2);
+        CombinedConfiguration cc2 = new CombinedConfiguration();
+        cc2.setForceReloadCheck(true);
+        cc2.addConfiguration(c1);
+        config.addConfiguration(cc2);
+        assertEquals("Wrong xml reload value", 0, config.getInt("xmlReload"));
+        writeReloadFile(RELOAD_XML_NAME, RELOAD_XML_CONTENT, 1);
+        assertEquals("XML reload not detected", 1, config.getInt("xmlReload"));
+    }
+
+    /**
+     * Prepares a test of the getSource() method.
+     */
+    private void setUpSourceTest()
+    {
+        HierarchicalConfiguration c1 = new HierarchicalConfiguration();
+        PropertiesConfiguration c2 = new PropertiesConfiguration();
+        c1.addProperty(TEST_KEY, TEST_NAME);
+        c2.addProperty("another.key", "test");
+        config.addConfiguration(c1, CHILD1);
+        config.addConfiguration(c2, CHILD2);
+    }
+
+    /**
+     * Tests the gestSource() method when the source property is defined in a
+     * hierarchical configuration.
+     */
+    @Test
+    public void testGetSourceHierarchical()
+    {
+        setUpSourceTest();
+        assertEquals("Wrong source configuration", config
+                .getConfiguration(CHILD1), config.getSource(TEST_KEY));
+    }
+
+    /**
+     * Tests whether the source configuration can be detected for non
+     * hierarchical configurations.
+     */
+    @Test
+    public void testGetSourceNonHierarchical()
+    {
+        setUpSourceTest();
+        assertEquals("Wrong source configuration", config
+                .getConfiguration(CHILD2), config.getSource("another.key"));
+    }
+
+    /**
+     * Tests the getSource() method when the passed in key is not contained.
+     * Result should be null in this case.
+     */
+    @Test
+    public void testGetSourceUnknown()
+    {
+        setUpSourceTest();
+        assertNull("Wrong result for unknown key", config
+                .getSource("an.unknown.key"));
+    }
+
+    /**
+     * Tests the getSource() method when a null key is passed in. This should
+     * cause an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetSourceNull()
+    {
+        config.getSource(null);
+    }
+
+    /**
+     * Tests the getSource() method when the passed in key belongs to the
+     * combined configuration itself.
+     */
+    @Test
+    public void testGetSourceCombined()
+    {
+        setUpSourceTest();
+        final String key = "yet.another.key";
+        config.addProperty(key, Boolean.TRUE);
+        assertEquals("Wrong source for key", config, config.getSource(key));
+    }
+
+    /**
+     * Tests the getSource() method when the passed in key refers to multiple
+     * values, which are all defined in the same source configuration.
+     */
+    @Test
+    public void testGetSourceMulti()
+    {
+        setUpSourceTest();
+        final String key = "list.key";
+        config.getConfiguration(CHILD1).addProperty(key, "1,2,3");
+        assertEquals("Wrong source for multi-value property", config
+                .getConfiguration(CHILD1), config.getSource(key));
+    }
+
+    /**
+     * Tests the getSource() method when the passed in key refers to multiple
+     * values defined by different sources. This should cause an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetSourceMultiSources()
+    {
+        setUpSourceTest();
+        final String key = "list.key";
+        config.getConfiguration(CHILD1).addProperty(key, "1,2,3");
+        config.getConfiguration(CHILD2).addProperty(key, "a,b,c");
+        config.getSource(key);
+    }
+
+    /**
+     * Tests whether escaped list delimiters are treated correctly.
+     */
+    @Test
+    public void testEscapeListDelimiters()
+    {
+        PropertiesConfiguration sub = new PropertiesConfiguration();
+        sub.addProperty("test.pi", "3\\,1415");
+        config.addConfiguration(sub);
+        assertEquals("Wrong value", "3,1415", config.getString("test.pi"));
+    }
+
+    /**
+     * Tests whether an invalidate event is fired only after a change. This test
+     * is related to CONFIGURATION-315.
+     */
+    @Test
+    public void testInvalidateAfterChange()
+    {
+        ConfigurationEvent event = new ConfigurationEvent(config, 0, null,
+                null, true);
+        config.configurationChanged(event);
+        assertEquals("Invalidate event fired", 0, listener.invalidateEvents);
+        event = new ConfigurationEvent(config, 0, null, null, false);
+        config.configurationChanged(event);
+        assertEquals("No invalidate event fired", 1, listener.invalidateEvents);
+    }
+
+    /**
+     * Tests using a conversion expression engine for child configurations with
+     * strange keys. This test is related to CONFIGURATION-336.
+     */
+    @Test
+    public void testConversionExpressionEngine()
+    {
+        PropertiesConfiguration child = new PropertiesConfiguration();
+        child.addProperty("test(a)", "1,2,3");
+        config.addConfiguration(child);
+        DefaultExpressionEngine engineQuery = new DefaultExpressionEngine();
+        engineQuery.setIndexStart("<");
+        engineQuery.setIndexEnd(">");
+        config.setExpressionEngine(engineQuery);
+        DefaultExpressionEngine engineConvert = new DefaultExpressionEngine();
+        engineConvert.setIndexStart("[");
+        engineConvert.setIndexEnd("]");
+        config.setConversionExpressionEngine(engineConvert);
+        assertEquals("Wrong property 1", "1", config.getString("test(a)<0>"));
+        assertEquals("Wrong property 2", "2", config.getString("test(a)<1>"));
+        assertEquals("Wrong property 3", "3", config.getString("test(a)<2>"));
+    }
+
+    /**
+     * Tests whether reload operations can cause a deadlock when the combined
+     * configuration is accessed concurrently. This test is related to
+     * CONFIGURATION-344.
+     */
+    @Test
+    public void testDeadlockWithReload() throws ConfigurationException,
+            InterruptedException
+    {
+        final PropertiesConfiguration child = new PropertiesConfiguration(
+                "test.properties");
+        child.setReloadingStrategy(new FileAlwaysReloadingStrategy());
+        config.addConfiguration(child);
+        final int count = 1000;
+
+        class TestDeadlockReloadThread extends Thread
+        {
+            boolean error = false;
+
+            @Override
+            public void run()
+            {
+                for (int i = 0; i < count && !error; i++)
+                {
+                    try
+                    {
+                        if (!child.getBoolean("configuration.loaded"))
+                        {
+                            error = true;
+                        }
+                    }
+                    catch (NoSuchElementException nsex)
+                    {
+                        error = true;
+                    }
+                }
+            }
+        }
+
+        TestDeadlockReloadThread reloadThread = new TestDeadlockReloadThread();
+        reloadThread.start();
+        for (int i = 0; i < count; i++)
+        {
+            assertEquals("Wrong value of combined property", 10, config
+                    .getInt("test.integer"));
+        }
+        reloadThread.join();
+        assertFalse("Failure in thread", reloadThread.error);
+    }
+
+    @Test
+    public void testGetConfigurations() throws Exception
+    {
+        config.addConfiguration(setUpTestConfiguration());
+        config.addConfiguration(setUpTestConfiguration(), TEST_NAME, "conf2");
+        AbstractConfiguration pc = new PropertiesConfiguration();
+        config.addConfiguration(pc, "props");
+        List<AbstractConfiguration> list = config.getConfigurations();
+        assertNotNull("No list of configurations returned", list);
+        assertTrue("Incorrect number of configurations", list.size() == 3);
+        AbstractConfiguration c = list.get(2);
+        assertTrue("Incorrect configuration", c == pc);
+    }
+
+    @Test
+    public void testGetConfigurationNameList() throws Exception
+    {
+        config.addConfiguration(setUpTestConfiguration());
+        config.addConfiguration(setUpTestConfiguration(), TEST_NAME, "conf2");
+        AbstractConfiguration pc = new PropertiesConfiguration();
+        config.addConfiguration(pc, "props");
+        List<String> list = config.getConfigurationNameList();
+        assertNotNull("No list of configurations returned", list);
+        assertTrue("Incorrect number of configurations", list.size() == 3);
+        String name = list.get(1);
+        assertNotNull("No name returned", name);
+        assertTrue("Incorrect configuration name", TEST_NAME.equals(name));
+    }
+
+    /**
+     * Tests whether changes on a sub node configuration that is part of a
+     * combined configuration are detected. This test is related to
+     * CONFIGURATION-368.
+     */
+    @Test
+    public void testReloadWithSubNodeConfig() throws Exception
+    {
+        final String reloadContent = "<config><default><xmlReload1>{0}</xmlReload1></default></config>";
+        config.setForceReloadCheck(true);
+        config.setNodeCombiner(new OverrideCombiner());
+        File testXmlFile1 = writeReloadFile(RELOAD_XML_NAME, reloadContent, 0);
+        final String prefix1 = "default";
+        XMLConfiguration c1 = new XMLConfiguration(testXmlFile1);
+        SubnodeConfiguration sub1 = c1.configurationAt(prefix1, true);
+        assertEquals("Inital test for sub config 1 failed", 0, sub1
+                .getInt("xmlReload1"));
+        config.addConfiguration(sub1);
+        assertEquals(
+                "Could not get value for sub config 1 from combined config", 0,
+                config.getInt("xmlReload1"));
+        c1.setReloadingStrategy(new FileAlwaysReloadingStrategy());
+        writeReloadFile(RELOAD_XML_NAME, reloadContent, 1);
+        assertEquals("Reload of sub config 1 not detected", 1, config
+                .getInt("xmlReload1"));
+    }
+
+    @Test
+    public void testConcurrentGetAndReload() throws Exception
+    {
+        final int threadCount = 5;
+        final int loopCount = 1000;
+        config.setForceReloadCheck(true);
+        config.setNodeCombiner(new MergeCombiner());
+        final XMLConfiguration xml = new XMLConfiguration("configA.xml");
+        xml.setReloadingStrategy(new FileRandomReloadingStrategy());
+        config.addConfiguration(xml);
+        final XMLConfiguration xml2 = new XMLConfiguration("configB.xml");
+        xml2.setReloadingStrategy(new FileRandomReloadingStrategy());
+        config.addConfiguration(xml2);
+        config.setExpressionEngine(new XPathExpressionEngine());
+
+        assertEquals(config.getString("/property[@name='config']/@value"), "100");
+
+        Thread testThreads[] = new Thread[threadCount];
+        int failures[] = new int[threadCount];
+
+        for (int i = 0; i < testThreads.length; ++i)
+        {
+            testThreads[i] = new ReloadThread(config, failures, i, loopCount);
+            testThreads[i].start();
+        }
+
+        int totalFailures = 0;
+        for (int i = 0; i < testThreads.length; ++i)
+        {
+            testThreads[i].join();
+            totalFailures += failures[i];
+        }
+        assertTrue(totalFailures + " failures Occurred", totalFailures == 0);
+    }
+
+    /**
+     * Tests whether a combined configuration can be copied to an XML
+     * configuration. This test is related to CONFIGURATION-445.
+     */
+    @Test
+    public void testCombinedCopyToXML() throws ConfigurationException
+    {
+        XMLConfiguration x1 = new XMLConfiguration();
+        x1.addProperty("key1", "value1");
+        x1.addProperty("key1[@override]", "USER1");
+        x1.addProperty("key2", "value2");
+        x1.addProperty("key2[@override]", "USER2");
+        XMLConfiguration x2 = new XMLConfiguration();
+        x2.addProperty("key2", "value2.2");
+        x2.addProperty("key2[@override]", "USER2");
+        config.setNodeCombiner(new OverrideCombiner());
+        config.addConfiguration(x2);
+        config.addConfiguration(x1);
+        XMLConfiguration x3 = new XMLConfiguration(config);
+        assertEquals("Wrong element value", "value2.2", x3.getString("key2"));
+        assertEquals("Wrong attribute value", "USER2",
+                x3.getString("key2[@override]"));
+        StringWriter w = new StringWriter();
+        x3.save(w);
+        String s = w.toString();
+        x3 = new XMLConfiguration();
+        x3.load(new StringReader(s));
+        assertEquals("Wrong element value after load", "value2.2",
+                x3.getString("key2"));
+        assertEquals("Wrong attribute value after load", "USER2",
+                x3.getString("key2[@override]"));
+    }
+
+    private class ReloadThread extends Thread
+    {
+        CombinedConfiguration combined;
+        int[] failures;
+        int index;
+        int count;
+
+        ReloadThread(CombinedConfiguration config, int[] failures, int index, int count)
+        {
+            combined = config;
+            this.failures = failures;
+            this.index = index;
+            this.count = count;
+        }
+        @Override
+        public void run()
+        {
+            failures[index] = 0;
+            for (int i = 0; i < count; i++)
+            {
+                try
+                {
+                    String value = combined.getString("/property[@name='config']/@value");
+                    if (value == null || !value.equals("100"))
+                    {
+                        ++failures[index];
+                    }
+                }
+                catch (Exception ex)
+                {
+                    ++failures[index];
+                }
+            }
+        }
+    }
+
+    /**
+     * Helper method for writing a file. The file is also added to a list and
+     * will be deleted in teadDown() automatically.
+     *
+     * @param file the file to be written
+     * @param content the file's content
+     * @throws IOException if an error occurs
+     */
+    private void writeFile(File file, String content) throws IOException
+    {
+        PrintWriter out = null;
+        try
+        {
+            out = new PrintWriter(new FileWriter(file));
+            out.print(content);
+        }
+        finally
+        {
+            if (out != null)
+            {
+                out.close();
+            }
+        }
+    }
+
+    /**
+     * Helper method for writing a test file. The file will be created in the
+     * test directory. It is also scheduled for automatic deletion after the
+     * test.
+     *
+     * @param fileName the name of the test file
+     * @param content the content of the file
+     * @return the <code>File</code> object for the test file
+     * @throws IOException if an error occurs
+     */
+    private File writeFile(String fileName, String content) throws IOException
+    {
+        File file = new File(folder.getRoot(), fileName);
+        writeFile(file, content);
+        return file;
+    }
+
+    /**
+     * Writes a file for testing reload operations.
+     *
+     * @param name the name of the reload test file
+     * @param content the content of the file
+     * @param value the value of the reload test property
+     * @return the file that was written
+     * @throws IOException if an error occurs
+     */
+    private File writeReloadFile(String name, String content, int value)
+            throws IOException
+    {
+        return writeFile(name, MessageFormat.format(content, new Object[] {
+            new Integer(value)
+        }));
+    }
+
+    /**
+     * Helper method for creating a test configuration to be added to the
+     * combined configuration.
+     *
+     * @return the test configuration
+     */
+    private AbstractConfiguration setUpTestConfiguration()
+    {
+        HierarchicalConfiguration config = new HierarchicalConfiguration();
+        config.addProperty(TEST_KEY, Boolean.TRUE);
+        config.addProperty("test.comment", "This is a test");
+        return config;
+    }
+
+    /**
+     * Test event listener class for checking if the expected invalidate events
+     * are fired.
+     */
+    static class CombinedListener implements ConfigurationListener
+    {
+        int invalidateEvents;
+
+        int otherEvents;
+
+        public void configurationChanged(ConfigurationEvent event)
+        {
+            if (event.getType() == CombinedConfiguration.EVENT_COMBINED_INVALIDATE)
+            {
+                invalidateEvents++;
+            }
+            else
+            {
+                otherEvents++;
+            }
+        }
+
+        /**
+         * Checks if the expected number of events was fired.
+         *
+         * @param expectedInvalidate the expected number of invalidate events
+         * @param expectedOthers the expected number of other events
+         */
+        public void checkEvent(int expectedInvalidate, int expectedOthers)
+        {
+            Assert.assertEquals("Wrong number of invalidate events",
+                    expectedInvalidate, invalidateEvents);
+            Assert.assertEquals("Wrong number of other events", expectedOthers,
+                    otherEvents);
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestCompositeConfiguration.java b/src/test/java/org/apache/commons/configuration/TestCompositeConfiguration.java
new file mode 100644
index 0000000..02b7006
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestCompositeConfiguration.java
@@ -0,0 +1,931 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import org.apache.commons.configuration.event.ConfigurationEvent;
+import org.apache.commons.configuration.event.ConfigurationListener;
+import org.apache.commons.configuration.reloading.FileAlwaysReloadingStrategy;
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test loading multiple configurations.
+ *
+ * @version $Id: TestCompositeConfiguration.java 1233058 2012-01-18 20:49:12Z oheger $
+ */
+public class TestCompositeConfiguration
+{
+    /** Constant for a test property to be checked.*/
+    private static final String TEST_PROPERTY = "test.source.property";
+
+    protected PropertiesConfiguration conf1;
+    protected PropertiesConfiguration conf2;
+    protected XMLConfiguration xmlConf;
+    protected CompositeConfiguration cc;
+
+    /**
+     * The File that we test with
+     */
+    private String testProperties = ConfigurationAssert.getTestFile("test.properties").getAbsolutePath();
+    private String testProperties2 = ConfigurationAssert.getTestFile("test2.properties").getAbsolutePath();
+    private String testPropertiesXML = ConfigurationAssert.getTestFile("test.xml").getAbsolutePath();
+
+    @Before
+    public void setUp() throws Exception
+    {
+        cc = new CompositeConfiguration();
+        conf1 = new PropertiesConfiguration(testProperties);
+        conf2 = new PropertiesConfiguration(testProperties2);
+        xmlConf = new XMLConfiguration(new File(testPropertiesXML));
+
+        cc.setThrowExceptionOnMissing(true);
+    }
+
+    @Test
+    public void testThrowExceptionOnMissing()
+    {
+        assertTrue("Throw Exception Property is not set!", cc.isThrowExceptionOnMissing());
+    }
+
+    @Test
+    public void testAddRemoveConfigurations() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        assertEquals("Number of configurations", 2, cc.getNumberOfConfigurations());
+        cc.addConfiguration(conf1);
+        assertEquals("Number of configurations", 2, cc.getNumberOfConfigurations());
+        cc.addConfiguration(conf2);
+        assertEquals("Number of configurations", 3, cc.getNumberOfConfigurations());
+        cc.removeConfiguration(conf1);
+        assertEquals("Number of configurations", 2, cc.getNumberOfConfigurations());
+        cc.clear();
+        assertEquals("Number of configurations", 1, cc.getNumberOfConfigurations());
+    }
+
+    @Test
+    public void testGetPropertyWIncludes() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(conf2);
+        List<Object> l = cc.getList("packages");
+        assertTrue(l.contains("packagea"));
+    }
+
+    @Test
+    public void testGetProperty() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(conf2);
+        assertEquals("Make sure we get the property from conf1 first", "test.properties", cc.getString("propertyInOrder"));
+        cc.clear();
+
+        cc.addConfiguration(conf2);
+        cc.addConfiguration(conf1);
+        assertEquals("Make sure we get the property from conf2 first", "test2.properties", cc.getString("propertyInOrder"));
+    }
+
+    @Test
+    public void testCantRemoveMemoryConfig() throws Exception
+    {
+        cc.clear();
+        assertEquals(1, cc.getNumberOfConfigurations());
+
+        Configuration internal = cc.getConfiguration(0);
+        cc.removeConfiguration(internal);
+
+        assertEquals(1, cc.getNumberOfConfigurations());
+    }
+
+    @Test
+    public void testGetPropertyMissing() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(conf2);
+        try
+        {
+            assertNull(cc.getString("bogus.property"));
+            fail("Should have thrown a NoSuchElementException");
+        }
+        catch (NoSuchElementException nsee)
+        {
+            assertTrue(nsee.getMessage().indexOf("bogus.property") > -1);
+        }
+
+        assertTrue("Should be false", !cc.getBoolean("test.missing.boolean", false));
+        assertTrue("Should be true", cc.getBoolean("test.missing.boolean.true", true));
+    }
+
+    /**
+     * Tests {@code List} parsing.
+     */
+    @Test
+    public void testMultipleTypesOfConfigs() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(xmlConf);
+        assertEquals("Make sure we get the property from conf1 first", 1, cc.getInt("test.short"));
+        cc.clear();
+
+        cc.addConfiguration(xmlConf);
+        cc.addConfiguration(conf1);
+        assertEquals("Make sure we get the property from xml", 8, cc.getInt("test.short"));
+    }
+
+    @Test
+    public void testPropertyExistsInOnlyOneConfig() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(xmlConf);
+        assertEquals("value", cc.getString("element"));
+    }
+
+    /**
+     * Tests getting a default when the key doesn't exist
+     */
+    @Test
+    public void testDefaultValueWhenKeyMissing() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(xmlConf);
+        assertEquals("default", cc.getString("bogus", "default"));
+        assertTrue(1.4 == cc.getDouble("bogus", 1.4));
+        assertTrue(1.4 == cc.getDouble("bogus", 1.4));
+    }
+
+    @Test
+    public void testGettingConfiguration() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(xmlConf);
+        assertEquals(PropertiesConfiguration.class, cc.getConfiguration(0).getClass());
+        assertEquals(XMLConfiguration.class, cc.getConfiguration(1).getClass());
+    }
+
+    /**
+     * Tests setting values.  These are set in memory mode only!
+     */
+    @Test
+    public void testClearingProperty() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(xmlConf);
+        cc.clearProperty("test.short");
+        assertTrue("Make sure test.short is gone!", !cc.containsKey("test.short"));
+    }
+
+    /**
+     * Tests adding values.  Make sure they _DON'T_ override any other properties but add to the
+     * existing properties  and keep sequence
+     */
+    @Test
+    public void testAddingProperty() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(xmlConf);
+
+        String[] values = cc.getStringArray("test.short");
+
+        assertEquals("Number of values before add is wrong!", 1, values.length);
+        assertEquals("First Value before add is wrong", "1", values[0]);
+
+        cc.addProperty("test.short", "88");
+
+        values = cc.getStringArray("test.short");
+
+        assertEquals("Number of values is wrong!", 2, values.length);
+        assertEquals("First Value is wrong", "1", values[0]);
+        assertEquals("Third Value is wrong", "88", values[1]);
+    }
+
+    /**
+     * Tests setting values.  These are set in memory mode only!
+     */
+    @Test
+    public void testSettingMissingProperty() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(xmlConf);
+        cc.setProperty("my.new.property", "supernew");
+        assertEquals("supernew", cc.getString("my.new.property"));
+    }
+
+    /**
+     * Tests retrieving subsets of configurations
+     */
+    @Test
+    public void testGettingSubset() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(xmlConf);
+
+        Configuration subset = cc.subset("test");
+        assertNotNull(subset);
+        assertFalse("Shouldn't be empty", subset.isEmpty());
+        assertEquals("Make sure the initial loaded configs subset overrides any later add configs subset", "1", subset.getString("short"));
+
+        cc.setProperty("test.short", "43");
+        subset = cc.subset("test");
+        assertEquals("Make sure the initial loaded configs subset overrides any later add configs subset", "43", subset.getString("short"));
+    }
+
+    /**
+     * Tests subsets and still can resolve elements
+     */
+    @Test
+    public void testSubsetCanResolve() throws Exception
+    {
+        cc = new CompositeConfiguration();
+        final BaseConfiguration config = new BaseConfiguration();
+        config.addProperty("subset.tempfile", "${java.io.tmpdir}/file.tmp");
+        cc.addConfiguration(config);
+        cc.addConfiguration(ConfigurationConverter.getConfiguration(System.getProperties()));
+
+        Configuration subset = cc.subset("subset");
+        assertEquals(System.getProperty("java.io.tmpdir") + "/file.tmp", subset.getString("tempfile"));
+    }
+
+    /**
+     * Tests {@code List} parsing.
+     */
+    @Test
+    public void testList() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(xmlConf);
+
+        List<Object> packages = cc.getList("packages");
+        // we should get 3 packages here
+        assertEquals(3, packages.size());
+
+        List<Object> defaultList = new ArrayList<Object>();
+        defaultList.add("1");
+        defaultList.add("2");
+
+        packages = cc.getList("packages.which.dont.exist", defaultList);
+        // we should get 2 packages here
+        assertEquals(2, packages.size());
+
+    }
+
+    /**
+     * Tests {@code String} array parsing.
+     */
+    @Test
+    public void testStringArray() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(xmlConf);
+
+        String[] packages = cc.getStringArray("packages");
+        // we should get 3 packages here
+        assertEquals(3, packages.length);
+
+        packages = cc.getStringArray("packages.which.dont.exist");
+        // we should get 0 packages here
+        assertEquals(0, packages.length);
+    }
+
+    @Test
+    public void testGetList()
+    {
+        Configuration conf1 = new BaseConfiguration();
+        conf1.addProperty("array", "value1");
+        conf1.addProperty("array", "value2");
+
+        Configuration conf2 = new BaseConfiguration();
+        conf2.addProperty("array", "value3");
+        conf2.addProperty("array", "value4");
+
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(conf2);
+
+        // check the composite 'array' property
+        List<Object> list = cc.getList("array");
+        assertNotNull("null list", list);
+        assertEquals("list size", 2, list.size());
+        assertTrue("'value1' not found in the list", list.contains("value1"));
+        assertTrue("'value2' not found in the list", list.contains("value2"));
+
+        // add an element to the list in the composite configuration
+        cc.addProperty("array", "value5");
+
+        // test the new list
+        list = cc.getList("array");
+        assertNotNull("null list", list);
+        assertEquals("list size", 3, list.size());
+        assertTrue("'value1' not found in the list", list.contains("value1"));
+        assertTrue("'value2' not found in the list", list.contains("value2"));
+        assertTrue("'value5' not found in the list", list.contains("value5"));
+    }
+
+    /**
+     * Tests {@code getKeys} preserves the order
+     */
+    @Test
+    public void testGetKeysPreservesOrder() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        List<String> orderedList = new ArrayList<String>();
+        for (Iterator<String> keys = conf1.getKeys(); keys.hasNext();)
+        {
+            orderedList.add(keys.next());
+        }
+        List<String> iteratedList = new ArrayList<String>();
+        for (Iterator<String> keys = cc.getKeys(); keys.hasNext();)
+        {
+            iteratedList.add(keys.next());
+        }
+        assertEquals(orderedList.size(), iteratedList.size());
+        for (int i = 0; i < orderedList.size(); i++)
+        {
+            assertEquals(orderedList.get(i), iteratedList.get(i));
+        }
+    }
+
+    /**
+     * Tests {@code getKeys(String key)} preserves the order
+     */
+    @Test
+    public void testGetKeys2PreservesOrder() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        List<String> orderedList = new ArrayList<String>();
+        for (Iterator<String> keys = conf1.getKeys("test"); keys.hasNext();)
+        {
+            orderedList.add(keys.next());
+        }
+        List<String> iteratedList = new ArrayList<String>();
+        for (Iterator<String> keys = cc.getKeys("test"); keys.hasNext();)
+        {
+            iteratedList.add(keys.next());
+        }
+        assertEquals(orderedList.size(), iteratedList.size());
+        for (int i = 0; i < orderedList.size(); i++)
+        {
+            assertEquals(orderedList.get(i), iteratedList.get(i));
+        }
+    }
+
+    @Test
+    public void testGetStringWithDefaults()
+    {
+        BaseConfiguration defaults = new BaseConfiguration();
+        defaults.addProperty("default", "default string");
+
+        CompositeConfiguration c = new CompositeConfiguration(defaults);
+        c.setThrowExceptionOnMissing(cc.isThrowExceptionOnMissing());
+        c.addProperty("string", "test string");
+
+        assertEquals("test string", c.getString("string"));
+        try
+        {
+            c.getString("XXX");
+            fail("Should throw NoSuchElementException exception");
+        }
+        catch (NoSuchElementException e)
+        {
+            //ok
+        }
+        catch (Exception e)
+        {
+            fail("Should throw NoSuchElementException exception, not " + e);
+        }
+
+        //test defaults
+        assertEquals("test string", c.getString("string", "some default value"));
+        assertEquals("default string", c.getString("default"));
+        assertEquals("default string", c.getString("default", "some default value"));
+        assertEquals("some default value", c.getString("XXX", "some default value"));
+    }
+
+    @Test
+    public void testCheckingInMemoryConfiguration() throws Exception
+    {
+        String TEST_KEY = "testKey";
+        Configuration defaults = new PropertiesConfiguration();
+        defaults.setProperty(TEST_KEY, "testValue");
+        Configuration testConfiguration = new CompositeConfiguration(defaults);
+        assertTrue(testConfiguration.containsKey(TEST_KEY));
+        assertFalse(testConfiguration.isEmpty());
+        boolean foundTestKey = false;
+        Iterator<String> i = testConfiguration.getKeys();
+        //assertTrue(i instanceof IteratorChain);
+        //IteratorChain ic = (IteratorChain)i;
+        //assertEquals(2,i.size());
+        for (; i.hasNext();)
+        {
+            String key = i.next();
+            if (key.equals(TEST_KEY))
+            {
+                foundTestKey = true;
+            }
+        }
+        assertTrue(foundTestKey);
+        testConfiguration.clearProperty(TEST_KEY);
+        assertFalse(testConfiguration.containsKey(TEST_KEY));
+    }
+
+    @Test
+    public void testStringArrayInterpolation()
+    {
+        CompositeConfiguration config = new CompositeConfiguration();
+        config.addProperty("base", "foo");
+        config.addProperty("list", "${base}.bar1");
+        config.addProperty("list", "${base}.bar2");
+        config.addProperty("list", "${base}.bar3");
+
+        String[] array = config.getStringArray("list");
+        assertEquals("size", 3, array.length);
+        assertEquals("1st element", "foo.bar1", array[0]);
+        assertEquals("2nd element", "foo.bar2", array[1]);
+        assertEquals("3rd element", "foo.bar3", array[2]);
+    }
+
+    /**
+     * Tests whether global interpolation works with lists.
+     */
+    @Test
+    public void testListInterpolation()
+    {
+        PropertiesConfiguration c1 = new PropertiesConfiguration();
+        c1.addProperty("c1.value", "test1");
+        c1.addProperty("c1.value", "${c2.value}");
+        cc.addConfiguration(c1);
+        PropertiesConfiguration c2 = new PropertiesConfiguration();
+        c2.addProperty("c2.value", "test2");
+        cc.addConfiguration(c2);
+        List<Object> lst = cc.getList("c1.value");
+        assertEquals("Wrong list size", 2, lst.size());
+        assertEquals("Wrong first element", "test1", lst.get(0));
+        assertEquals("Wrong second element", "test2", lst.get(1));
+    }
+
+    /**
+     * Tests interpolation in combination with reloading.
+     */
+    @Test
+    public void testInterpolationWithReload() throws IOException,
+            ConfigurationException
+    {
+        File testFile = new File("target/testConfig.properties");
+        final String propFirst = "first.name";
+        final String propFull = "full.name";
+
+        try
+        {
+            writeTestConfig(testFile, propFirst, "John");
+            PropertiesConfiguration c1 = new PropertiesConfiguration(testFile);
+            c1.setReloadingStrategy(new FileAlwaysReloadingStrategy());
+            PropertiesConfiguration c2 = new PropertiesConfiguration();
+            c2.addProperty(propFull, "${" + propFirst + "} Doe");
+            CompositeConfiguration cc = new CompositeConfiguration();
+            cc.addConfiguration(c1);
+            cc.addConfiguration(c2);
+            assertEquals("Wrong name", "John Doe", cc.getString(propFull));
+
+            writeTestConfig(testFile, propFirst, "Jane");
+            assertEquals("First name not changed", "Jane", c1
+                    .getString(propFirst));
+            assertEquals("First name not changed in composite", "Jane", cc
+                    .getString(propFirst));
+            assertEquals("Full name not changed", "Jane Doe", cc
+                    .getString(propFull));
+        }
+        finally
+        {
+            if (testFile.exists())
+            {
+                testFile.delete();
+            }
+        }
+    }
+
+    /**
+     * Writes a test properties file containing a single property definition.
+     *
+     * @param f the file to write
+     * @param prop the property name
+     * @param value the property value
+     * @throws IOException if an error occurs
+     */
+    private void writeTestConfig(File f, String prop, String value)
+            throws IOException
+    {
+        PrintWriter out = new PrintWriter(new FileWriter(f));
+        out.print(prop);
+        out.print("=");
+        out.println(value);
+        out.close();
+    }
+
+    @Test
+    public void testInstanciateWithCollection()
+    {
+        Collection<Configuration> configs = new ArrayList<Configuration>();
+        configs.add(xmlConf);
+        configs.add(conf1);
+        configs.add(conf2);
+
+        CompositeConfiguration config = new CompositeConfiguration(configs);
+        assertEquals("Number of configurations", 4, config.getNumberOfConfigurations());
+        assertTrue("The in memory configuration is not empty", config.getInMemoryConfiguration().isEmpty());
+    }
+
+    @Test
+    public void testClone()
+    {
+        CompositeConfiguration cc2 = (CompositeConfiguration) cc.clone();
+        assertEquals("Wrong number of contained configurations", cc
+                .getNumberOfConfigurations(), cc2.getNumberOfConfigurations());
+
+        StrictConfigurationComparator comp = new StrictConfigurationComparator();
+        for (int i = 0; i < cc.getNumberOfConfigurations(); i++)
+        {
+            assertEquals("Wrong configuration class at " + i, cc
+                    .getConfiguration(i).getClass(), cc2.getConfiguration(i)
+                    .getClass());
+            assertNotSame("Configuration was not cloned", cc
+                    .getConfiguration(i), cc2.getConfiguration(i));
+            assertTrue("Configurations at " + i + " not equal", comp.compare(cc
+                    .getConfiguration(i), cc2.getConfiguration(i)));
+        }
+
+        assertTrue("Configurations are not equal", comp.compare(cc, cc2));
+    }
+
+    /**
+     * Tests cloning if one of the contained configurations does not support
+     * this operation. This should cause an exception.
+     */
+    @Test(expected = ConfigurationRuntimeException.class)
+    public void testCloneNotSupported()
+    {
+        cc.addConfiguration(new NonCloneableConfiguration());
+        cc.clone();
+    }
+
+    /**
+     * Ensures that event listeners are not cloned.
+     */
+    @Test
+    public void testCloneEventListener()
+    {
+        cc.addConfigurationListener(new TestEventListenerImpl());
+        CompositeConfiguration cc2 = (CompositeConfiguration) cc.clone();
+        assertTrue("Listeners have been cloned", cc2
+                .getConfigurationListeners().isEmpty());
+    }
+
+    /**
+     * Tests whether add property events are triggered.
+     */
+    @Test
+    public void testEventAddProperty()
+    {
+        TestEventListenerImpl l = new TestEventListenerImpl();
+        cc.addConfigurationListener(l);
+        cc.addProperty("test", "value");
+        assertEquals("No add events received", 2, l.eventCount);
+    }
+
+    /**
+     * Tests whether set property events are triggered.
+     */
+    @Test
+    public void testEventSetProperty()
+    {
+        TestEventListenerImpl l = new TestEventListenerImpl();
+        cc.addConfigurationListener(l);
+        cc.setProperty("test", "value");
+        assertEquals("No set events received", 2, l.eventCount);
+    }
+
+    /**
+     * Tests whether clear property events are triggered.
+     */
+    @Test
+    public void testEventClearProperty()
+    {
+        cc.addConfiguration(conf1);
+        assertTrue("Wrong value for property", cc
+                .getBoolean("configuration.loaded"));
+        TestEventListenerImpl l = new TestEventListenerImpl();
+        cc.addConfigurationListener(l);
+        cc.clearProperty("configuration.loaded");
+        assertFalse("Key still present", cc.containsKey("configuration.loaded"));
+        assertEquals("No clear events received", 2, l.eventCount);
+    }
+
+    /**
+     * Tests changing the list delimiter character.
+     */
+    @Test
+    public void testSetListDelimiter()
+    {
+        cc.setListDelimiter('/');
+        checkSetListDelimiter();
+    }
+
+    /**
+     * Tests whether the correct list delimiter is set after a clear operation.
+     */
+    @Test
+    public void testSetListDelimiterAfterClear()
+    {
+        cc.setListDelimiter('/');
+        cc.clear();
+        checkSetListDelimiter();
+    }
+
+    /**
+     * Helper method for testing whether the list delimiter is correctly
+     * handled.
+     */
+    private void checkSetListDelimiter()
+    {
+        cc.addProperty("test.list", "a/b/c");
+        cc.addProperty("test.property", "a,b,c");
+        assertEquals("Wrong number of list elements", 3, cc
+                .getList("test.list").size());
+        assertEquals("Wrong value of property", "a,b,c", cc
+                .getString("test.property"));
+    }
+
+    /**
+     * Tests whether list splitting can be disabled.
+     */
+    @Test
+    public void testSetDelimiterParsingDisabled()
+    {
+        cc.setDelimiterParsingDisabled(true);
+        checkSetListDelimiterParsingDisabled();
+    }
+
+    /**
+     * Tests whether the list parsing flag is correctly handled after a clear()
+     * operation.
+     */
+    @Test
+    public void testSetDelimiterParsingDisabledAfterClear()
+    {
+        cc.setDelimiterParsingDisabled(true);
+        cc.clear();
+        checkSetListDelimiterParsingDisabled();
+    }
+
+    /**
+     * Helper method for checking whether the list parsing flag is correctly
+     * handled.
+     */
+    private void checkSetListDelimiterParsingDisabled()
+    {
+        cc.addProperty("test.property", "a,b,c");
+        assertEquals("Wrong value of property", "a,b,c", cc
+                .getString("test.property"));
+    }
+
+    /**
+     * Prepares a test of the getSource() method.
+     */
+    private void setUpSourceTest()
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(conf2);
+    }
+
+    /**
+     * Tests the getSource() method if the property is defined in a single child
+     * configuration.
+     */
+    @Test
+    public void testGetSourceSingle()
+    {
+        setUpSourceTest();
+        conf1.addProperty(TEST_PROPERTY, Boolean.TRUE);
+        assertSame("Wrong source configuration", conf1, cc
+                .getSource(TEST_PROPERTY));
+    }
+
+    /**
+     * Tests the getSource() method for an unknown property key.
+     */
+    @Test
+    public void testGetSourceUnknown()
+    {
+        setUpSourceTest();
+        assertNull("Wrong source for unknown key", cc.getSource(TEST_PROPERTY));
+    }
+
+    /**
+     * Tests the getSource() method for a property contained in the in memory
+     * configuration.
+     */
+    @Test
+    public void testGetSourceInMemory()
+    {
+        setUpSourceTest();
+        cc.addProperty(TEST_PROPERTY, Boolean.TRUE);
+        assertSame("Source not found in in-memory config", cc
+                .getInMemoryConfiguration(), cc.getSource(TEST_PROPERTY));
+    }
+
+    /**
+     * Tests the getSource() method if the property is defined by multiple child
+     * configurations. In this case an exception should be thrown.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetSourceMultiple()
+    {
+        setUpSourceTest();
+        conf1.addProperty(TEST_PROPERTY, Boolean.TRUE);
+        cc.addProperty(TEST_PROPERTY, "a value");
+        cc.getSource(TEST_PROPERTY);
+    }
+
+    /**
+     * Tests the getSource() method for a null key. This should cause an
+     * exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetSourceNull()
+    {
+        cc.getSource(null);
+    }
+
+    /**
+     * Prepares a test for interpolation with multiple configurations and
+     * similar properties.
+     */
+    private void prepareInterpolationTest()
+    {
+        PropertiesConfiguration p = new PropertiesConfiguration();
+        p.addProperty("foo", "initial");
+        p.addProperty("bar", "${foo}");
+        p.addProperty("prefix.foo", "override");
+
+        cc.addConfiguration(p.subset("prefix"));
+        cc.addConfiguration(p);
+        assertEquals("Wrong value on direct access", "override", cc
+                .getString("bar"));
+    }
+
+    /**
+     * Tests querying a list when a tricky interpolation is involved. This is
+     * related to CONFIGURATION-339.
+     */
+    @Test
+    public void testGetListWithInterpolation()
+    {
+        prepareInterpolationTest();
+        List<Object> lst = cc.getList("bar");
+        assertEquals("Wrong number of values", 1, lst.size());
+        assertEquals("Wrong value in list", "override", lst.get(0));
+    }
+
+    /**
+     * Tests querying a string array when a tricky interpolation is involved.
+     */
+    @Test
+    public void testGetStringArrayWithInterpolation()
+    {
+        prepareInterpolationTest();
+        String[] values = cc.getStringArray("bar");
+        assertEquals("Wrong number of values", 1, values.length);
+        assertEquals("Wrong value in array", "override", values[0]);
+    }
+
+    /**
+     * Tests whether interpolation works if multiple configurations are
+     * involved. This test is related to CONFIGURATION-441.
+     */
+    @Test
+    public void testInterpolationInMultipleConfigs()
+    {
+        Configuration c1 = new PropertiesConfiguration();
+        c1.addProperty("property.one", "one");
+        c1.addProperty("property.two", "two");
+        Configuration c2 = new PropertiesConfiguration();
+        c2.addProperty("property.one.ref", "${property.one}");
+        cc.addConfiguration(c1);
+        cc.addConfiguration(c2);
+        assertEquals("Wrong interpolated value", "one",
+                cc.getString("property.one.ref"));
+    }
+
+    /**
+     * Tests the behavior of setListDelimiter() if the in-memory configuration
+     * is not derived from BaseConfiguration. This test is related to
+     * CONFIGURATION-476.
+     */
+    @Test
+    public void testSetListDelimiterInMemoryConfigNonBaseConfig()
+    {
+        Configuration inMemoryConfig = EasyMock.createMock(Configuration.class);
+        EasyMock.replay(inMemoryConfig);
+        cc = new CompositeConfiguration(inMemoryConfig);
+        cc.setListDelimiter(';');
+    }
+
+    /**
+     * Tests the behavior of setDelimiterParsingDisabled() if the in-memory
+     * configuration is not derived from BaseConfiguration. This test is related
+     * to CONFIGURATION-476.
+     */
+    @Test
+    public void testSetDelimiterParsingDisabledInMemoryConfigNonBaseConfig()
+    {
+        Configuration inMemoryConfig = EasyMock.createMock(Configuration.class);
+        EasyMock.replay(inMemoryConfig);
+        cc = new CompositeConfiguration(inMemoryConfig);
+        cc.setDelimiterParsingDisabled(true);
+    }
+
+    /**
+     * Tests whether a configuration can act as both regular child configuration
+     * and in-memory configuration. This test is related to CONFIGURATION-471.
+     */
+    @Test
+    public void testUseChildConfigAsInMemoryConfig()
+    {
+        conf1.setProperty(TEST_PROPERTY, "conf1");
+        conf2.setProperty(TEST_PROPERTY, "conf2");
+        cc.addConfiguration(conf1, true);
+        cc.addConfiguration(conf2);
+        assertEquals("Wrong number of configurations", 2,
+                cc.getNumberOfConfigurations());
+        assertEquals("Wrong property", "conf1", cc.getString(TEST_PROPERTY));
+        cc.addProperty("newProperty", "newValue");
+        assertEquals("Not added to in-memory config", "newValue",
+                conf1.getString("newProperty"));
+    }
+
+    /**
+     * Tests whether the in-memory configuration can be replaced by a new child
+     * configuration.
+     */
+    @Test
+    public void testReplaceInMemoryConfig()
+    {
+        conf1.setProperty(TEST_PROPERTY, "conf1");
+        conf2.setProperty(TEST_PROPERTY, "conf2");
+        cc.addConfiguration(conf1, true);
+        cc.addProperty("newProperty1", "newValue1");
+        cc.addConfiguration(conf2, true);
+        cc.addProperty("newProperty2", "newValue2");
+        assertEquals("Wrong property", "conf1", cc.getString(TEST_PROPERTY));
+        assertEquals("Not added to in-memory config", "newValue1",
+                conf1.getString("newProperty1"));
+        assertEquals("In-memory config not changed", "newValue2",
+                conf2.getString("newProperty2"));
+    }
+
+    /**
+     * A test configuration event listener that counts the number of received
+     * events. Used for testing the event facilities.
+     */
+    static class TestEventListenerImpl implements ConfigurationListener
+    {
+        /** The number of received events.*/
+        int eventCount;
+
+        public void configurationChanged(ConfigurationEvent event)
+        {
+            eventCount++;
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestCompositeConfigurationNonStringProperties.java b/src/test/java/org/apache/commons/configuration/TestCompositeConfigurationNonStringProperties.java
new file mode 100644
index 0000000..a37976e
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestCompositeConfigurationNonStringProperties.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import org.junit.Before;
+
+
+/**
+ * Test if non-string properties are handled correctly.
+ *
+ * @version $Id: TestCompositeConfigurationNonStringProperties.java 1222445 2011-12-22 20:53:47Z oheger $
+ */
+public class TestCompositeConfigurationNonStringProperties extends BaseNonStringProperties
+{
+    /** The File that we test with */
+    private String testProperties = ConfigurationAssert.getTestFile("test.properties").getAbsolutePath();
+
+    @Before
+    public void setUp() throws Exception
+    {
+        CompositeConfiguration cc = new CompositeConfiguration();
+        cc.addConfiguration(new PropertiesConfiguration(testProperties));
+        conf = cc;
+        nonStringTestHolder.setConfiguration(conf);
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestConfiguration.java b/src/test/java/org/apache/commons/configuration/TestConfiguration.java
new file mode 100644
index 0000000..69b0cb6
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestConfiguration.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestConfiguration
+{
+    @Test
+    public void testConfigurationGetList()
+    {
+        final List<String> defaults = new ArrayList<String>();
+
+        String key = UUID.randomUUID().toString();
+        for (int i = 0; i < 10; i++) {
+            defaults.add(UUID.randomUUID().toString());
+        }
+
+        final Configuration c = new MapConfiguration(Collections.<String, String>emptyMap());
+
+        final List<Object> values = c.getList(key, defaults);
+
+        Assert.assertEquals(defaults, values);
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestConfigurationConverter.java b/src/test/java/org/apache/commons/configuration/TestConfigurationConverter.java
new file mode 100644
index 0000000..676a56a
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestConfigurationConverter.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.commons.collections.ExtendedProperties;
+import org.junit.Test;
+
+/**
+ * Tests the ConfigurationConverter class.
+ *
+ * @author Martin Poeschl
+ * @author Emmanuel Bourg
+ * @version $Id: TestConfigurationConverter.java 1223010 2011-12-24 20:21:36Z oheger $
+ */
+public class TestConfigurationConverter
+{
+    @Test
+    public void testExtendedPropertiesToConfiguration()
+    {
+        ExtendedProperties eprops = new ExtendedProperties();
+        eprops.setProperty("string", "teststring");
+        eprops.setProperty("int", "123");
+        eprops.addProperty("list", "item 1");
+        eprops.addProperty("list", "item 2");
+
+        Configuration config = ConfigurationConverter.getConfiguration(eprops);
+
+        assertEquals("This returns 'teststring'", "teststring", config.getString("string"));
+        List<Object> item1 = config.getList("list");
+        assertEquals("This returns 'item 1'", "item 1", item1.get(0));
+
+        assertEquals("This returns 123", 123, config.getInt("int"));
+    }
+
+    @Test
+    public void testPropertiesToConfiguration()
+    {
+        Properties props = new Properties();
+        props.setProperty("string", "teststring");
+        props.setProperty("int", "123");
+        props.setProperty("list", "item 1, item 2");
+
+        Configuration config = ConfigurationConverter.getConfiguration(props);
+
+        assertEquals("This returns 'teststring'", "teststring", config.getString("string"));
+        List<Object> item1 = config.getList("list");
+        assertEquals("This returns 'item 1'", "item 1", item1.get(0));
+
+        assertEquals("This returns 123", 123, config.getInt("int"));
+    }
+
+    @Test
+    public void testConfigurationToExtendedProperties()
+    {
+        Configuration config = new BaseConfiguration();
+        config.setProperty("string", "teststring");
+        config.setProperty("int", "123");
+        config.addProperty("list", "item 1");
+        config.addProperty("list", "item 2");
+
+        ExtendedProperties eprops = ConfigurationConverter.getExtendedProperties(config);
+
+        assertEquals("This returns 'teststring'", "teststring", eprops.getString("string"));
+        List<?> list = eprops.getVector("list");
+        assertEquals("This returns 'item 1'", "item 1", list.get(0));
+        assertEquals("This returns 123", 123, eprops.getInt("int"));
+    }
+
+    @Test
+    public void testConfigurationToProperties()
+    {
+        BaseConfiguration config = new BaseConfiguration();
+        config.addProperty("string", "teststring");
+        config.addProperty("array", "item 1");
+        config.addProperty("array", "item 2");
+        config.addProperty("interpolated", "${string}");
+        config.addProperty("interpolated-array", "${interpolated}");
+        config.addProperty("interpolated-array", "${interpolated}");
+
+        Properties props = ConfigurationConverter.getProperties(config);
+
+        assertNotNull("null properties", props);
+        assertEquals("'string' property", "teststring", props.getProperty("string"));
+        assertEquals("'interpolated' property", "teststring", props.getProperty("interpolated"));
+        assertEquals("'array' property", "item 1,item 2", props.getProperty("array"));
+        assertEquals("'interpolated-array' property", "teststring,teststring", props.getProperty("interpolated-array"));
+
+        // change the list delimiter
+        config.setListDelimiter(';');
+        props = ConfigurationConverter.getProperties(config);
+        assertEquals("'array' property", "item 1;item 2", props.getProperty("array"));
+    }
+
+    /**
+     * Tests the conversion of a configuration object to properties if scalar
+     * values are involved. This test is related to CONFIGURATION-432.
+     */
+    @Test
+    public void testConfigurationToPropertiesScalarValue()
+    {
+        BaseConfiguration config = new BaseConfiguration();
+        config.addProperty("scalar", new Integer(42));
+        Properties props = ConfigurationConverter.getProperties(config);
+        assertEquals("Wrong value", "42", props.getProperty("scalar"));
+    }
+
+    @Test
+    public void testConfigurationToMap()
+    {
+        Configuration config = new BaseConfiguration();
+        config.addProperty("string", "teststring");
+
+        Map<Object, Object> map = ConfigurationConverter.getMap(config);
+
+        assertNotNull("null map", map);
+        assertEquals("'string' property", "teststring", map.get("string"));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestConfigurationFactory.java b/src/test/java/org/apache/commons/configuration/TestConfigurationFactory.java
new file mode 100644
index 0000000..6fd0ff1
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestConfigurationFactory.java
@@ -0,0 +1,407 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.net.URL;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.xml.sax.SAXException;
+
+/**
+ * Test the ConfigurationFactory.
+ *
+ * @version $Id: TestConfigurationFactory.java 1223011 2011-12-24 20:30:45Z oheger $
+ */
+ at SuppressWarnings("deprecation")
+public class TestConfigurationFactory
+{
+    /** The Files that we test with */
+    private URL digesterRules = getClass().getResource("/digesterRules.xml");
+    private File testDigesterFile = ConfigurationAssert.getTestFile("testDigesterConfiguration.xml");
+    private File testDigesterFileReverseOrder =
+            ConfigurationAssert.getTestFile("testDigesterConfigurationReverseOrder.xml");
+    private File testDigesterFileNamespaceAware =
+            ConfigurationAssert.getTestFile("testDigesterConfigurationNamespaceAware.xml");
+    private File testDigesterFileBasePath =
+            ConfigurationAssert.getTestFile("testDigesterConfigurationBasePath.xml");
+    private File testDigesterFileEnhanced =
+            ConfigurationAssert.getTestFile("testDigesterConfiguration2.xml");
+    private File testDigesterFileComplete =
+            ConfigurationAssert.getTestFile("testDigesterConfiguration3.xml");
+    private File testDigesterFileOptional =
+            ConfigurationAssert.getTestFile("testDigesterOptionalConfiguration.xml");
+    private File testDigesterFileOptionalEx =
+            ConfigurationAssert.getTestFile("testDigesterOptionalConfigurationEx.xml");
+    private File testDigesterFileSysProps =
+            ConfigurationAssert.getTestFile("testDigesterConfigurationSysProps.xml");
+    private File testDigesterFileInitProps =
+            ConfigurationAssert.getTestFile("testDigesterConfigurationWithProps.xml");
+
+    private File testDigesterBadXML = ConfigurationAssert.getTestFile("testDigesterBadXML.xml");
+
+    private String testBasePath = new File("conf").getAbsolutePath();
+
+    private File testProperties = ConfigurationAssert.getTestFile("test.properties");
+    private File testAbsConfig = ConfigurationAssert.getOutFile("testAbsConfig.xml");
+
+    private Configuration configuration;
+    private CompositeConfiguration compositeConfiguration;
+    private ConfigurationFactory factory;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        System.setProperty("java.naming.factory.initial", "org.apache.commons.configuration.MockInitialContextFactory");
+        factory = new ConfigurationFactory();
+    }
+
+    @Test
+    public void testJNDI() throws Exception
+    {
+        JNDIConfiguration jndiConfiguration = new JNDIConfiguration();
+        Object o = jndiConfiguration.getProperty("test.boolean");
+        assertNotNull(o);
+        assertEquals("true", o.toString());
+    }
+
+    @Test
+    public void testLoadingConfiguration() throws Exception
+    {
+        factory.setConfigurationFileName(testDigesterFile.toString());
+
+        compositeConfiguration = (CompositeConfiguration) factory.getConfiguration();
+
+        assertEquals("Number of configurations", 4, compositeConfiguration.getNumberOfConfigurations());
+        assertEquals(PropertiesConfiguration.class, compositeConfiguration.getConfiguration(0).getClass());
+        assertEquals(XMLPropertiesConfiguration.class, compositeConfiguration.getConfiguration(1).getClass());
+        assertEquals(XMLConfiguration.class, compositeConfiguration.getConfiguration(2).getClass());
+
+        // check the first configuration
+        PropertiesConfiguration pc = (PropertiesConfiguration) compositeConfiguration.getConfiguration(0);
+        assertNotNull("Make sure we have a fileName: " + pc.getFileName(), pc.getFileName());
+
+        // check some properties
+        assertTrue("Make sure we have loaded our key", compositeConfiguration.getBoolean("test.boolean"));
+        assertEquals("I'm complex!", compositeConfiguration.getProperty("element2.subelement.subsubelement"));
+        assertEquals("property in the XMLPropertiesConfiguration", "value1", compositeConfiguration.getProperty("key1"));
+    }
+
+    @Test
+    public void testLoadingConfigurationWithRulesXML() throws Exception
+    {
+        factory.setConfigurationFileName(testDigesterFile.toString());
+        factory.setDigesterRules(digesterRules);
+
+        compositeConfiguration = (CompositeConfiguration) factory.getConfiguration();
+
+        assertEquals("Number of configurations", 4, compositeConfiguration.getNumberOfConfigurations());
+        assertEquals(PropertiesConfiguration.class, compositeConfiguration.getConfiguration(0).getClass());
+        //assertEquals(XMLPropertiesConfiguration.class, compositeConfiguration.getConfiguration(1).getClass()); // doesn't work
+        assertEquals(XMLConfiguration.class, compositeConfiguration.getConfiguration(2).getClass());
+
+        // check the first configuration
+        PropertiesConfiguration pc = (PropertiesConfiguration) compositeConfiguration.getConfiguration(0);
+        assertNotNull("Make sure we have a fileName: " + pc.getFileName(), pc.getFileName());
+
+        // check some properties
+        assertTrue("Make sure we have loaded our key", pc.getBoolean("test.boolean"));
+        assertTrue("Make sure we have loaded our key", compositeConfiguration.getBoolean("test.boolean"));
+
+        assertEquals("I'm complex!", compositeConfiguration.getProperty("element2.subelement.subsubelement"));
+    }
+
+    @Test
+    public void testLoadingConfigurationReverseOrder() throws Exception
+    {
+        factory.setConfigurationFileName(testDigesterFileReverseOrder.toString());
+
+        configuration = factory.getConfiguration();
+
+        assertEquals("8", configuration.getProperty("test.short"));
+
+        factory.setConfigurationFileName(testDigesterFile.toString());
+
+        configuration = factory.getConfiguration();
+        assertEquals("1", configuration.getProperty("test.short"));
+    }
+
+    @Test
+    public void testLoadingConfigurationNamespaceAware() throws Exception
+    {
+        factory.setConfigurationFileName(testDigesterFileNamespaceAware.toString());
+        factory.setDigesterRuleNamespaceURI("namespace-one");
+
+        checkCompositeConfiguration();
+    }
+
+    @Test
+    public void testLoadingConfigurationBasePath() throws Exception
+    {
+        factory.setConfigurationFileName(testDigesterFileBasePath.toString());
+
+        factory.setBasePath(testBasePath);
+
+        //factory.setDigesterRuleNamespaceURI("namespace-one");
+
+        checkCompositeConfiguration();
+    }
+
+    @Test
+    public void testLoadingAdditional() throws Exception
+    {
+        factory.setConfigurationFileName(testDigesterFileEnhanced.toString());
+        factory.setBasePath(null);
+        checkUnionConfig();
+    }
+
+    @Test
+    public void testLoadingURL() throws Exception
+    {
+        factory.setConfigurationURL(testDigesterFileEnhanced.toURL());
+        checkUnionConfig();
+    }
+
+    @Test(expected = ConfigurationException.class)
+    public void testLoadingURLNonExisting() throws Exception
+    {
+        factory = new ConfigurationFactory();
+        File nonExistingFile = new File("conf/nonexisting.xml");
+        factory.setConfigurationURL(nonExistingFile.toURL());
+        factory.getConfiguration();
+    }
+
+    @Test
+    public void testThrowingConfigurationInitializationException() throws Exception
+    {
+        factory.setConfigurationFileName(testDigesterBadXML.toString());
+        try
+        {
+            factory.getConfiguration();
+            fail("Should have throw an Exception");
+        }
+        catch (ConfigurationException cle)
+        {
+            assertTrue("Unexpected cause: " + cle.getCause(),
+                    cle.getCause() instanceof SAXException);
+        }
+    }
+
+    // Tests if properties from all sources can be loaded
+    @Test
+    public void testAllConfiguration() throws Exception
+    {
+        factory.setConfigurationURL(testDigesterFileComplete.toURL());
+        Configuration config = factory.getConfiguration();
+        assertFalse(config.isEmpty());
+        assertTrue(config instanceof CompositeConfiguration);
+        CompositeConfiguration cc = (CompositeConfiguration) config;
+        assertTrue(cc.getNumberOfConfigurations() > 1);
+        // Currently fails, should be 4?  Only 2?
+        //assertEquals(4, cc.getNumberOfConfigurations());
+
+        assertNotNull(config.getProperty("tables.table(0).fields.field(2).name"));
+        assertNotNull(config.getProperty("element2.subelement.subsubelement"));
+        assertEquals("value", config.getProperty("element3"));
+        assertEquals("foo", config.getProperty("element3[@name]"));
+        assertNotNull(config.getProperty("mail.account.user"));
+
+        // test JNDIConfiguration
+        assertNotNull(config.getProperty("test.onlyinjndi"));
+        assertTrue(config.getBoolean("test.onlyinjndi"));
+
+        Configuration subset = config.subset("test");
+        assertNotNull(subset.getProperty("onlyinjndi"));
+        assertTrue(subset.getBoolean("onlyinjndi"));
+
+        // test SystemConfiguration
+        assertNotNull(config.getProperty("java.version"));
+        assertEquals(System.getProperty("java.version"), config.getString("java.version"));
+    }
+
+    // Checks if optional configurations work
+    @Test
+    public void testOptionalConfigurations() throws Exception
+    {
+        factory.setConfigurationURL(testDigesterFileOptional.toURL());
+        Configuration config = factory.getConfiguration();
+        assertTrue(config.getBoolean("test.boolean"));
+        assertEquals("value", config.getProperty("element"));
+
+        factory.setConfigurationURL(testDigesterFileOptionalEx.toURL());
+        try
+        {
+            config = factory.getConfiguration();
+            fail("Unexisting properties loaded!");
+        }
+        catch(ConfigurationException cex)
+        {
+            // fine
+        }
+    }
+
+    // Checks if a file with an absolute path can be loaded
+    @Test
+    public void testLoadAbsolutePath() throws Exception
+    {
+        try
+        {
+            FileWriter out = null;
+            try
+            {
+                out = new FileWriter(testAbsConfig);
+                out.write("<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>");
+                out.write("<configuration>");
+                out.write("<properties fileName=\"");
+                out.write(testProperties.getAbsolutePath());
+                out.write("\"/>");
+                out.write("</configuration>");
+            }
+            finally
+            {
+                if (out != null)
+                {
+                    out.close();
+                }
+            }
+
+            factory.setConfigurationFileName(testAbsConfig.toString());
+            Configuration config = factory.getConfiguration();
+            assertTrue(config.getBoolean("configuration.loaded"));
+        }
+        finally
+        {
+            if (testAbsConfig.exists())
+            {
+                testAbsConfig.delete();
+            }
+        }
+    }
+
+    @Test
+    public void testBasePath() throws Exception
+    {
+        assertEquals(".", factory.getBasePath());
+        factory.setConfigurationFileName(testDigesterFile.getAbsolutePath());
+        // if no specific base path has been set, the base is determined
+        // from the file name
+        assertEquals(testDigesterFile.getParentFile().getAbsolutePath(),
+                factory.getBasePath());
+
+        String homeDir = System.getProperty("user.home");
+        factory = new ConfigurationFactory();
+        factory.setBasePath(homeDir);
+        factory.setConfigurationFileName(testDigesterFile.getAbsolutePath());
+        // if a base path was set, the file name does not play a role
+        assertEquals(homeDir, factory.getBasePath());
+
+        factory = new ConfigurationFactory(testDigesterFile.getAbsolutePath());
+        assertEquals(testDigesterFile.getParentFile().getAbsolutePath(),
+                factory.getBasePath());
+        factory.setBasePath(homeDir);
+        assertEquals(homeDir, factory.getBasePath());
+
+        factory = new ConfigurationFactory();
+        factory.setConfigurationURL(testDigesterFile.toURL());
+        assertEquals(testDigesterFile.toURL().toString(), factory.getBasePath());
+    }
+
+    // Tests if system properties can be resolved in the configuration
+    // definition
+    @Test
+    public void testLoadingWithSystemProperties() throws ConfigurationException
+    {
+        System.setProperty("config.file", "test.properties");
+        factory.setConfigurationFileName(testDigesterFileSysProps
+                .getAbsolutePath());
+        Configuration config = factory.getConfiguration();
+        assertTrue("Configuration not loaded", config
+                .getBoolean("configuration.loaded"));
+    }
+
+    // Tests if the properties of a configuration object are correctly set
+    // before it is loaded.
+    @Test
+    public void testLoadInitProperties() throws ConfigurationException
+    {
+        factory.setConfigurationFileName(testDigesterFileInitProps
+                .getAbsolutePath());
+        Configuration config = factory.getConfiguration();
+        PropertiesConfiguration c = (PropertiesConfiguration) ((CompositeConfiguration) config)
+                .getConfiguration(0);
+        assertEquals("List delimiter was not set", ';', c.getListDelimiter());
+        List<Object> l = c.getList("test.mixed.array");
+        assertEquals("Wrong number of list elements", 2, l.size());
+        assertEquals("List delimiter was not applied", "b, c, d", l.get(1));
+    }
+
+    private void checkUnionConfig() throws Exception
+    {
+        compositeConfiguration = (CompositeConfiguration) factory.getConfiguration();
+        assertEquals("Verify how many configs", 3, compositeConfiguration.getNumberOfConfigurations());
+
+        // Test if union was constructed correctly
+        Object prop = compositeConfiguration.getProperty("tables.table.name");
+        assertTrue(prop instanceof Collection);
+        assertEquals(3, ((Collection<?>) prop).size());
+        assertEquals("users", compositeConfiguration.getProperty("tables.table(0).name"));
+        assertEquals("documents", compositeConfiguration.getProperty("tables.table(1).name"));
+        assertEquals("tasks", compositeConfiguration.getProperty("tables.table(2).name"));
+
+        prop = compositeConfiguration.getProperty("tables.table.fields.field.name");
+        assertTrue(prop instanceof Collection);
+        assertEquals(17, ((Collection<?>) prop).size());
+
+        assertEquals("smtp.mydomain.org", compositeConfiguration.getString("mail.host.smtp"));
+        assertEquals("pop3.mydomain.org", compositeConfiguration.getString("mail.host.pop"));
+
+        // This was overriden
+        assertEquals("masterOfPost", compositeConfiguration.getString("mail.account.user"));
+        assertEquals("topsecret", compositeConfiguration.getString("mail.account.psswd"));
+
+        // This was overriden, too, but not in additional section
+        assertEquals("enhanced factory", compositeConfiguration.getString("test.configuration"));
+    }
+
+    private void checkCompositeConfiguration() throws Exception
+    {
+        compositeConfiguration = (CompositeConfiguration) factory.getConfiguration();
+
+        assertEquals("Verify how many configs", 2, compositeConfiguration.getNumberOfConfigurations());
+        assertEquals(PropertiesConfiguration.class, compositeConfiguration.getConfiguration(0).getClass());
+
+        PropertiesConfiguration pc = (PropertiesConfiguration) compositeConfiguration.getConfiguration(0);
+        assertNotNull("Make sure we have a fileName:" + pc.getFileName(), pc.getFileName());
+        assertTrue("Make sure we have loaded our key", pc.getBoolean("test.boolean"));
+        assertTrue("Make sure we have loaded our key", compositeConfiguration.getBoolean("test.boolean"));
+
+        Object property = compositeConfiguration.getProperty("element2.subelement.subsubelement");
+        assertNull("Should have returned a null", property);
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestConfigurationKey.java b/src/test/java/org/apache/commons/configuration/TestConfigurationKey.java
new file mode 100644
index 0000000..1192d2f
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestConfigurationKey.java
@@ -0,0 +1,262 @@
+package org.apache.commons.configuration;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.NoSuchElementException;
+
+import org.junit.Test;
+
+/**
+ * Test class for ConfigurationKey.
+ *
+ * @version $Id: TestConfigurationKey.java 1231749 2012-01-15 20:48:56Z oheger $
+ */
+ at SuppressWarnings("deprecation")
+public class TestConfigurationKey
+{
+    private static final String TESTPROPS = "tables.table(0).fields.field(1)";
+
+    private static final String TESTATTR = "[@dataType]";
+
+    private static final String TESTKEY = TESTPROPS + TESTATTR;
+
+    @Test
+    public void testAppend()
+    {
+        ConfigurationKey key = new ConfigurationKey();
+        key.append("tables").append("table.").appendIndex(0);
+        key.append("fields.").append("field").appendIndex(1);
+        key.appendAttribute("dataType");
+        assertEquals(TESTKEY, key.toString());
+    }
+
+    @Test
+    public void testIterate()
+    {
+        ConfigurationKey key = new ConfigurationKey(TESTKEY);
+        ConfigurationKey.KeyIterator it = key.iterator();
+        assertTrue(it.hasNext());
+        assertEquals("tables", it.nextKey());
+        assertEquals("table", it.nextKey());
+        assertTrue(it.hasIndex());
+        assertEquals(0, it.getIndex());
+        assertEquals("fields", it.nextKey());
+        assertFalse(it.hasIndex());
+        assertEquals("field", it.nextKey(true));
+        assertEquals(1, it.getIndex());
+        assertFalse(it.isAttribute());
+        assertEquals("field", it.currentKey(true));
+        assertEquals("dataType", it.nextKey());
+        assertEquals("[@dataType]", it.currentKey(true));
+        assertTrue(it.isAttribute());
+        assertFalse(it.hasNext());
+        try
+        {
+            it.next();
+            fail("Could iterate over the iteration's end!");
+        }
+        catch(NoSuchElementException nex)
+        {
+            //ok
+        }
+
+        key = new ConfigurationKey();
+        assertFalse(key.iterator().hasNext());
+        key.append("simple");
+        it = key.iterator();
+        assertTrue(it.hasNext());
+        assertEquals("simple", it.next());
+        try
+        {
+            it.remove();
+            fail("Could remove key component!");
+        }
+        catch(UnsupportedOperationException uex)
+        {
+            //ok
+        }
+    }
+
+    @Test
+    public void testAttribute()
+    {
+        assertTrue(ConfigurationKey.isAttributeKey(TESTATTR));
+        assertFalse(ConfigurationKey.isAttributeKey(TESTPROPS));
+        assertFalse(ConfigurationKey.isAttributeKey(TESTKEY));
+
+        ConfigurationKey key = new ConfigurationKey(TESTPROPS);
+        key.append(TESTATTR);
+        assertEquals(TESTKEY, key.toString());
+    }
+
+    @Test
+    public void testLength()
+    {
+        ConfigurationKey key = new ConfigurationKey(TESTPROPS);
+        assertEquals(TESTPROPS.length(), key.length());
+        key.appendAttribute("dataType");
+        assertEquals(TESTKEY.length(), key.length());
+        key.setLength(TESTPROPS.length());
+        assertEquals(TESTPROPS.length(), key.length());
+        assertEquals(TESTPROPS, key.toString());
+    }
+
+    @Test
+    public void testConstructAttributeKey()
+    {
+        assertEquals("[@attribute]", ConfigurationKey.constructAttributeKey("attribute"));
+        assertEquals("attribute", ConfigurationKey.attributeName("[@attribute]"));
+        assertEquals("attribute", ConfigurationKey.attributeName("attribute"));
+    }
+
+    @Test
+    public void testEquals()
+    {
+        ConfigurationKey k1 = new ConfigurationKey(TESTKEY);
+        ConfigurationKey k2 = new ConfigurationKey(TESTKEY);
+        assertTrue(k1.equals(k2));
+        assertTrue(k2.equals(k1));
+        assertEquals(k1.hashCode(), k2.hashCode());
+        k2.append("anotherPart");
+        assertFalse(k1.equals(k2));
+        assertFalse(k2.equals(k1));
+        assertFalse(k1.equals(null));
+        assertTrue(k1.equals(TESTKEY));
+    }
+
+    @Test
+    public void testCommonKey()
+    {
+        ConfigurationKey k1 = new ConfigurationKey(TESTKEY);
+        ConfigurationKey k2 = new ConfigurationKey("tables.table(0).name");
+        ConfigurationKey kc = k1.commonKey(k2);
+        assertEquals(new ConfigurationKey("tables.table(0)"), kc);
+        assertEquals(kc, k2.commonKey(k1));
+
+        k2 = new ConfigurationKey("tables.table(1).fields.field(1)");
+        kc = k1.commonKey(k2);
+        assertEquals(new ConfigurationKey("tables"), kc);
+
+        k2 = new ConfigurationKey("completely.different.key");
+        kc = k1.commonKey(k2);
+        assertEquals(0, kc.length());
+
+        k2 = new ConfigurationKey();
+        kc = k1.commonKey(k2);
+        assertEquals(0, kc.length());
+
+        kc = k1.commonKey(k1);
+        assertEquals(kc, k1);
+
+        try
+        {
+            kc.commonKey(null);
+            fail("Could construct common key with null key!");
+        }
+        catch(IllegalArgumentException iex)
+        {
+            //ok
+        }
+    }
+
+    @Test
+    public void testDifferenceKey()
+    {
+        ConfigurationKey k1 = new ConfigurationKey(TESTKEY);
+        ConfigurationKey kd = k1.differenceKey(k1);
+        assertEquals(0, kd.length());
+
+        ConfigurationKey k2 = new ConfigurationKey("tables.table(0).name");
+        kd = k1.differenceKey(k2);
+        assertEquals("name", kd.toString());
+
+        k2 = new ConfigurationKey("tables.table(1).fields.field(1)");
+        kd = k1.differenceKey(k2);
+        assertEquals("table(1).fields.field(1)", kd.toString());
+
+        k2 = new ConfigurationKey("completely.different.key");
+        kd = k1.differenceKey(k2);
+        assertEquals(k2, kd);
+    }
+
+    @Test
+    public void testEscapedDelimiters()
+    {
+        ConfigurationKey k = new ConfigurationKey();
+        k.append("my..elem");
+        k.append("trailing..dot..");
+        k.append("strange");
+        assertEquals("my..elem.trailing..dot...strange", k.toString());
+
+        ConfigurationKey.KeyIterator kit = k.iterator();
+        assertEquals("my.elem", kit.nextKey());
+        assertEquals("trailing.dot.", kit.nextKey());
+        assertEquals("strange", kit.nextKey());
+        assertFalse(kit.hasNext());
+    }
+
+    /**
+     * Tests some funny keys.
+     */
+    @Test
+    public void testIterateStrangeKeys()
+    {
+        ConfigurationKey k = new ConfigurationKey("key.");
+        ConfigurationKey.KeyIterator it = k.iterator();
+        assertTrue(it.hasNext());
+        assertEquals("key", it.next());
+        assertFalse(it.hasNext());
+
+        k = new ConfigurationKey(".");
+        it = k.iterator();
+        assertFalse(it.hasNext());
+
+        k = new ConfigurationKey("key().index()undefined(0).test");
+        it = k.iterator();
+        assertEquals("key()", it.next());
+        assertFalse(it.hasIndex());
+        assertEquals("index()undefined", it.nextKey(false));
+        assertTrue(it.hasIndex());
+        assertEquals(0, it.getIndex());
+    }
+
+    /**
+     * Tests iterating over an attribute key that has an index.
+     */
+    @Test
+    public void testAttributeKeyWithIndex()
+    {
+        ConfigurationKey k = new ConfigurationKey(TESTATTR);
+        k.appendIndex(0);
+        assertEquals("Wrong attribute key with index", TESTATTR + "(0)", k.toString());
+
+        ConfigurationKey.KeyIterator it = k.iterator();
+        assertTrue("No first element", it.hasNext());
+        it.next();
+        assertTrue("Index not found", it.hasIndex());
+        assertEquals("Incorrect index", 0, it.getIndex());
+        assertTrue("Attribute not found", it.isAttribute());
+        assertEquals("Wrong plain key", "dataType", it.currentKey(false));
+        assertEquals("Wrong decorated key", TESTATTR, it.currentKey(true));
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestConfigurationMap.java b/src/test/java/org/apache/commons/configuration/TestConfigurationMap.java
new file mode 100644
index 0000000..30803f8
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestConfigurationMap.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:ricardo.gladwell at btinternet.com">Ricardo Gladwell</a>
+ */
+public class TestConfigurationMap
+{
+
+    ConfigurationMap map;
+
+    String[] properties = {
+            "booleanProperty",
+            "doubleProperty",
+            "floatProperty",
+            "intProperty",
+            "longProperty",
+            "shortProperty",
+            "stringProperty"
+    };
+
+    Object[] values = {
+            Boolean.TRUE,
+            new Double(Double.MAX_VALUE),
+            new Float(Float.MAX_VALUE),
+            new Integer(Integer.MAX_VALUE),
+            new Long(Long.MAX_VALUE),
+            new Short(Short.MAX_VALUE),
+            "This is a string"
+    };
+
+    /**
+     * Set up instance variables required by this test case.
+     */
+    @Before
+    public void setUp() throws Exception
+    {
+        BaseConfiguration configuration = new BaseConfiguration();
+        for(int i = 0; i < properties.length ; i++)
+            configuration.setProperty(properties[i], values[i]);
+        map = new ConfigurationMap(configuration);
+    }
+
+    /**
+     * Tear down instance variables required by this test case.
+     */
+    @After
+    public void tearDown()
+    {
+        map = null;
+    }
+
+    /**
+     * Class under test for Object put(Object, Object)
+     */
+    @Test
+    public void testPut()
+    {
+        for(int i = 0; i < properties.length; i++) {
+            Object object = map.put(properties[i], values[i]);
+            assertNotNull("Returned null from put.",object);
+            assertEquals("Returned wrong result.",values[i],object);
+            object = map.get(properties[i]);
+            assertNotNull("Returned null from get.",object);
+            assertEquals("Returned wrong result.",values[i],object);
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestConfigurationSet.java b/src/test/java/org/apache/commons/configuration/TestConfigurationSet.java
new file mode 100644
index 0000000..6d57c8f
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestConfigurationSet.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author rgladwel
+ */
+public class TestConfigurationSet {
+
+    ConfigurationMap.ConfigurationSet set;
+
+    String[] properties = {
+            "booleanProperty",
+            "doubleProperty",
+            "floatProperty",
+            "intProperty",
+            "longProperty",
+            "shortProperty",
+            "stringProperty"
+    };
+
+    Object[] values = {
+            Boolean.TRUE,
+            new Double(Double.MAX_VALUE),
+            new Float(Float.MAX_VALUE),
+            new Integer(Integer.MAX_VALUE),
+            new Long(Long.MAX_VALUE),
+            new Short(Short.MAX_VALUE),
+            "This is a string"
+    };
+
+    /**
+     * Set up instance variables required by this test case.
+     */
+    @Before
+    public void setUp() throws Exception
+    {
+        BaseConfiguration configuration = new BaseConfiguration();
+        for(int i = 0; i < properties.length ; i++)
+            configuration.setProperty(properties[i], values[i]);
+        set = new ConfigurationMap.ConfigurationSet(configuration);
+    }
+
+    /**
+     * Tear down instance variables required by this test case.
+     */
+    @After
+    public void tearDown()
+    {
+        set = null;
+    }
+
+    @Test
+    public void testSize() {
+        assertEquals("Entry set does not match properties size.", properties.length, set.size());
+    }
+
+    /**
+     * Class under test for Iterator iterator()
+     */
+    @Test
+    public void testIterator() {
+        Iterator<Map.Entry<Object, Object>> iterator = set.iterator();
+        while(iterator.hasNext()) {
+            Map.Entry<Object, Object> entry = iterator.next();
+            boolean found = false;
+            for(int i = 0; i < properties.length; i++) {
+                if(entry.getKey().equals(properties[i])) {
+                    assertEquals("Incorrect value for property " +
+                            properties[i],values[i],entry.getValue());
+                    found = true;
+                }
+            }
+            assertTrue("Could not find property " + entry.getKey(),found);
+            iterator.remove();
+        }
+        assertTrue("Iterator failed to remove all properties.",set.isEmpty());
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestConfigurationUtils.java b/src/test/java/org/apache/commons/configuration/TestConfigurationUtils.java
new file mode 100644
index 0000000..47f3bcc
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestConfigurationUtils.java
@@ -0,0 +1,459 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import junitx.framework.ListAssert;
+
+import org.apache.commons.configuration.tree.DefaultExpressionEngine;
+import org.apache.commons.configuration.tree.ExpressionEngine;
+import org.junit.Test;
+
+import com.mockobjects.dynamic.Mock;
+
+/**
+ * Tests the ConfigurationUtils class
+ *
+ * @version $Id: TestConfigurationUtils.java 1301996 2012-03-17 20:30:39Z sebb $
+ */
+public class TestConfigurationUtils
+{
+    protected Configuration config = new BaseConfiguration();
+
+    @Test
+    public void testToString()
+    {
+        String lineSeparator = System.getProperty("line.separator");
+
+        assertEquals("String representation of an empty configuration", "", ConfigurationUtils.toString(config));
+
+        config.setProperty("one", "1");
+        assertEquals("String representation of a configuration", "one=1", ConfigurationUtils.toString(config));
+
+        config.setProperty("two", "2");
+        assertEquals("String representation of a configuration", "one=1" + lineSeparator + "two=2" , ConfigurationUtils.toString(config));
+
+        config.clearProperty("one");
+        assertEquals("String representation of a configuration", "two=2" , ConfigurationUtils.toString(config));
+
+        config.setProperty("one","1");
+        assertEquals("String representation of a configuration", "two=2" + lineSeparator + "one=1" , ConfigurationUtils.toString(config));
+    }
+
+    @Test
+    public void testGetURL() throws Exception
+    {
+        assertEquals(
+            "http://localhost:8080/webapp/config/config.xml",
+            ConfigurationUtils
+                .getURL(
+                    "http://localhost:8080/webapp/config/baseConfig.xml",
+                    "config.xml")
+                .toString());
+        assertEquals(
+            "http://localhost:8080/webapp/config/config.xml",
+            ConfigurationUtils
+                .getURL(
+                    "http://localhost:8080/webapp/baseConfig.xml",
+                    "config/config.xml")
+                .toString());
+        URL url = ConfigurationUtils.getURL(null, "config.xml");
+        assertEquals("file", url.getProtocol());
+        assertEquals("", url.getHost());
+
+        assertEquals(
+            "http://localhost:8080/webapp/config/config.xml",
+            ConfigurationUtils
+                .getURL(
+                    "ftp://ftp.server.com/downloads/baseConfig.xml",
+                    "http://localhost:8080/webapp/config/config.xml")
+                .toString());
+        assertEquals(
+            "http://localhost:8080/webapp/config/config.xml",
+            ConfigurationUtils
+                .getURL(null, "http://localhost:8080/webapp/config/config.xml")
+                .toString());
+        File absFile = new File("config.xml").getAbsoluteFile();
+        assertEquals(
+            absFile.toURI().toURL(),
+            ConfigurationUtils.getURL(
+                "http://localhost:8080/webapp/config/baseConfig.xml",
+                absFile.getAbsolutePath()));
+        assertEquals(
+            absFile.toURI().toURL(),
+            ConfigurationUtils.getURL(null, absFile.getAbsolutePath()));
+
+        assertEquals(absFile.toURI().toURL(),
+        ConfigurationUtils.getURL(absFile.getParent(), "config.xml"));
+    }
+
+    @Test
+    public void testGetBasePath() throws Exception
+    {
+        URL url = new URL("http://xyz.net/foo/bar.xml");
+        assertEquals("base path of " + url, "http://xyz.net/foo/", ConfigurationUtils.getBasePath(url));
+
+        url = new URL("http://xyz.net/foo/");
+        assertEquals("base path of " + url, "http://xyz.net/foo/", ConfigurationUtils.getBasePath(url));
+
+        url = new URL("http://xyz.net/foo");
+        assertEquals("base path of " + url, "http://xyz.net/", ConfigurationUtils.getBasePath(url));
+
+        url = new URL("http://xyz.net/");
+        assertEquals("base path of " + url, "http://xyz.net/", ConfigurationUtils.getBasePath(url));
+
+        url = new URL("http://xyz.net");
+        assertEquals("base path of " + url, "http://xyz.net", ConfigurationUtils.getBasePath(url));
+    }
+
+    @Test
+    public void testGetFileName() throws Exception
+    {
+        assertEquals("file name for a null URL", null, ConfigurationUtils.getFileName(null));
+
+        URL url = new URL("http://xyz.net/foo/");
+        assertEquals("file for a directory URL " + url, null, ConfigurationUtils.getFileName(url));
+
+        url = new URL("http://xyz.net/foo/bar.xml");
+        assertEquals("file name for a valid URL " + url, "bar.xml", ConfigurationUtils.getFileName(url));
+    }
+
+    @Test
+    public void testCopy()
+    {
+        // create the source configuration
+        Configuration conf1 = new BaseConfiguration();
+        conf1.addProperty("key1", "value1");
+        conf1.addProperty("key2", "value2");
+
+        // create the target configuration
+        Configuration conf2 = new BaseConfiguration();
+        conf2.addProperty("key1", "value3");
+        conf2.addProperty("key2", "value4");
+
+        // copy the source configuration into the target configuration
+        ConfigurationUtils.copy(conf1, conf2);
+
+        assertEquals("'key1' property", "value1", conf2.getProperty("key1"));
+        assertEquals("'key2' property", "value2", conf2.getProperty("key2"));
+    }
+
+    @Test
+    public void testAppend()
+    {
+        // create the source configuration
+        Configuration conf1 = new BaseConfiguration();
+        conf1.addProperty("key1", "value1");
+        conf1.addProperty("key2", "value2");
+
+        // create the target configuration
+        Configuration conf2 = new BaseConfiguration();
+        conf2.addProperty("key1", "value3");
+        conf2.addProperty("key2", "value4");
+
+        // append the source configuration to the target configuration
+        ConfigurationUtils.append(conf1, conf2);
+
+        List<Object> expected = new ArrayList<Object>();
+        expected.add("value3");
+        expected.add("value1");
+        ListAssert.assertEquals("'key1' property", expected, conf2.getList("key1"));
+
+        expected = new ArrayList<Object>();
+        expected.add("value4");
+        expected.add("value2");
+        ListAssert.assertEquals("'key2' property", expected, conf2.getList("key2"));
+    }
+
+    @Test
+    public void testGetFile() throws Exception
+    {
+        File directory = new File("target");
+        File reference = new File(directory, "test.txt").getAbsoluteFile();
+
+        assertEquals(reference, ConfigurationUtils.getFile(null, reference.getAbsolutePath()));
+        assertEquals(reference, ConfigurationUtils.getFile(directory.getAbsolutePath(), reference.getAbsolutePath()));
+        assertEquals(reference, ConfigurationUtils.getFile(directory.getAbsolutePath(), reference.getName()));
+        assertEquals(reference, ConfigurationUtils.getFile(directory.toURI().toURL().toString(), reference.getName()));
+        assertEquals(reference, ConfigurationUtils.getFile("invalid", reference.toURI().toURL().toString()));
+        assertEquals(reference, ConfigurationUtils.getFile(
+                "jar:file:/C:/myjar.jar!/my-config.xml/someprops.properties",
+                reference.getAbsolutePath()));
+    }
+
+    /**
+     * Tests whether a "+" character in the file name is handled correctly by
+     * fileFromURL(). This test is related to CONFIGURATION-415.
+     */
+    @Test
+    public void testFileFromURLWithPlus() throws MalformedURLException
+    {
+        File file = new File(new File("target"), "foo+bar.txt")
+                .getAbsoluteFile();
+        URL fileURL = file.toURI().toURL();
+        File file2 = ConfigurationUtils.fileFromURL(fileURL);
+        assertEquals("Wrong file", file, file2);
+    }
+
+    /**
+     * Tests whether fileFromURL() handles null URLs correctly.
+     */
+    @Test
+    public void testFileFromURLNull() throws Exception
+    {
+        assertNull("Wrong file for null URL", ConfigurationUtils
+                .fileFromURL(null));
+    }
+
+    @Test
+    public void testLocateWithNullTCCL() throws Exception
+    {
+        ClassLoader cl = Thread.currentThread().getContextClassLoader();
+        try
+        {
+            Thread.currentThread().setContextClassLoader(null);
+            assertNull(ConfigurationUtils.locate("abase", "aname"));
+            // This assert fails when maven 2 is used, so commented out
+            //assertNotNull(ConfigurationUtils.locate("test.xml"));
+        }
+        finally
+        {
+            Thread.currentThread().setContextClassLoader(cl);
+        }
+    }
+
+    /**
+     * Tests converting a configuration into a hierarchical one.
+     */
+    @Test
+    public void testConvertToHierarchical()
+    {
+        Configuration conf = new BaseConfiguration();
+        for (int i = 0; i < 10; i++)
+        {
+            conf.addProperty("test" + i, "value" + i);
+            conf.addProperty("test.list", "item" + i);
+        }
+
+        HierarchicalConfiguration hc = ConfigurationUtils
+                .convertToHierarchical(conf);
+        for (Iterator<String> it = conf.getKeys(); it.hasNext();)
+        {
+            String key = it.next();
+            assertEquals("Wrong value for key " + key, conf.getProperty(key),
+                    hc.getProperty(key));
+        }
+    }
+
+    /**
+     * Tests converting a configuration into a hierarchical one that is already
+     * hierarchical.
+     */
+    @Test
+    public void testConvertHierarchicalToHierarchical()
+    {
+        Configuration conf = new HierarchicalConfiguration();
+        conf.addProperty("test", "yes");
+        assertSame("Wrong configuration returned", conf, ConfigurationUtils
+                .convertToHierarchical(conf));
+    }
+
+    /**
+     * Tests converting a null configuration to a hierarchical one. The result
+     * should be null, too.
+     */
+    @Test
+    public void testConvertNullToHierarchical()
+    {
+        assertNull("Wrong conversion result for null config",
+                ConfigurationUtils.convertToHierarchical(null));
+    }
+
+    /**
+     * Tests converting a configuration into a hierarchical one if some of its
+     * properties contain escaped list delimiter characters.
+     */
+    @Test
+    public void testConvertToHierarchicalDelimiters()
+    {
+        Configuration conf = new BaseConfiguration();
+        conf.addProperty("test.key", "1\\,2\\,3");
+        assertEquals("Wrong property value", "1,2,3", conf
+                .getString("test.key"));
+        HierarchicalConfiguration hc = ConfigurationUtils
+                .convertToHierarchical(conf);
+        assertEquals("Escaped list delimiters not correctly handled", "1,2,3",
+                hc.getString("test.key"));
+    }
+
+    /**
+     * Tests converting a configuration to a hierarchical one using a specific
+     * expression engine.
+     */
+    @Test
+    public void testConvertToHierarchicalEngine()
+    {
+        Configuration conf = new BaseConfiguration();
+        conf.addProperty("test(a)", Boolean.TRUE);
+        conf.addProperty("test(b)", Boolean.FALSE);
+        DefaultExpressionEngine engine = new DefaultExpressionEngine();
+        engine.setIndexStart("[");
+        engine.setIndexEnd("]");
+        HierarchicalConfiguration hc = ConfigurationUtils
+                .convertToHierarchical(conf, engine);
+        assertTrue("Wrong value for test(a)", hc.getBoolean("test(a)"));
+        assertFalse("Wrong value for test(b)", hc.getBoolean("test(b)"));
+    }
+
+    /**
+     * Tests converting an already hierarchical configuration using an
+     * expression engine. The new engine should be set.
+     */
+    @Test
+    public void testConvertHierarchicalToHierarchicalEngine()
+    {
+        HierarchicalConfiguration hc = new HierarchicalConfiguration();
+        ExpressionEngine engine = new DefaultExpressionEngine();
+        assertSame("Created new configuration", hc, ConfigurationUtils
+                .convertToHierarchical(hc, engine));
+        assertSame("Engine was not set", engine, hc.getExpressionEngine());
+    }
+
+    /**
+     * Tests converting an already hierarchical configuration using a null
+     * expression engine. In this case the expression engine of the
+     * configuration should not be touched.
+     */
+    @Test
+    public void testConvertHierarchicalToHierarchicalNullEngine()
+    {
+        HierarchicalConfiguration hc = new HierarchicalConfiguration();
+        ExpressionEngine engine = new DefaultExpressionEngine();
+        hc.setExpressionEngine(engine);
+        assertSame("Created new configuration", hc, ConfigurationUtils
+                .convertToHierarchical(hc, null));
+        assertSame("Expression engine was changed", engine, hc
+                .getExpressionEngine());
+    }
+
+    /**
+     * Tests converting a configuration to a hierarchical one that contains a
+     * property with multiple values. This test is related to CONFIGURATION-346.
+     */
+    @Test
+    public void testConvertToHierarchicalMultiValues()
+    {
+        BaseConfiguration config = new BaseConfiguration();
+        config.addProperty("test", "1,2,3");
+        HierarchicalConfiguration hc = ConfigurationUtils
+                .convertToHierarchical(config);
+        assertEquals("Wrong value 1", 1, hc.getInt("test(0)"));
+        assertEquals("Wrong value 2", 2, hc.getInt("test(1)"));
+        assertEquals("Wrong value 3", 3, hc.getInt("test(2)"));
+    }
+
+    /**
+     * Tests cloning a configuration that supports this operation.
+     */
+    @Test
+    public void testCloneConfiguration()
+    {
+        HierarchicalConfiguration conf = new HierarchicalConfiguration();
+        conf.addProperty("test", "yes");
+        HierarchicalConfiguration copy = (HierarchicalConfiguration) ConfigurationUtils
+                .cloneConfiguration(conf);
+        assertNotSame("Same object was returned", conf, copy);
+        assertEquals("Property was not cloned", "yes", copy.getString("test"));
+    }
+
+    /**
+     * Tests cloning a configuration that does not support this operation. This
+     * should cause an exception.
+     */
+    @Test(expected = ConfigurationRuntimeException.class)
+    public void testCloneConfigurationNotSupported()
+    {
+        Configuration myNonCloneableConfig = new NonCloneableConfiguration();
+        ConfigurationUtils.cloneConfiguration(myNonCloneableConfig);
+    }
+
+    /**
+     * Tests cloning a <b>null</b> configuration.
+     */
+    @Test
+    public void testCloneConfigurationNull()
+    {
+        assertNull("Wrong return value", ConfigurationUtils
+                .cloneConfiguration(null));
+    }
+
+    /**
+     * Tests whether runtime exceptions can be enabled.
+     */
+    @Test(expected = ConfigurationRuntimeException.class)
+    public void testEnableRuntimeExceptions()
+    {
+        PropertiesConfiguration config = new PropertiesConfiguration()
+        {
+            @Override
+            protected void addPropertyDirect(String key, Object value)
+            {
+                // always simulate an exception
+                fireError(EVENT_ADD_PROPERTY, key, value, new RuntimeException(
+                        "A faked exception!"));
+            }
+        };
+        config.clearErrorListeners();
+        ConfigurationUtils.enableRuntimeExceptions(config);
+        config.addProperty("test", "testValue");
+    }
+
+    /**
+     * Tries to enable runtime exceptions for a configuration that does not
+     * inherit from EventSource. This should cause an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testEnableRuntimeExceptionsInvalid()
+    {
+        ConfigurationUtils.enableRuntimeExceptions((Configuration) new Mock(
+                Configuration.class).proxy());
+    }
+
+    /**
+     * Tries to enable runtime exceptions for a null configuration. This should
+     * cause an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testEnableRuntimeExceptionsNull()
+    {
+        ConfigurationUtils.enableRuntimeExceptions(null);
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestDataConfiguration.java b/src/test/java/org/apache/commons/configuration/TestDataConfiguration.java
new file mode 100644
index 0000000..7797b62
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestDataConfiguration.java
@@ -0,0 +1,2402 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.awt.Color;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.URL;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.NoSuchElementException;
+
+import junitx.framework.ArrayAssert;
+import junitx.framework.ListAssert;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Emmanuel Bourg
+ * @version $Id: TestDataConfiguration.java 1535297 2013-10-24 07:53:37Z henning $
+ */
+public class TestDataConfiguration
+{
+    private DataConfiguration conf;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        conf = new DataConfiguration(new BaseConfiguration());
+
+        // empty value
+        conf.addProperty("empty", "");
+
+        // lists of boolean
+        conf.addProperty("boolean.list1", "true");
+        conf.addProperty("boolean.list1", "false");
+        conf.addProperty("boolean.list2", "true, false");
+        conf.addProperty("boolean.list3", Boolean.TRUE);
+        conf.addProperty("boolean.list3", Boolean.FALSE);
+        conf.addPropertyDirect("boolean.list4", new Boolean[] { Boolean.TRUE, Boolean.FALSE });
+        conf.addPropertyDirect("boolean.list5", new boolean[] { true, false });
+        List<Object> booleans = new ArrayList<Object>();
+        booleans.add(Boolean.TRUE);
+        booleans.add(Boolean.FALSE);
+        conf.addProperty("boolean.list6", booleans);
+        conf.addProperty("boolean.string", "true");
+        conf.addProperty("boolean.object", Boolean.TRUE);
+        conf.addProperty("boolean.list.interpolated", "${boolean.string},false");
+
+        // lists of bytes
+        conf.addProperty("byte.list1", "1");
+        conf.addProperty("byte.list1", "2");
+        conf.addProperty("byte.list2", "1, 2");
+        conf.addProperty("byte.list3", new Byte("1"));
+        conf.addProperty("byte.list3", new Byte("2"));
+        conf.addPropertyDirect("byte.list4", new Byte[] { new Byte("1"), new Byte("2") });
+        conf.addPropertyDirect("byte.list5", new byte[] { 1, 2 });
+        List<Object> bytes = new ArrayList<Object>();
+        bytes.add(new Byte("1"));
+        bytes.add(new Byte("2"));
+        conf.addProperty("byte.list6", bytes);
+        conf.addProperty("byte.string", "1");
+        conf.addProperty("byte.object", new Byte("1"));
+        conf.addProperty("byte.list.interpolated", "${byte.string},2");
+
+        // lists of shorts
+        conf.addProperty("short.list1", "1");
+        conf.addProperty("short.list1", "2");
+        conf.addProperty("short.list2", "1, 2");
+        conf.addProperty("short.list3", new Short("1"));
+        conf.addProperty("short.list3", new Short("2"));
+        conf.addPropertyDirect("short.list4", new Short[] { new Short("1"), new Short("2") });
+        conf.addPropertyDirect("short.list5", new short[] { 1, 2 });
+        List<Object> shorts = new ArrayList<Object>();
+        shorts.add(new Short("1"));
+        shorts.add(new Short("2"));
+        conf.addProperty("short.list6", shorts);
+        conf.addProperty("short.string", "1");
+        conf.addProperty("short.object", new Short("1"));
+        conf.addProperty("short.list.interpolated", "${short.string},2");
+
+        // lists of integers
+        conf.addProperty("integer.list1", "1");
+        conf.addProperty("integer.list1", "2");
+        conf.addProperty("integer.list2", "1, 2");
+        conf.addProperty("integer.list3", new Integer("1"));
+        conf.addProperty("integer.list3", new Integer("2"));
+        conf.addPropertyDirect("integer.list4", new Integer[] { new Integer("1"), new Integer("2") });
+        conf.addPropertyDirect("integer.list5", new int[] { 1, 2 });
+        List<Object> integers = new ArrayList<Object>();
+        integers.add(new Integer("1"));
+        integers.add(new Integer("2"));
+        conf.addProperty("integer.list6", integers);
+        conf.addProperty("integer.string", "1");
+        conf.addProperty("integer.object", new Integer("1"));
+        conf.addProperty("integer.list.interpolated", "${integer.string},2");
+
+        // lists of longs
+        conf.addProperty("long.list1", "1");
+        conf.addProperty("long.list1", "2");
+        conf.addProperty("long.list2", "1, 2");
+        conf.addProperty("long.list3", new Long("1"));
+        conf.addProperty("long.list3", new Long("2"));
+        conf.addPropertyDirect("long.list4", new Long[] { new Long("1"), new Long("2") });
+        conf.addPropertyDirect("long.list5", new long[] { 1, 2 });
+        List<Object> longs = new ArrayList<Object>();
+        longs.add(new Long("1"));
+        longs.add(new Long("2"));
+        conf.addProperty("long.list6", longs);
+        conf.addProperty("long.string", "1");
+        conf.addProperty("long.object", new Long("1"));
+        conf.addProperty("long.list.interpolated", "${long.string},2");
+
+        // lists of floats
+        conf.addProperty("float.list1", "1");
+        conf.addProperty("float.list1", "2");
+        conf.addProperty("float.list2", "1, 2");
+        conf.addProperty("float.list3", new Float("1"));
+        conf.addProperty("float.list3", new Float("2"));
+        conf.addPropertyDirect("float.list4", new Float[] { new Float("1"), new Float("2") });
+        conf.addPropertyDirect("float.list5", new float[] { 1, 2 });
+        List<Object> floats = new ArrayList<Object>();
+        floats.add(new Float("1"));
+        floats.add(new Float("2"));
+        conf.addProperty("float.list6", floats);
+        conf.addProperty("float.string", "1");
+        conf.addProperty("float.object", new Float("1"));
+        conf.addProperty("float.list.interpolated", "${float.string},2");
+
+        // lists of doubles
+        conf.addProperty("double.list1", "1");
+        conf.addProperty("double.list1", "2");
+        conf.addProperty("double.list2", "1, 2");
+        conf.addProperty("double.list3", new Double("1"));
+        conf.addProperty("double.list3", new Double("2"));
+        conf.addPropertyDirect("double.list4", new Double[] { new Double("1"), new Double("2") });
+        conf.addPropertyDirect("double.list5", new double[] { 1, 2 });
+        List<Object> doubles = new ArrayList<Object>();
+        doubles.add(new Double("1"));
+        doubles.add(new Double("2"));
+        conf.addProperty("double.list6", doubles);
+        conf.addProperty("double.string", "1");
+        conf.addProperty("double.object", new Double("1"));
+        conf.addProperty("double.list.interpolated", "${double.string},2");
+
+        // lists of big integers
+        conf.addProperty("biginteger.list1", "1");
+        conf.addProperty("biginteger.list1", "2");
+        conf.addProperty("biginteger.list2", "1, 2");
+        conf.addProperty("biginteger.list3", new BigInteger("1"));
+        conf.addProperty("biginteger.list3", new BigInteger("2"));
+        conf.addPropertyDirect("biginteger.list4", new BigInteger[] { new BigInteger("1"), new BigInteger("2") });
+        List<Object> bigintegers = new ArrayList<Object>();
+        bigintegers.add(new BigInteger("1"));
+        bigintegers.add(new BigInteger("2"));
+        conf.addProperty("biginteger.list6", bigintegers);
+        conf.addProperty("biginteger.string", "1");
+        conf.addProperty("biginteger.object", new BigInteger("1"));
+        conf.addProperty("biginteger.list.interpolated", "${biginteger.string},2");
+
+        // lists of big decimals
+        conf.addProperty("bigdecimal.list1", "1");
+        conf.addProperty("bigdecimal.list1", "2");
+        conf.addProperty("bigdecimal.list2", "1, 2");
+        conf.addProperty("bigdecimal.list3", new BigDecimal("1"));
+        conf.addProperty("bigdecimal.list3", new BigDecimal("2"));
+        conf.addPropertyDirect("bigdecimal.list4", new BigDecimal[] { new BigDecimal("1"), new BigDecimal("2") });
+        List<Object> bigdecimals = new ArrayList<Object>();
+        bigdecimals.add(new BigDecimal("1"));
+        bigdecimals.add(new BigDecimal("2"));
+        conf.addProperty("bigdecimal.list6", bigdecimals);
+        conf.addProperty("bigdecimal.string", "1");
+        conf.addProperty("bigdecimal.object", new BigDecimal("1"));
+        conf.addProperty("bigdecimal.list.interpolated", "${bigdecimal.string},2");
+
+        // URLs
+        String url1 = "http://jakarta.apache.org";
+        String url2 = "http://www.apache.org";
+        conf.addProperty("url.string", url1);
+        conf.addProperty("url.string.interpolated", "${url.string}");
+        conf.addProperty("url.object", new URL(url1));
+        conf.addProperty("url.list1", url1);
+        conf.addProperty("url.list1", url2);
+        conf.addProperty("url.list2", url1 + ", " + url2);
+        conf.addProperty("url.list3", new URL(url1));
+        conf.addProperty("url.list3", new URL(url2));
+        conf.addPropertyDirect("url.list4", new URL[] { new URL(url1), new URL(url2) });
+        List<Object> urls = new ArrayList<Object>();
+        urls.add(new URL(url1));
+        urls.add(new URL(url2));
+        conf.addProperty("url.list6", urls);
+        conf.addProperty("url.list.interpolated", "${url.string}," + url2);
+
+        // Locales
+        conf.addProperty("locale.string", "fr");
+        conf.addProperty("locale.string.interpolated", "${locale.string}");
+        conf.addProperty("locale.object", Locale.FRENCH);
+        conf.addProperty("locale.list1", "fr");
+        conf.addProperty("locale.list1", "de");
+        conf.addProperty("locale.list2", "fr, de");
+        conf.addProperty("locale.list3", Locale.FRENCH);
+        conf.addProperty("locale.list3", Locale.GERMAN);
+        conf.addPropertyDirect("locale.list4", new Locale[] { Locale.FRENCH, Locale.GERMAN });
+        List<Object> locales = new ArrayList<Object>();
+        locales.add(Locale.FRENCH);
+        locales.add(Locale.GERMAN);
+        conf.addProperty("locale.list6", locales);
+        conf.addProperty("locale.list.interpolated", "${locale.string},de");
+
+        // Colors
+        String color1 = "FF0000";
+        String color2 = "0000FF";
+        conf.addProperty("color.string", color1);
+        conf.addProperty("color.string.interpolated", "${color.string}");
+        conf.addProperty("color.object", Color.red);
+        conf.addProperty("color.list1", color1);
+        conf.addProperty("color.list1", color2);
+        conf.addProperty("color.list2", color1 + ", " + color2);
+        conf.addProperty("color.list3", Color.red);
+        conf.addProperty("color.list3", Color.blue);
+        conf.addPropertyDirect("color.list4", new Color[] { Color.red, Color.blue });
+        List<Object> colors = new ArrayList<Object>();
+        colors.add(Color.red);
+        colors.add(Color.blue);
+        conf.addProperty("color.list6", colors);
+        conf.addProperty("color.list.interpolated", "${color.string}," + color2);
+
+        // Dates & Calendars
+        String pattern = "yyyy-MM-dd";
+        DateFormat format = new SimpleDateFormat(pattern);
+        conf.setProperty(DataConfiguration.DATE_FORMAT_KEY, pattern);
+
+        Date date1 = format.parse("2004-01-01");
+        Date date2 = format.parse("2004-12-31");
+        Calendar calendar1 = Calendar.getInstance();
+        calendar1.setTime(date1);
+        Calendar calendar2 = Calendar.getInstance();
+        calendar2.setTime(date2);
+
+        conf.addProperty("date.string", "2004-01-01");
+        conf.addProperty("date.string.interpolated", "${date.string}");
+        conf.addProperty("date.object", date1);
+        conf.addProperty("date.list1", "2004-01-01");
+        conf.addProperty("date.list1", "2004-12-31");
+        conf.addProperty("date.list2", "2004-01-01, 2004-12-31");
+        conf.addProperty("date.list3", date1);
+        conf.addProperty("date.list3", date2);
+        conf.addPropertyDirect("date.list4", new Date[] { date1, date2 });
+        conf.addPropertyDirect("date.list5", new Calendar[] { calendar1, calendar2 });
+        List<Object> dates = new ArrayList<Object>();
+        dates.add(date1);
+        dates.add(date2);
+        conf.addProperty("date.list6", dates);
+        conf.addProperty("date.list.interpolated", "${date.string},2004-12-31");
+        conf.addPropertyDirect("date.list7", new String[] { "2004-01-01", "2004-12-31" });
+
+        conf.addProperty("calendar.string", "2004-01-01");
+        conf.addProperty("calendar.string.interpolated", "${calendar.string}");
+        conf.addProperty("calendar.object", calendar1);
+        conf.addProperty("calendar.list1", "2004-01-01");
+        conf.addProperty("calendar.list1", "2004-12-31");
+        conf.addProperty("calendar.list2", "2004-01-01, 2004-12-31");
+        conf.addProperty("calendar.list3", calendar1);
+        conf.addProperty("calendar.list3", calendar2);
+        conf.addPropertyDirect("calendar.list4", new Calendar[] { calendar1, calendar2 });
+        conf.addPropertyDirect("calendar.list5", new Date[] { date1, date2 });
+        List<Object> calendars = new ArrayList<Object>();
+        calendars.add(date1);
+        calendars.add(date2);
+        conf.addProperty("calendar.list6", calendars);
+        conf.addProperty("calendar.list.interpolated", "${calendar.string},2004-12-31");
+        conf.addPropertyDirect("calendar.list7", new String[] { "2004-01-01", "2004-12-31" });
+
+        // host address
+        conf.addProperty("ip.string", "127.0.0.1");
+        conf.addProperty("ip.string.interpolated", "${ip.string}");
+        conf.addProperty("ip.object", InetAddress.getByName("127.0.0.1"));
+
+        // email address
+        conf.addProperty("email.string", "ebourg at apache.org");
+        conf.addProperty("email.string.interpolated", "${email.string}");
+        conf.addProperty("email.object", createInternetAddress("ebourg at apache.org"));
+    }
+
+    @Test
+    public void testGetConfiguration()
+    {
+        Configuration baseconf = new BaseConfiguration();
+        DataConfiguration conf = new DataConfiguration(baseconf);
+
+        assertEquals("base configuration", baseconf, conf.getConfiguration());
+    }
+
+    @Test
+    public void testIsEmpty()
+    {
+        Configuration baseconf = new BaseConfiguration();
+        DataConfiguration conf = new DataConfiguration(baseconf);
+
+        assertTrue("not empty", conf.isEmpty());
+
+        baseconf.setProperty("foo", "bar");
+
+        assertFalse("empty", conf.isEmpty());
+    }
+
+    @Test
+    public void testContainsKey()
+    {
+        Configuration baseconf = new BaseConfiguration();
+        DataConfiguration conf = new DataConfiguration(baseconf);
+
+        assertFalse(conf.containsKey("foo"));
+
+        baseconf.setProperty("foo", "bar");
+
+        assertTrue(conf.containsKey("foo"));
+    }
+
+    @Test
+    public void testGetKeys()
+    {
+        Configuration baseconf = new BaseConfiguration();
+        DataConfiguration conf = new DataConfiguration(baseconf);
+
+        baseconf.setProperty("foo", "bar");
+
+        Iterator<String> it = conf.getKeys();
+        assertTrue("the iterator is empty", it.hasNext());
+        assertEquals("unique key", "foo", it.next());
+        assertFalse("the iterator is not exhausted", it.hasNext());
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testGetInvalidType()
+    {
+        conf.get(Boolean.class, "url.object", null);
+    }
+
+    @Test
+    public void testGetUnknown()
+    {
+        assertNull("non null object for a missing key", conf.get(Object.class, "unknownkey"));
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void testGetUnknownException()
+    {
+        conf.setThrowExceptionOnMissing(true);
+        conf.get(Object.class, "unknownkey");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetArrayInvalidDefaultType()
+    {
+        conf.getArray(Boolean.class, "unknownkey", new URL[] {});
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testGetPrimitiveArrayInvalidType()
+    {
+        conf.getArray(Boolean.TYPE, "calendar.list4");
+    }
+
+    @Test
+    public void testGetBooleanArray()
+    {
+        // missing list
+        boolean[] defaultValue = new boolean[] { false, true };
+        ArrayAssert.assertEquals(defaultValue, conf.getBooleanArray("boolean.list", defaultValue));
+
+        boolean[] expected = new boolean[] { true, false };
+
+        // list of strings
+        ArrayAssert.assertEquals(expected, conf.getBooleanArray("boolean.list1"));
+
+        // list of strings, comma separated
+        ArrayAssert.assertEquals(expected, conf.getBooleanArray("boolean.list2"));
+
+        // list of Boolean objects
+        ArrayAssert.assertEquals(expected, conf.getBooleanArray("boolean.list3"));
+
+        // array of Boolean objects
+        ArrayAssert.assertEquals(expected, conf.getBooleanArray("boolean.list4"));
+
+        // array of boolean primitives
+        ArrayAssert.assertEquals(expected, conf.getBooleanArray("boolean.list5"));
+
+        // list of Boolean objects
+        ArrayAssert.assertEquals(expected, conf.getBooleanArray("boolean.list6"));
+
+        // list of interpolated values
+        ArrayAssert.assertEquals(expected, conf.getBooleanArray("boolean.list.interpolated"));
+
+        // single boolean values
+        ArrayAssert.assertEquals(new boolean[] { true }, conf.getBooleanArray("boolean.string"));
+        ArrayAssert.assertEquals(new boolean[] { true }, conf.getBooleanArray("boolean.object"));
+
+        // empty array
+        ArrayAssert.assertEquals(new boolean[] { }, conf.getBooleanArray("empty"));
+    }
+
+    @Test
+    public void testGetBooleanList()
+    {
+        // missing list
+        ListAssert.assertEquals(null, conf.getBooleanList("boolean.list", null));
+
+        List<Object> expected = new ArrayList<Object>();
+        expected.add(Boolean.TRUE);
+        expected.add(Boolean.FALSE);
+
+        // list of strings
+        ListAssert.assertEquals(expected, conf.getBooleanList("boolean.list1"));
+
+        // list of strings, comma separated
+        ListAssert.assertEquals(expected, conf.getBooleanList("boolean.list2"));
+
+        // list of Boolean objects
+        ListAssert.assertEquals(expected, conf.getBooleanList("boolean.list3"));
+
+        // array of Boolean objects
+        ListAssert.assertEquals(expected, conf.getBooleanList("boolean.list4"));
+
+        // array of boolean primitives
+        ListAssert.assertEquals(expected, conf.getBooleanList("boolean.list5"));
+
+        // list of Boolean objects
+        ListAssert.assertEquals(expected, conf.getBooleanList("boolean.list6"));
+
+        // list of interpolated values
+        ListAssert.assertEquals(expected, conf.getBooleanList("boolean.list.interpolated"));
+
+        // single boolean values
+        expected = new ArrayList<Object>();
+        expected.add(Boolean.TRUE);
+        ListAssert.assertEquals(expected, conf.getBooleanList("boolean.string"));
+        ListAssert.assertEquals(expected, conf.getBooleanList("boolean.object"));
+
+        // empty list
+        ListAssert.assertEquals(new ArrayList<Object>(), conf.getBooleanList("empty"));
+    }
+
+    @Test
+    public void testGetByteArray()
+    {
+        // missing list
+        byte[] defaultValue = new byte[] { 1, 2};
+        ArrayAssert.assertEquals(defaultValue, conf.getByteArray("byte.list", defaultValue));
+
+        byte[] expected = new byte[] { 1, 2 };
+
+        // list of strings
+        ArrayAssert.assertEquals(expected, conf.getByteArray("byte.list1"));
+
+        // list of strings, comma separated
+        ArrayAssert.assertEquals(expected, conf.getByteArray("byte.list2"));
+
+        // list of Byte objects
+        ArrayAssert.assertEquals(expected, conf.getByteArray("byte.list3"));
+
+        // array of Byte objects
+        ArrayAssert.assertEquals(expected, conf.getByteArray("byte.list4"));
+
+        // array of byte primitives
+        ArrayAssert.assertEquals(expected, conf.getByteArray("byte.list5"));
+
+        // list of Byte objects
+        ArrayAssert.assertEquals(expected, conf.getByteArray("byte.list6"));
+
+        // list of interpolated values
+        ArrayAssert.assertEquals(expected, conf.getByteArray("byte.list.interpolated"));
+
+        // single byte values
+        ArrayAssert.assertEquals(new byte[] { 1 }, conf.getByteArray("byte.string"));
+        ArrayAssert.assertEquals(new byte[] { 1 }, conf.getByteArray("byte.object"));
+
+        // empty array
+        ArrayAssert.assertEquals(new byte[] { }, conf.getByteArray("empty"));
+    }
+
+    @Test
+    public void testGetByteList()
+    {
+        // missing list
+        ListAssert.assertEquals(null, conf.getByteList("byte.list", null));
+
+        List<Object> expected = new ArrayList<Object>();
+        expected.add(new Byte("1"));
+        expected.add(new Byte("2"));
+
+        // list of strings
+        ListAssert.assertEquals(expected, conf.getByteList("byte.list1"));
+
+        // list of strings, comma separated
+        ListAssert.assertEquals(expected, conf.getByteList("byte.list2"));
+
+        // list of Byte objects
+        ListAssert.assertEquals(expected, conf.getByteList("byte.list3"));
+
+        // array of Byte objects
+        ListAssert.assertEquals(expected, conf.getByteList("byte.list4"));
+
+        // array of byte primitives
+        ListAssert.assertEquals(expected, conf.getByteList("byte.list5"));
+
+        // list of Byte objects
+        ListAssert.assertEquals(expected, conf.getByteList("byte.list6"));
+
+        // list of interpolated values
+        ListAssert.assertEquals(expected, conf.getByteList("byte.list.interpolated"));
+
+        // single byte values
+        expected = new ArrayList<Object>();
+        expected.add(new Byte("1"));
+        ListAssert.assertEquals(expected, conf.getByteList("byte.string"));
+        ListAssert.assertEquals(expected, conf.getByteList("byte.object"));
+
+        // empty list
+        ListAssert.assertEquals(new ArrayList<Object>(), conf.getByteList("empty"));
+    }
+
+    @Test
+    public void testGetShortArray()
+    {
+        // missing list
+        short[] defaultValue = new short[] { 2, 1};
+        ArrayAssert.assertEquals(defaultValue, conf.getShortArray("short.list", defaultValue));
+
+        short[] expected = new short[] { 1, 2 };
+
+        // list of strings
+        ArrayAssert.assertEquals(expected, conf.getShortArray("short.list1"));
+
+        // list of strings, comma separated
+        ArrayAssert.assertEquals(expected, conf.getShortArray("short.list2"));
+
+        // list of Byte objects
+        ArrayAssert.assertEquals(expected, conf.getShortArray("short.list3"));
+
+        // array of Byte objects
+        ArrayAssert.assertEquals(expected, conf.getShortArray("short.list4"));
+
+        // array of byte primitives
+        ArrayAssert.assertEquals(expected, conf.getShortArray("short.list5"));
+
+        // list of Byte objects
+        ArrayAssert.assertEquals(expected, conf.getShortArray("short.list6"));
+
+        // list of interpolated values
+        ArrayAssert.assertEquals(expected, conf.getShortArray("short.list.interpolated"));
+
+        // single byte values
+        ArrayAssert.assertEquals(new short[] { 1 }, conf.getShortArray("short.string"));
+        ArrayAssert.assertEquals(new short[] { 1 }, conf.getShortArray("short.object"));
+
+        // empty array
+        ArrayAssert.assertEquals(new short[] { }, conf.getShortArray("empty"));
+    }
+
+    @Test
+    public void testGetShortList()
+    {
+        // missing list
+        ListAssert.assertEquals(null, conf.getShortList("short.list", null));
+
+        List<Object> expected = new ArrayList<Object>();
+        expected.add(new Short("1"));
+        expected.add(new Short("2"));
+
+        // list of strings
+        ListAssert.assertEquals(expected, conf.getShortList("short.list1"));
+
+        // list of strings, comma separated
+        ListAssert.assertEquals(expected, conf.getShortList("short.list2"));
+
+        // list of Short objects
+        ListAssert.assertEquals(expected, conf.getShortList("short.list3"));
+
+        // array of Short objects
+        ListAssert.assertEquals(expected, conf.getShortList("short.list4"));
+
+        // array of short primitives
+        ListAssert.assertEquals(expected, conf.getShortList("short.list5"));
+
+        // list of Short objects
+        ListAssert.assertEquals(expected, conf.getShortList("short.list6"));
+
+        // list of interpolated values
+        ListAssert.assertEquals(expected, conf.getShortList("short.list.interpolated"));
+
+        // single short values
+        expected = new ArrayList<Object>();
+        expected.add(new Short("1"));
+        ListAssert.assertEquals(expected, conf.getShortList("short.string"));
+        ListAssert.assertEquals(expected, conf.getShortList("short.object"));
+
+        // empty list
+        ListAssert.assertEquals(new ArrayList<Object>(), conf.getShortList("empty"));
+    }
+
+    @Test
+    public void testGetIntegerArray()
+    {
+        // missing list
+        int[] defaultValue = new int[] { 2, 1};
+        ArrayAssert.assertEquals(defaultValue, conf.getIntArray("integer.list", defaultValue));
+
+        int[] expected = new int[] { 1, 2 };
+
+        // list of strings
+        ArrayAssert.assertEquals(expected, conf.getIntArray("integer.list1"));
+
+        // list of strings, comma separated
+        ArrayAssert.assertEquals(expected, conf.getIntArray("integer.list2"));
+
+        // list of Integer objects
+        ArrayAssert.assertEquals(expected, conf.getIntArray("integer.list3"));
+
+        // array of Integer objects
+        ArrayAssert.assertEquals(expected, conf.getIntArray("integer.list4"));
+
+        // array of int primitives
+        ArrayAssert.assertEquals(expected, conf.getIntArray("integer.list5"));
+
+        // list of Integer objects
+        ArrayAssert.assertEquals(expected, conf.getIntArray("integer.list6"));
+
+        // list of interpolated values
+        ArrayAssert.assertEquals(expected, conf.getIntArray("integer.list.interpolated"));
+
+        // single int values
+        ArrayAssert.assertEquals(new int[] { 1 }, conf.getIntArray("integer.string"));
+        ArrayAssert.assertEquals(new int[] { 1 }, conf.getIntArray("integer.object"));
+
+        // empty array
+        ArrayAssert.assertEquals(new int[] { }, conf.getIntArray("empty"));
+    }
+
+    @Test
+    public void testGetIntegerList()
+    {
+        // missing list
+        ListAssert.assertEquals(null, conf.getIntegerList("integer.list", null));
+
+        List<Object> expected = new ArrayList<Object>();
+        expected.add(new Integer("1"));
+        expected.add(new Integer("2"));
+
+        // list of strings
+        ListAssert.assertEquals(expected, conf.getIntegerList("integer.list1"));
+
+        // list of strings, comma separated
+        ListAssert.assertEquals(expected, conf.getIntegerList("integer.list2"));
+
+        // list of Integer objects
+        ListAssert.assertEquals(expected, conf.getIntegerList("integer.list3"));
+
+        // array of Integer objects
+        ListAssert.assertEquals(expected, conf.getIntegerList("integer.list4"));
+
+        // array of int primitives
+        ListAssert.assertEquals(expected, conf.getIntegerList("integer.list5"));
+
+        // list of Integer objects
+        ListAssert.assertEquals(expected, conf.getIntegerList("integer.list6"));
+
+        // list of interpolated values
+        ListAssert.assertEquals(expected, conf.getIntegerList("integer.list.interpolated"));
+
+        // single int values
+        expected = new ArrayList<Object>();
+        expected.add(new Integer("1"));
+        ListAssert.assertEquals(expected, conf.getIntegerList("integer.string"));
+        ListAssert.assertEquals(expected, conf.getIntegerList("integer.object"));
+
+        // empty list
+        ListAssert.assertEquals(new ArrayList<Object>(), conf.getIntegerList("empty"));
+    }
+
+    @Test
+    public void testGetLongArray()
+    {
+        // missing list
+        long[] defaultValue = new long[] { 2, 1};
+        ArrayAssert.assertEquals(defaultValue, conf.getLongArray("long.list", defaultValue));
+
+        long[] expected = new long[] { 1, 2 };
+
+        // list of strings
+        ArrayAssert.assertEquals(expected, conf.getLongArray("long.list1"));
+
+        // list of strings, comma separated
+        ArrayAssert.assertEquals(expected, conf.getLongArray("long.list2"));
+
+        // list of Long objects
+        ArrayAssert.assertEquals(expected, conf.getLongArray("long.list3"));
+
+        // array of Long objects
+        ArrayAssert.assertEquals(expected, conf.getLongArray("long.list4"));
+
+        // array of long primitives
+        ArrayAssert.assertEquals(expected, conf.getLongArray("long.list5"));
+
+        // list of Long objects
+        ArrayAssert.assertEquals(expected, conf.getLongArray("long.list6"));
+
+        // list of interpolated values
+        ArrayAssert.assertEquals(expected, conf.getLongArray("long.list.interpolated"));
+
+        // single long values
+        ArrayAssert.assertEquals(new long[] { 1 }, conf.getLongArray("long.string"));
+        ArrayAssert.assertEquals(new long[] { 1 }, conf.getLongArray("long.object"));
+
+        // empty array
+        ArrayAssert.assertEquals(new long[] { }, conf.getLongArray("empty"));
+    }
+
+    @Test
+    public void testGetLongList()
+    {
+        // missing list
+        ListAssert.assertEquals(null, conf.getLongList("long.list", null));
+
+        List<Object> expected = new ArrayList<Object>();
+        expected.add(new Long("1"));
+        expected.add(new Long("2"));
+
+        // list of strings
+        ListAssert.assertEquals(expected, conf.getLongList("long.list1"));
+
+        // list of strings, comma separated
+        ListAssert.assertEquals(expected, conf.getLongList("long.list2"));
+
+        // list of Long objects
+        ListAssert.assertEquals(expected, conf.getLongList("long.list3"));
+
+        // array of Long objects
+        ListAssert.assertEquals(expected, conf.getLongList("long.list4"));
+
+        // array of long primitives
+        ListAssert.assertEquals(expected, conf.getLongList("long.list5"));
+
+        // list of Long objects
+        ListAssert.assertEquals(expected, conf.getLongList("long.list6"));
+
+        // list of interpolated values
+        ListAssert.assertEquals(expected, conf.getLongList("long.list.interpolated"));
+
+        // single long values
+        expected = new ArrayList<Object>();
+        expected.add(new Long("1"));
+        ListAssert.assertEquals(expected, conf.getLongList("long.string"));
+        ListAssert.assertEquals(expected, conf.getLongList("long.object"));
+
+        // empty list
+        ListAssert.assertEquals(new ArrayList<Object>(), conf.getLongList("empty"));
+    }
+
+    @Test
+    public void testGetFloatArray()
+    {
+        // missing list
+        float[] defaultValue = new float[] { 2, 1};
+        ArrayAssert.assertEquals(defaultValue, conf.getFloatArray("float.list", defaultValue), 0);
+
+        float[] expected = new float[] { 1, 2 };
+
+        // list of strings
+        ArrayAssert.assertEquals(expected, conf.getFloatArray("float.list1"), 0);
+
+        // list of strings, comma separated
+        ArrayAssert.assertEquals(expected, conf.getFloatArray("float.list2"), 0);
+
+        // list of Float objects
+        ArrayAssert.assertEquals(expected, conf.getFloatArray("float.list3"), 0);
+
+        // array of Float objects
+        ArrayAssert.assertEquals(expected, conf.getFloatArray("float.list4"), 0);
+
+        // array of float primitives
+        ArrayAssert.assertEquals(expected, conf.getFloatArray("float.list5"), 0);
+
+        // list of Float objects
+        ArrayAssert.assertEquals(expected, conf.getFloatArray("float.list6"), 0);
+
+        // list of interpolated values
+        ArrayAssert.assertEquals(expected, conf.getFloatArray("float.list.interpolated"), 0);
+
+        // single float values
+        ArrayAssert.assertEquals(new float[] { 1 }, conf.getFloatArray("float.string"), 0);
+        ArrayAssert.assertEquals(new float[] { 1 }, conf.getFloatArray("float.object"), 0);
+
+        // empty array
+        ArrayAssert.assertEquals(new float[] { }, conf.getFloatArray("empty"), 0);
+    }
+
+    @Test
+    public void testGetFloatList()
+    {
+        // missing list
+        ListAssert.assertEquals(null, conf.getFloatList("float.list", null));
+
+        List<Object> expected = new ArrayList<Object>();
+        expected.add(new Float("1"));
+        expected.add(new Float("2"));
+
+        // list of strings
+        ListAssert.assertEquals(expected, conf.getFloatList("float.list1"));
+
+        // list of strings, comma separated
+        ListAssert.assertEquals(expected, conf.getFloatList("float.list2"));
+
+        // list of Float objects
+        ListAssert.assertEquals(expected, conf.getFloatList("float.list3"));
+
+        // array of Float objects
+        ListAssert.assertEquals(expected, conf.getFloatList("float.list4"));
+
+        // array of float primitives
+        ListAssert.assertEquals(expected, conf.getFloatList("float.list5"));
+
+        // list of Float objects
+        ListAssert.assertEquals(expected, conf.getFloatList("float.list6"));
+
+        // list of interpolated values
+        ListAssert.assertEquals(expected, conf.getFloatList("float.list.interpolated"));
+
+        // single float values
+        expected = new ArrayList<Object>();
+        expected.add(new Float("1"));
+        ListAssert.assertEquals(expected, conf.getFloatList("float.string"));
+        ListAssert.assertEquals(expected, conf.getFloatList("float.object"));
+
+        // empty list
+        ListAssert.assertEquals(new ArrayList<Object>(), conf.getFloatList("empty"));
+    }
+
+    @Test
+    public void testGetDoubleArray()
+    {
+        // missing list
+        double[] defaultValue = new double[] { 2, 1 };
+        ArrayAssert.assertEquals(defaultValue, conf.getDoubleArray("double.list", defaultValue), 0);
+
+        double[] expected = new double[] { 1, 2 };
+
+        // list of strings
+        ArrayAssert.assertEquals(expected, conf.getDoubleArray("double.list1"), 0);
+
+        // list of strings, comma separated
+        ArrayAssert.assertEquals(expected, conf.getDoubleArray("double.list2"), 0);
+
+        // list of Double objects
+        ArrayAssert.assertEquals(expected, conf.getDoubleArray("double.list3"), 0);
+
+        // array of Double objects
+        ArrayAssert.assertEquals(expected, conf.getDoubleArray("double.list4"), 0);
+
+        // array of double primitives
+        ArrayAssert.assertEquals(expected, conf.getDoubleArray("double.list5"), 0);
+
+        // list of Double objects
+        ArrayAssert.assertEquals(expected, conf.getDoubleArray("double.list6"), 0);
+
+        // list of interpolated values
+        ArrayAssert.assertEquals(expected, conf.getDoubleArray("double.list.interpolated"), 0);
+
+        // single double values
+        ArrayAssert.assertEquals(new double[] { 1 }, conf.getDoubleArray("double.string"), 0);
+        ArrayAssert.assertEquals(new double[] { 1 }, conf.getDoubleArray("double.object"), 0);
+
+        // empty array
+        ArrayAssert.assertEquals(new double[] { }, conf.getDoubleArray("empty"), 0);
+    }
+
+    @Test
+    public void testGetDoubleList()
+    {
+        // missing list
+        ListAssert.assertEquals(null, conf.getDoubleList("double.list", null));
+
+        List<Object> expected = new ArrayList<Object>();
+        expected.add(new Double("1"));
+        expected.add(new Double("2"));
+
+        // list of strings
+        ListAssert.assertEquals(expected, conf.getDoubleList("double.list1"));
+
+        // list of strings, comma separated
+        ListAssert.assertEquals(expected, conf.getDoubleList("double.list2"));
+
+        // list of Double objects
+        ListAssert.assertEquals(expected, conf.getDoubleList("double.list3"));
+
+        // array of Double objects
+        ListAssert.assertEquals(expected, conf.getDoubleList("double.list4"));
+
+        // array of double primitives
+        ListAssert.assertEquals(expected, conf.getDoubleList("double.list5"));
+
+        // list of Double objects
+        ListAssert.assertEquals(expected, conf.getDoubleList("double.list6"));
+
+        // list of interpolated values
+        ListAssert.assertEquals(expected, conf.getDoubleList("double.list.interpolated"));
+
+        // single double values
+        expected = new ArrayList<Object>();
+        expected.add(new Double("1"));
+        ListAssert.assertEquals(expected, conf.getDoubleList("double.string"));
+        ListAssert.assertEquals(expected, conf.getDoubleList("double.object"));
+
+        // empty list
+        ListAssert.assertEquals(new ArrayList<Object>(), conf.getDoubleList("empty"));
+    }
+
+    @Test
+    public void testGetBigIntegerArray()
+    {
+        // missing list
+        BigInteger[] defaultValue = new BigInteger[] { new BigInteger("2"), new BigInteger("1") };
+        ArrayAssert.assertEquals(defaultValue, conf.getBigIntegerArray("biginteger.list", defaultValue));
+
+        BigInteger[] expected = new BigInteger[] { new BigInteger("1"), new BigInteger("2") };
+
+        // list of strings
+        ArrayAssert.assertEquals(expected, conf.getBigIntegerArray("biginteger.list1"));
+
+        // list of strings, comma separated
+        ArrayAssert.assertEquals(expected, conf.getBigIntegerArray("biginteger.list2"));
+
+        // list of BigInteger objects
+        ArrayAssert.assertEquals(expected, conf.getBigIntegerArray("biginteger.list3"));
+
+        // array of BigInteger objects
+        ArrayAssert.assertEquals(expected, conf.getBigIntegerArray("biginteger.list4"));
+
+        // list of BigInteger objects
+        ArrayAssert.assertEquals(expected, conf.getBigIntegerArray("biginteger.list6"));
+
+        // list of interpolated values
+        ArrayAssert.assertEquals(expected, conf.getBigIntegerArray("biginteger.list.interpolated"));
+
+        // single BigInteger values
+        ArrayAssert.assertEquals(new BigInteger[] { new BigInteger("1") }, conf.getBigIntegerArray("biginteger.string"));
+        ArrayAssert.assertEquals(new BigInteger[] { new BigInteger("1") }, conf.getBigIntegerArray("biginteger.object"));
+
+        // empty array
+        ArrayAssert.assertEquals(new BigInteger[] { }, conf.getBigIntegerArray("empty"));
+    }
+
+    @Test
+    public void testGetBigIntegerList()
+    {
+        // missing list
+        ListAssert.assertEquals(null, conf.getBigIntegerList("biginteger.list", null));
+
+        List<Object> expected = new ArrayList<Object>();
+        expected.add(new BigInteger("1"));
+        expected.add(new BigInteger("2"));
+
+        // list of strings
+        ListAssert.assertEquals(expected, conf.getBigIntegerList("biginteger.list1"));
+
+        // list of strings, comma separated
+        ListAssert.assertEquals(expected, conf.getBigIntegerList("biginteger.list2"));
+
+        // list of BigInteger objects
+        ListAssert.assertEquals(expected, conf.getBigIntegerList("biginteger.list3"));
+
+        // array of BigInteger objects
+        ListAssert.assertEquals(expected, conf.getBigIntegerList("biginteger.list4"));
+
+        // list of BigInteger objects
+        ListAssert.assertEquals(expected, conf.getBigIntegerList("biginteger.list6"));
+
+        // list of interpolated values
+        ListAssert.assertEquals(expected, conf.getBigIntegerList("biginteger.list.interpolated"));
+
+        // single BigInteger values
+        expected = new ArrayList<Object>();
+        expected.add(new BigInteger("1"));
+        ListAssert.assertEquals(expected, conf.getBigIntegerList("biginteger.string"));
+        ListAssert.assertEquals(expected, conf.getBigIntegerList("biginteger.object"));
+
+        // empty list
+        ListAssert.assertEquals(new ArrayList<Object>(), conf.getBigIntegerList("empty"));
+    }
+
+    @Test
+    public void testGetBigDecimalArray()
+    {
+        // missing list
+        BigDecimal[] defaultValue = new BigDecimal[] { new BigDecimal("2"), new BigDecimal("1") };
+        ArrayAssert.assertEquals(defaultValue, conf.getBigDecimalArray("bigdecimal.list", defaultValue));
+
+        BigDecimal[] expected = new BigDecimal[] { new BigDecimal("1"), new BigDecimal("2") };
+
+        // list of strings
+        ArrayAssert.assertEquals(expected, conf.getBigDecimalArray("bigdecimal.list1"));
+
+        // list of strings, comma separated
+        ArrayAssert.assertEquals(expected, conf.getBigDecimalArray("bigdecimal.list2"));
+
+        // list of BigDecimal objects
+        ArrayAssert.assertEquals(expected, conf.getBigDecimalArray("bigdecimal.list3"));
+
+        // array of BigDecimal objects
+        ArrayAssert.assertEquals(expected, conf.getBigDecimalArray("bigdecimal.list4"));
+
+        // list of BigDecimal objects
+        ArrayAssert.assertEquals(expected, conf.getBigDecimalArray("bigdecimal.list6"));
+
+        // list of interpolated values
+        ArrayAssert.assertEquals(expected, conf.getBigDecimalArray("bigdecimal.list.interpolated"));
+
+        // single BigDecimal values
+        ArrayAssert.assertEquals(new BigDecimal[] { new BigDecimal("1") }, conf.getBigDecimalArray("bigdecimal.string"));
+        ArrayAssert.assertEquals(new BigDecimal[] { new BigDecimal("1") }, conf.getBigDecimalArray("bigdecimal.object"));
+
+        // empty array
+        ArrayAssert.assertEquals(new BigDecimal[] { }, conf.getBigDecimalArray("empty"));
+    }
+
+    @Test
+    public void testGetBigDecimalList()
+    {
+        // missing list
+        ListAssert.assertEquals(null, conf.getBigDecimalList("bigdecimal.list", null));
+
+        List<Object> expected = new ArrayList<Object>();
+        expected.add(new BigDecimal("1"));
+        expected.add(new BigDecimal("2"));
+
+        // list of strings
+        ListAssert.assertEquals(expected, conf.getBigDecimalList("bigdecimal.list1"));
+
+        // list of strings, comma separated
+        ListAssert.assertEquals(expected, conf.getBigDecimalList("bigdecimal.list2"));
+
+        // list of BigDecimal objects
+        ListAssert.assertEquals(expected, conf.getBigDecimalList("bigdecimal.list3"));
+
+        // array of BigDecimal objects
+        ListAssert.assertEquals(expected, conf.getBigDecimalList("bigdecimal.list4"));
+
+        // list of BigDecimal objects
+        ListAssert.assertEquals(expected, conf.getBigDecimalList("bigdecimal.list6"));
+
+        // list of interpolated values
+        ListAssert.assertEquals(expected, conf.getBigDecimalList("bigdecimal.list.interpolated"));
+
+        // single BigDecimal values
+        expected = new ArrayList<Object>();
+        expected.add(new BigDecimal("1"));
+        ListAssert.assertEquals(expected, conf.getBigDecimalList("bigdecimal.string"));
+        ListAssert.assertEquals(expected, conf.getBigDecimalList("bigdecimal.object"));
+
+        // empty list
+        ListAssert.assertEquals(new ArrayList<Object>(), conf.getBigDecimalList("empty"));
+    }
+
+    @Test
+    public void testGetURL() throws Exception
+    {
+        // missing URL
+        URL defaultValue = new URL("http://www.google.com");
+        assertEquals(defaultValue, conf.getURL("url", defaultValue));
+
+        URL expected = new URL("http://jakarta.apache.org");
+
+        // URL string
+        assertEquals(expected, conf.getURL("url.string"));
+
+        // URL object
+        assertEquals(expected, conf.getURL("url.object"));
+
+        // interpolated value
+        assertEquals(expected, conf.getURL("url.string.interpolated"));
+    }
+
+    @Test
+    public void testGetURLArray() throws Exception
+    {
+        // missing list
+        URL[] defaultValue = new URL[] { new URL("http://www.apache.org"), new URL("http://jakarta.apache.org") };
+        ArrayAssert.assertEquals(defaultValue, conf.getURLArray("url.list", defaultValue));
+
+        URL[] expected = new URL[] { new URL("http://jakarta.apache.org"), new URL("http://www.apache.org") };
+
+        // list of strings
+        ArrayAssert.assertEquals(expected, conf.getURLArray("url.list1"));
+
+        // list of strings, comma separated
+        ArrayAssert.assertEquals(expected, conf.getURLArray("url.list2"));
+
+        // list of URL objects
+        ArrayAssert.assertEquals(expected, conf.getURLArray("url.list3"));
+
+        // array of URL objects
+        ArrayAssert.assertEquals(expected, conf.getURLArray("url.list4"));
+
+        // list of URL objects
+        ArrayAssert.assertEquals(expected, conf.getURLArray("url.list6"));
+
+        // list of interpolated values
+        ArrayAssert.assertEquals(expected, conf.getURLArray("url.list.interpolated"));
+
+        // single URL values
+        ArrayAssert.assertEquals(new URL[] { new URL("http://jakarta.apache.org") }, conf.getURLArray("url.string"));
+        ArrayAssert.assertEquals(new URL[] { new URL("http://jakarta.apache.org") }, conf.getURLArray("url.object"));
+
+        // empty array
+        ArrayAssert.assertEquals(new URL[] { }, conf.getURLArray("empty"));
+    }
+
+    @Test
+    public void testGetURLList() throws Exception
+    {
+        // missing list
+        ListAssert.assertEquals(null, conf.getURLList("url.list", null));
+
+        List<Object> expected = new ArrayList<Object>();
+        expected.add(new URL("http://jakarta.apache.org"));
+        expected.add(new URL("http://www.apache.org"));
+
+        // list of strings
+        ListAssert.assertEquals(expected, conf.getURLList("url.list1"));
+
+        // list of strings, comma separated
+        ListAssert.assertEquals(expected, conf.getURLList("url.list2"));
+
+        // list of URL objects
+        ListAssert.assertEquals(expected, conf.getURLList("url.list3"));
+
+        // array of URL objects
+        ListAssert.assertEquals(expected, conf.getURLList("url.list4"));
+
+        // list of URL objects
+        ListAssert.assertEquals(expected, conf.getURLList("url.list6"));
+
+        // list of interpolated values
+        ListAssert.assertEquals(expected, conf.getURLList("url.list.interpolated"));
+
+        // single URL values
+        expected = new ArrayList<Object>();
+        expected.add(new URL("http://jakarta.apache.org"));
+        ListAssert.assertEquals(expected, conf.getURLList("url.string"));
+        ListAssert.assertEquals(expected, conf.getURLList("url.object"));
+
+        // empty list
+        ListAssert.assertEquals(new ArrayList<Object>(), conf.getURLList("empty"));
+    }
+
+    @Test
+    public void testGetLocale()
+    {
+        // language
+        conf.setProperty("locale", "fr");
+        assertEquals("language", new Locale("fr", ""), conf.getLocale("locale"));
+
+        // language + variant
+        conf.setProperty("locale", "fr__POSIX");
+        assertEquals("language + variant", new Locale("fr", "", "POSIX"), conf.getLocale("locale"));
+
+        // country
+        conf.setProperty("locale", "_FR");
+        assertEquals("country", new Locale("", "FR"), conf.getLocale("locale"));
+
+        // country + variant
+        conf.setProperty("locale", "_FR_WIN");
+        assertEquals("country + variant", new Locale("", "FR", "WIN"), conf.getLocale("locale"));
+
+        // language + country
+        conf.setProperty("locale", "fr_FR");
+        assertEquals("language + country", new Locale("fr", "FR"), conf.getLocale("locale"));
+
+        // language + country + variant
+        conf.setProperty("locale", "fr_FR_MAC");
+        assertEquals("language + country + variant", new Locale("fr", "FR", "MAC"), conf.getLocale("locale"));
+
+        // default value
+        conf.setProperty("locale", "fr");
+        assertEquals("Existing key with default value", Locale.FRENCH, conf.getLocale("locale", Locale.GERMAN));
+        assertEquals("Missing key with default value", Locale.GERMAN, conf.getLocale("localeNotInConfig", Locale.GERMAN));
+
+        // interpolated value
+        assertEquals(Locale.FRENCH, conf.getLocale("locale.string.interpolated"));
+    }
+
+    @Test
+    public void testGetLocaleArray() throws Exception
+    {
+        // missing list
+        Locale[] defaultValue = new Locale[] { Locale.GERMAN, Locale.FRENCH };
+        ArrayAssert.assertEquals(defaultValue, conf.getLocaleArray("locale.list", defaultValue));
+
+        Locale[] expected = new Locale[] { Locale.FRENCH, Locale.GERMAN };
+
+        // list of strings
+        ArrayAssert.assertEquals(expected, conf.getLocaleArray("locale.list1"));
+
+        // list of strings, comma separated
+        ArrayAssert.assertEquals(expected, conf.getLocaleArray("locale.list2"));
+
+        // list of Locale objects
+        ArrayAssert.assertEquals(expected, conf.getLocaleArray("locale.list3"));
+
+        // array of Locale objects
+        ArrayAssert.assertEquals(expected, conf.getLocaleArray("locale.list4"));
+
+        // list of Locale objects
+        ArrayAssert.assertEquals(expected, conf.getLocaleArray("locale.list6"));
+
+        // list of interpolated values
+        ArrayAssert.assertEquals(expected, conf.getLocaleArray("locale.list.interpolated"));
+
+        // single Locale values
+        ArrayAssert.assertEquals(new Locale[] { Locale.FRENCH }, conf.getLocaleArray("locale.string"));
+        ArrayAssert.assertEquals(new Locale[] { Locale.FRENCH }, conf.getLocaleArray("locale.object"));
+
+        // empty array
+        ArrayAssert.assertEquals(new Locale[] { }, conf.getLocaleArray("empty"));
+    }
+
+    @Test
+    public void testGetLocaleList() throws Exception
+    {
+        // missing list
+        ListAssert.assertEquals(null, conf.getLocaleList("locale.list", null));
+
+        List<Object> expected = new ArrayList<Object>();
+        expected.add(Locale.FRENCH);
+        expected.add(Locale.GERMAN);
+
+        // list of strings
+        ListAssert.assertEquals(expected, conf.getLocaleList("locale.list1"));
+
+        // list of strings, comma separated
+        ListAssert.assertEquals(expected, conf.getLocaleList("locale.list2"));
+
+        // list of Locale objects
+        ListAssert.assertEquals(expected, conf.getLocaleList("locale.list3"));
+
+        // array of Locale objects
+        ListAssert.assertEquals(expected, conf.getLocaleList("locale.list4"));
+
+        // list of Locale objects
+        ListAssert.assertEquals(expected, conf.getLocaleList("locale.list6"));
+
+        // list of interpolated values
+        ListAssert.assertEquals(expected, conf.getLocaleList("locale.list.interpolated"));
+
+        // single Locale values
+        expected = new ArrayList<Object>();
+        expected.add(Locale.FRENCH);
+        ListAssert.assertEquals(expected, conf.getLocaleList("locale.string"));
+        ListAssert.assertEquals(expected, conf.getLocaleList("locale.object"));
+
+        // empty list
+        ListAssert.assertEquals(new ArrayList<Object>(), conf.getLocaleList("empty"));
+    }
+
+    @Test
+    public void testGetColor()
+    {
+        // RRGGBB
+        conf.setProperty("color", "FF0000");
+        assertEquals("color", Color.red, conf.getColor("color"));
+
+        // #RRGGBB
+        conf.setProperty("color", "#00FF00");
+        assertEquals("color", Color.green, conf.getColor("color"));
+
+        // #RRGGBBAA
+        conf.setProperty("color", "#01030507");
+        Color color = conf.getColor("color");
+        assertNotNull("null color", color);
+        assertEquals("red",   1, color.getRed());
+        assertEquals("green", 3, color.getGreen());
+        assertEquals("blue",  5, color.getBlue());
+        assertEquals("alpha", 7, color.getAlpha());
+
+        // interpolated value
+        assertEquals(Color.red, conf.getColor("color.string.interpolated"));
+
+        // default value
+        assertEquals(Color.cyan, conf.getColor("unknownkey", Color.cyan));
+    }
+
+    @Test
+    public void testGetColorArray() throws Exception
+    {
+        // missing list
+        Color[] defaultValue = new Color[] { Color.red, Color.blue };
+        ArrayAssert.assertEquals(defaultValue, conf.getColorArray("color.list", defaultValue));
+
+        Color[] expected = new Color[] { Color.red, Color.blue };
+
+        // list of strings
+        ArrayAssert.assertEquals(expected, conf.getColorArray("color.list1"));
+
+        // list of strings, comma separated
+        ArrayAssert.assertEquals(expected, conf.getColorArray("color.list2"));
+
+        // list of Color objects
+        ArrayAssert.assertEquals(expected, conf.getColorArray("color.list3"));
+
+        // array of Color objects
+        ArrayAssert.assertEquals(expected, conf.getColorArray("color.list4"));
+
+        // list of Color objects
+        ArrayAssert.assertEquals(expected, conf.getColorArray("color.list6"));
+
+        // list of interpolated values
+        ArrayAssert.assertEquals(expected, conf.getColorArray("color.list.interpolated"));
+
+        // single Color values
+        ArrayAssert.assertEquals(new Color[] { Color.red }, conf.getColorArray("color.string"));
+        ArrayAssert.assertEquals(new Color[] { Color.red }, conf.getColorArray("color.object"));
+
+        // empty array
+        ArrayAssert.assertEquals(new Color[] { }, conf.getColorArray("empty"));
+    }
+
+    @Test
+    public void testGetColorList() throws Exception
+    {
+        // missing list
+        ListAssert.assertEquals(null, conf.getColorList("color.list", null));
+
+        List<Object> expected = new ArrayList<Object>();
+        expected.add(Color.red);
+        expected.add(Color.blue);
+
+        // list of strings
+        ListAssert.assertEquals(expected, conf.getColorList("color.list1"));
+
+        // list of strings, comma separated
+        ListAssert.assertEquals(expected, conf.getColorList("color.list2"));
+
+        // list of Color objects
+        ListAssert.assertEquals(expected, conf.getColorList("color.list3"));
+
+        // array of Color objects
+        ListAssert.assertEquals(expected, conf.getColorList("color.list4"));
+
+        // list of Color objects
+        ListAssert.assertEquals(expected, conf.getColorList("color.list6"));
+
+        // list of interpolated values
+        ListAssert.assertEquals(expected, conf.getColorList("color.list.interpolated"));
+
+        // single Color values
+        expected = new ArrayList<Object>();
+        expected.add(Color.red);
+        ListAssert.assertEquals(expected, conf.getColorList("color.string"));
+        ListAssert.assertEquals(expected, conf.getColorList("color.object"));
+
+        // empty list
+        ListAssert.assertEquals(new ArrayList<Object>(), conf.getColorList("empty"));
+    }
+
+    @Test
+    public void testGetDate() throws Exception
+    {
+        DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+
+        // missing Date
+        Date defaultValue = new Date();
+        assertEquals(defaultValue, conf.getDate("date", defaultValue));
+        assertNull("non null object for a missing key", conf.getDate("unknownkey", "yyyy-MM-dd"));
+
+        conf.setThrowExceptionOnMissing(true);
+
+        try
+        {
+            conf.getDate("unknownkey", "yyyy-MM-dd");
+            fail("NoSuchElementException should be thrown for missing properties");
+        }
+        catch (NoSuchElementException e)
+        {
+            // expected
+        }
+
+        Date expected = format.parse("2004-01-01");
+
+        // Date string
+        assertEquals(expected, conf.getDate("date.string"));
+        assertEquals(expected, conf.getDate("date.string", "yyyy-MM-dd"));
+
+        // Date object
+        assertEquals(expected, conf.getDate("date.object"));
+
+        // Calendar object
+        assertEquals(expected, conf.getDate("calendar.object"));
+
+        // interpolated value
+        assertEquals(expected, conf.getDate("date.string.interpolated"));
+    }
+
+    @Test
+    public void testGetDateArray() throws Exception
+    {
+        DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+        Date date1 = format.parse("2004-01-01");
+        Date date2 = format.parse("2004-12-31");
+
+        // missing list
+        Date[] defaultValue = new Date[] { date2, date1 };
+        ArrayAssert.assertEquals(defaultValue, conf.getDateArray("date.list", defaultValue));
+
+        Date[] expected = new Date[] { date1, date2 };
+
+        // list of strings
+        ArrayAssert.assertEquals(expected, conf.getDateArray("date.list1"));
+
+        // list of strings, comma separated
+        ArrayAssert.assertEquals(expected, conf.getDateArray("date.list2"));
+
+        // list of Date objects
+        ArrayAssert.assertEquals(expected, conf.getDateArray("date.list3"));
+
+        // array of Date objects
+        ArrayAssert.assertEquals(expected, conf.getDateArray("date.list4"));
+
+        // list of Calendar objects
+        ArrayAssert.assertEquals(expected, conf.getDateArray("date.list5"));
+
+        // list of Date objects
+        ArrayAssert.assertEquals(expected, conf.getDateArray("date.list6"));
+
+        // list of interpolated values
+        ArrayAssert.assertEquals(expected, conf.getDateArray("date.list.interpolated"));
+
+        // single Date values
+        ArrayAssert.assertEquals(new Date[] { date1 }, conf.getDateArray("date.string"));
+        ArrayAssert.assertEquals(new Date[] { date1 }, conf.getDateArray("date.object"));
+
+        // empty array
+        ArrayAssert.assertEquals(new Date[] { }, conf.getDateArray("empty"));
+    }
+
+    @Test
+    public void testGetDateArrayWithFormat() throws Exception
+    {
+        DateFormat format = new SimpleDateFormat("MM/dd/yyyy");
+        Date date1 = format.parse("01/01/2004");
+        Date date2 = format.parse("12/31/2004");
+        Date[] expected = new Date[] { date1, date2 };
+
+        conf.addProperty("date.format", "01/01/2004");
+        conf.addProperty("date.format", "12/31/2004");
+        ArrayAssert.assertEquals("Wrong dates with format", expected, conf.getDateArray("date.format", "MM/dd/yyyy"));
+    }
+
+    @Test
+    public void testGetDateList() throws Exception
+    {
+        DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+        Date date1 = format.parse("2004-01-01");
+        Date date2 = format.parse("2004-12-31");
+
+        // missing list
+        List<Date> nullList = null;
+        ListAssert.assertEquals(null, conf.getDateList("date.list", nullList));
+
+        List<Object> expected = new ArrayList<Object>();
+        expected.add(date1);
+        expected.add(date2);
+
+        // list of strings
+        ListAssert.assertEquals(expected, conf.getDateList("date.list1"));
+        ListAssert.assertEquals(expected, conf.getList(Date.class, "date.list1"));
+
+        // list of strings, comma separated
+        ListAssert.assertEquals(expected, conf.getDateList("date.list2"));
+
+        // list of Date objects
+        ListAssert.assertEquals(expected, conf.getDateList("date.list3"));
+
+        // array of Date objects
+        ListAssert.assertEquals(expected, conf.getDateList("date.list4"));
+
+        // list of Calendar objects
+        ListAssert.assertEquals(expected, conf.getDateList("date.list5"));
+
+        // list of Date objects
+        ListAssert.assertEquals(expected, conf.getDateList("date.list6"));
+
+        // array of strings
+        ListAssert.assertEquals(expected, conf.getList(Date.class, "date.list7"));
+
+        // list of interpolated values
+        ListAssert.assertEquals(expected, conf.getDateList("date.list.interpolated"));
+
+        // single Date values
+        expected = new ArrayList<Object>();
+        expected.add(date1);
+        ListAssert.assertEquals(expected, conf.getDateList("date.string"));
+        ListAssert.assertEquals(expected, conf.getDateList("date.object"));
+
+        // empty list
+        ListAssert.assertEquals(new ArrayList<Object>(), conf.getDateList("empty"));
+    }
+
+    @Test
+    public void testGetCalendar() throws Exception
+    {
+        DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+
+        // missing Date
+        Calendar defaultValue = Calendar.getInstance();
+        defaultValue.setTime(new Date());
+        assertEquals(defaultValue, conf.getCalendar("calendar", defaultValue));
+        assertNull("non null object for a missing key", conf.getCalendar("unknownkey", "yyyy-MM-dd"));
+
+        conf.setThrowExceptionOnMissing(true);
+
+        try
+        {
+            conf.getCalendar("unknownkey", "yyyy-MM-dd");
+            fail("NoSuchElementException should be thrown for missing properties");
+        }
+        catch (NoSuchElementException e)
+        {
+            // expected
+        }
+
+        Calendar expected = Calendar.getInstance();
+        expected.setTime(format.parse("2004-01-01"));
+
+        // Calendar string
+        assertEquals(expected, conf.getCalendar("calendar.string"));
+        assertEquals(expected, conf.getCalendar("calendar.string",  "yyyy-MM-dd"));
+
+        // Calendar object
+        assertEquals(expected, conf.getCalendar("calendar.object"));
+
+        // Date object
+        assertEquals(expected, conf.getCalendar("date.object"));
+
+        // interpolated value
+        assertEquals(expected, conf.getCalendar("calendar.string.interpolated"));
+    }
+
+    @Test
+    public void testGetCalendarArray() throws Exception
+    {
+        DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+        Date date1 = format.parse("2004-01-01");
+        Date date2 = format.parse("2004-12-31");
+        Calendar calendar1 = Calendar.getInstance();
+        calendar1.setTime(date1);
+        Calendar calendar2 = Calendar.getInstance();
+        calendar2.setTime(date2);
+
+        // missing list
+        Calendar[] defaultValue = new Calendar[] { calendar2, calendar1 };
+        ArrayAssert.assertEquals(defaultValue, conf.getCalendarArray("calendar.list", defaultValue));
+
+        Calendar[] expected = new Calendar[] { calendar1, calendar2 };
+
+        // list of strings
+        ArrayAssert.assertEquals(expected, conf.getCalendarArray("calendar.list1"));
+
+        // list of strings, comma separated
+        ArrayAssert.assertEquals(expected, conf.getCalendarArray("calendar.list2"));
+
+        // list of Calendar objects
+        ArrayAssert.assertEquals(expected, conf.getCalendarArray("calendar.list3"));
+
+        // array of Calendar objects
+        ArrayAssert.assertEquals(expected, conf.getCalendarArray("calendar.list4"));
+
+        // list of Date objects
+        ArrayAssert.assertEquals(expected, conf.getCalendarArray("calendar.list5"));
+
+        // list of Calendar objects
+        ArrayAssert.assertEquals(expected, conf.getCalendarArray("calendar.list6"));
+
+        // list of interpolated values
+        ArrayAssert.assertEquals(expected, conf.getCalendarArray("calendar.list.interpolated"));
+
+        // single Calendar values
+        ArrayAssert.assertEquals(new Calendar[] { calendar1 }, conf.getCalendarArray("calendar.string"));
+        ArrayAssert.assertEquals(new Calendar[] { calendar1 }, conf.getCalendarArray("calendar.object"));
+
+        // empty array
+        ArrayAssert.assertEquals(new Calendar[] { }, conf.getCalendarArray("empty"));
+    }
+
+    @Test
+    public void testGetCalendarArrayWithFormat() throws Exception
+    {
+        DateFormat format = new SimpleDateFormat("MM/dd/yyyy");
+        Date date1 = format.parse("01/01/2004");
+        Date date2 = format.parse("12/31/2004");
+
+        Calendar calendar1 = Calendar.getInstance();
+        calendar1.setTime(date1);
+        Calendar calendar2 = Calendar.getInstance();
+        calendar2.setTime(date2);
+        Calendar[] expected = new Calendar[] { calendar1, calendar2 };
+
+        conf.addProperty("calendar.format", "01/01/2004");
+        conf.addProperty("calendar.format", "12/31/2004");
+        ArrayAssert.assertEquals("Wrong calendars with format", expected, conf.getCalendarArray("calendar.format", "MM/dd/yyyy"));
+    }
+
+    @Test
+    public void testGetCalendarList() throws Exception
+    {
+        DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+        Date date1 = format.parse("2004-01-01");
+        Date date2 = format.parse("2004-12-31");
+        Calendar calendar1 = Calendar.getInstance();
+        calendar1.setTime(date1);
+        Calendar calendar2 = Calendar.getInstance();
+        calendar2.setTime(date2);
+
+        // missing list
+        List<Calendar> nullList = null;
+        ListAssert.assertEquals(null, conf.getCalendarList("calendar.list", nullList));
+
+        List<Object> expected = new ArrayList<Object>();
+        expected.add(calendar1);
+        expected.add(calendar2);
+
+        // list of strings
+        ListAssert.assertEquals(expected, conf.getCalendarList("calendar.list1"));
+        ListAssert.assertEquals(expected, conf.getList(Calendar.class, "calendar.list1"));
+
+        // list of strings, comma separated
+        ListAssert.assertEquals(expected, conf.getCalendarList("calendar.list2"));
+
+        // list of Calendar objects
+        ListAssert.assertEquals(expected, conf.getCalendarList("calendar.list3"));
+
+        // array of Calendar objects
+        ListAssert.assertEquals(expected, conf.getCalendarList("calendar.list4"));
+
+        // list of Date objects
+        ListAssert.assertEquals(expected, conf.getCalendarList("calendar.list5"));
+
+        // list of Calendar objects
+        ListAssert.assertEquals(expected, conf.getCalendarList("calendar.list6"));
+
+        // array of strings
+        ListAssert.assertEquals(expected, conf.getList(Calendar.class, "calendar.list7"));
+
+        // list of interpolated values
+        ListAssert.assertEquals(expected, conf.getCalendarList("calendar.list.interpolated"));
+
+        // single Calendar values
+        expected = new ArrayList<Object>();
+        expected.add(calendar1);
+        ListAssert.assertEquals(expected, conf.getCalendarList("date.string"));
+        ListAssert.assertEquals(expected, conf.getCalendarList("date.object"));
+
+        // empty list
+        ListAssert.assertEquals(new ArrayList<Object>(), conf.getCalendarList("empty"));
+    }
+
+    @Test
+    public void testGetInetAddress() throws Exception
+    {
+        InetAddress expected = InetAddress.getByName("127.0.0.1");
+
+        // address as string
+        assertEquals(expected, conf.get(InetAddress.class, "ip.string"));
+
+        // address object
+        assertEquals(expected, conf.get(InetAddress.class, "ip.object"));
+
+        // interpolated value
+        assertEquals(expected, conf.get(InetAddress.class, "ip.string.interpolated"));
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testGetInetAddressInvalidType()
+    {
+        conf.setProperty("ip.unknownhost", "random-foo-that-does-not-exist.ever");
+        conf.get(InetAddress.class, "ip.unknownhost");
+    }
+
+    @Test
+    public void testGetInternetAddress() throws Exception
+    {
+        Object expected = createInternetAddress("ebourg at apache.org");
+
+        // address as string
+        assertEquals(expected, conf.get(expected.getClass(), "email.string"));
+
+        // address object
+        assertEquals(expected, conf.get(expected.getClass(), "email.object"));
+
+        // interpolated value
+        assertEquals(expected, conf.get(expected.getClass(), "email.string.interpolated"));
+
+        conf.setProperty("email.invalid", "ebourg at apache@org");
+        try
+        {
+            conf.get(expected.getClass(), "email.invalid");
+            fail("ConversionException should be thrown for invalid emails");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testGetInternetAddressInvalidType() throws Exception
+    {
+        Object expected = createInternetAddress("ebourg at apache.org");
+        conf.setProperty("email.invalid", "ebourg at apache@org");
+        conf.get(expected.getClass(), "email.invalid");
+    }
+
+    /**
+     * Create an instance of InternetAddress. This trick is necessary to
+     * compile and run the test with Java 1.3 and the javamail-1.4 which
+     * is not compatible with Java 1.3
+     */
+    private Object createInternetAddress(String email) throws Exception
+    {
+        Class<?> cls = Class.forName("javax.mail.internet.InternetAddress");
+        return cls.getConstructor(new Class[]{String.class}).newInstance(new Object[]{email});
+    }
+
+    @Test
+    public void testConversionException() throws Exception
+    {
+        conf.addProperty("key1", new Object());
+        conf.addProperty("key2", "xxxxxx");
+
+        try
+        {
+            conf.getBooleanArray("key1");
+            fail("getBooleanArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getBooleanArray("key2");
+            fail("getBooleanArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getBooleanList("key1");
+            fail("getBooleanList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getBooleanList("key2");
+            fail("getBooleanList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getByteArray("key1");
+            fail("getByteArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getByteArray("key2");
+            fail("getByteArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getByteList("key1");
+            fail("getByteList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getByteList("key2");
+            fail("getByteList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getShortArray("key1");
+            fail("getShortArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getShortArray("key2");
+            fail("getShortArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getShortList("key1");
+            fail("getShortList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getShortList("key2");
+            fail("getShortList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getIntArray("key1");
+            fail("getIntArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getIntArray("key2");
+            fail("getIntArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getIntegerList("key1");
+            fail("getIntegerList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getIntegerList("key2");
+            fail("getIntegerList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getLongArray("key1");
+            fail("getLongArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getLongArray("key2");
+            fail("getLongArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getLongList("key1");
+            fail("getLongList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getLongList("key2");
+            fail("getLongList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getFloatArray("key1");
+            fail("getFloatArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getFloatArray("key2");
+            fail("getFloatArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getFloatList("key1");
+            fail("getFloatList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getFloatList("key2");
+            fail("getFloatList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getDoubleArray("key1");
+            fail("getDoubleArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getDoubleArray("key2");
+            fail("getDoubleArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getDoubleList("key1");
+            fail("getDoubleList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getDoubleList("key2");
+            fail("getDoubleList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getBigIntegerArray("key1");
+            fail("getBigIntegerArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getBigIntegerArray("key2");
+            fail("getBigIntegerArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getBigIntegerList("key1");
+            fail("getBigIntegerList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getBigIntegerList("key2");
+            fail("getBigIntegerList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getBigDecimalArray("key1");
+            fail("getBigDecimalArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getBigDecimalArray("key2");
+            fail("getBigDecimalArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getBigDecimalList("key1");
+            fail("getBigDecimalList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getBigDecimalList("key2");
+            fail("getBigDecimalList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getURLArray("key1");
+            fail("getURLArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getURLArray("key2");
+            fail("getURLArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getURLList("key1");
+            fail("getURLList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getURLList("key2");
+            fail("getURLList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getLocaleArray("key1");
+            fail("getLocaleArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getLocaleArray("key2");
+            fail("getLocaleArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getLocaleList("key1");
+            fail("getLocaleList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getLocaleList("key2");
+            fail("getLocaleList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getColorArray("key1");
+            fail("getColorArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getColorArray("key2");
+            fail("getColorArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getColorList("key1");
+            fail("getColorList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getColorList("key2");
+            fail("getColorList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getDateArray("key1");
+            fail("getDateArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getDate("key1", "yyyy-MM-dd");
+            fail("getDate didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getDate("key2", "yyyy-MM-dd");
+            fail("getDate didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getDateArray("key2");
+            fail("getDateArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getDateList("key1");
+            fail("getDateList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getDateList("key2");
+            fail("getDateList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getCalendar("key1", "yyyy-MM-dd");
+            fail("getCalendar didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getCalendar("key2","yyyy-MM-dd");
+            fail("getCalendar didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getCalendarArray("key1");
+            fail("getCalendarArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getCalendarArray("key2");
+            fail("getCalendarArray didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getCalendarList("key1");
+            fail("getCalendarList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.getCalendarList("key2");
+            fail("getCalendarList didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.get(InetAddress.class, "key1");
+            fail("getInetAddress didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            conf.get(Class.forName("javax.mail.internet.InternetAddress"), "key1");
+            fail("getInternetAddress didn't throw a ConversionException");
+        }
+        catch (ConversionException e)
+        {
+            // expected
+        }
+    }
+
+    /**
+     * Tests whether a string property can be obtained through get() if no type
+     * conversion is required.
+     */
+    @Test
+    public void testGetPropertyWithoutConversion()
+    {
+        String key = "test.str";
+        String value = "someTestValue";
+        conf.addProperty(key, value);
+        assertEquals("Wrong result", value, conf.get(String.class, key));
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestDatabaseConfiguration.java b/src/test/java/org/apache/commons/configuration/TestDatabaseConfiguration.java
new file mode 100644
index 0000000..1add7cb
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestDatabaseConfiguration.java
@@ -0,0 +1,551 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test for database stored configurations.  Note, when running this Unit
+ * Test in Eclipse it sometimes takes a couple tries. Otherwise you may get
+ * database is already in use by another process errors.
+ *
+ * @version $Id: TestDatabaseConfiguration.java 1223016 2011-12-24 20:56:52Z oheger $
+ */
+public class TestDatabaseConfiguration
+{
+    /** Constant for another configuration name. */
+    private static final String CONFIG_NAME2 = "anotherTestConfig";
+
+    /** An error listener for testing whether internal errors occurred.*/
+    private ConfigurationErrorListenerImpl listener;
+
+    /** The test helper. */
+    private DatabaseConfigurationTestHelper helper;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        /*
+         * Thread.sleep may or may not help with the database is already in
+         * use exception.
+         */
+        //Thread.sleep(1000);
+
+        // set up the datasource
+
+        helper = new DatabaseConfigurationTestHelper();
+        helper.setUp();
+    }
+
+    @After
+    public void tearDown() throws Exception
+    {
+        // if an error listener is defined, we check whether an error occurred
+        if(listener != null)
+        {
+            assertEquals("An internal error occurred", 0, listener.getErrorCount());
+        }
+        helper.tearDown();
+    }
+
+    /**
+     * Creates a database configuration with default values.
+     *
+     * @return the configuration
+     */
+    private PotentialErrorDatabaseConfiguration setUpConfig()
+    {
+        return new PotentialErrorDatabaseConfiguration(helper.getDatasource(),
+                DatabaseConfigurationTestHelper.TABLE,
+                DatabaseConfigurationTestHelper.COL_KEY,
+                DatabaseConfigurationTestHelper.COL_VALUE);
+    }
+
+    /**
+     * Creates an error listener and adds it to the specified configuration.
+     *
+     * @param config the configuration
+     */
+    private void setUpErrorListener(PotentialErrorDatabaseConfiguration config)
+    {
+        // remove log listener to avoid exception longs
+        config.removeErrorListener(config.getErrorListeners().iterator().next());
+        listener = new ConfigurationErrorListenerImpl();
+        config.addErrorListener(listener);
+        config.failOnConnect = true;
+    }
+
+    /**
+     * Prepares a test for a database error. Sets up a config and registers an
+     * error listener.
+     *
+     * @return the initialized configuration
+     */
+    private PotentialErrorDatabaseConfiguration setUpErrorConfig()
+    {
+        PotentialErrorDatabaseConfiguration config = setUpConfig();
+        setUpErrorListener(config);
+        return config;
+    }
+
+    /**
+     * Checks the error listener for an expected error. The properties of the
+     * error event will be compared with the expected values.
+     *
+     * @param type the expected type of the error event
+     * @param key the expected property key
+     * @param value the expected property value
+     */
+    private void checkErrorListener(int type, String key, Object value)
+    {
+        listener.verify(type, key, value);
+        assertTrue(
+                "Wrong event source",
+                listener.getLastEvent().getSource() instanceof DatabaseConfiguration);
+        assertTrue("Wrong exception",
+                listener.getLastEvent().getCause() instanceof SQLException);
+        listener = null; // mark as checked
+    }
+
+    /**
+     * Tests the default value of the doCommits property.
+     */
+    @Test
+    public void testDoCommitsDefault()
+    {
+        DatabaseConfiguration config = new DatabaseConfiguration(helper
+                .getDatasource(), DatabaseConfigurationTestHelper.TABLE,
+                DatabaseConfigurationTestHelper.COL_KEY,
+                DatabaseConfigurationTestHelper.COL_VALUE);
+        assertFalse("Wrong commits flag", config.isDoCommits());
+    }
+
+    /**
+     * Tests the default value of the doCommits property for multiple
+     * configurations in a table.
+     */
+    @Test
+    public void testDoCommitsDefaultMulti()
+    {
+        DatabaseConfiguration config = new DatabaseConfiguration(helper
+                .getDatasource(), DatabaseConfigurationTestHelper.TABLE,
+                DatabaseConfigurationTestHelper.COL_NAME,
+                DatabaseConfigurationTestHelper.COL_KEY,
+                DatabaseConfigurationTestHelper.COL_VALUE,
+                DatabaseConfigurationTestHelper.CONFIG_NAME);
+        assertFalse("Wrong commits flag", config.isDoCommits());
+    }
+
+    @Test
+    public void testAddPropertyDirectSingle()
+    {
+        DatabaseConfiguration config = helper.setUpConfig();
+        config.addPropertyDirect("key", "value");
+
+        assertTrue("missing property", config.containsKey("key"));
+    }
+
+    /**
+     * Tests whether a commit is performed after a property was added.
+     */
+    @Test
+    public void testAddPropertyDirectCommit()
+    {
+        helper.setAutoCommit(false);
+        DatabaseConfiguration config = helper.setUpConfig();
+        config.addPropertyDirect("key", "value");
+        assertTrue("missing property", config.containsKey("key"));
+    }
+
+    @Test
+    public void testAddPropertyDirectMultiple()
+    {
+        DatabaseConfiguration config = helper.setUpMultiConfig();
+        config.addPropertyDirect("key", "value");
+
+        assertTrue("missing property", config.containsKey("key"));
+    }
+
+    @Test
+    public void testAddNonStringProperty()
+    {
+        DatabaseConfiguration config = helper.setUpConfig();
+        config.addPropertyDirect("boolean", Boolean.TRUE);
+
+        assertTrue("missing property", config.containsKey("boolean"));
+    }
+
+    @Test
+    public void testGetPropertyDirectSingle()
+    {
+        Configuration config = setUpConfig();
+
+        assertEquals("property1", "value1", config.getProperty("key1"));
+        assertEquals("property2", "value2", config.getProperty("key2"));
+        assertEquals("unknown property", null, config.getProperty("key3"));
+    }
+
+    @Test
+    public void testGetPropertyDirectMultiple()
+    {
+        Configuration config = helper.setUpMultiConfig();
+
+        assertEquals("property1", "value1", config.getProperty("key1"));
+        assertEquals("property2", "value2", config.getProperty("key2"));
+        assertEquals("unknown property", null, config.getProperty("key3"));
+    }
+
+    @Test
+    public void testClearPropertySingle()
+    {
+        Configuration config = helper.setUpConfig();
+        config.clearProperty("key1");
+
+        assertFalse("property not cleared", config.containsKey("key1"));
+    }
+
+    @Test
+    public void testClearPropertyMultiple()
+    {
+        Configuration config = helper.setUpMultiConfig();
+        config.clearProperty("key1");
+
+        assertFalse("property not cleared", config.containsKey("key1"));
+    }
+
+    /**
+     * Tests that another configuration is not affected when clearing
+     * properties.
+     */
+    @Test
+    public void testClearPropertyMultipleOtherConfig()
+    {
+        DatabaseConfiguration config = helper.setUpMultiConfig();
+        DatabaseConfiguration config2 = helper.setUpMultiConfig(CONFIG_NAME2);
+        config2.addProperty("key1", "some test");
+        config.clearProperty("key1");
+        assertFalse("property not cleared", config.containsKey("key1"));
+        assertTrue("Property cleared in other config", config2
+                .containsKey("key1"));
+    }
+
+    /**
+     * Tests whether a commit is performed after a property was cleared.
+     */
+    @Test
+    public void testClearPropertyCommit()
+    {
+        helper.setAutoCommit(false);
+        Configuration config = helper.setUpConfig();
+        config.clearProperty("key1");
+        assertFalse("property not cleared", config.containsKey("key1"));
+    }
+
+    @Test
+    public void testClearSingle()
+    {
+        Configuration config = helper.setUpConfig();
+        config.clear();
+
+        assertTrue("configuration is not cleared", config.isEmpty());
+    }
+
+    @Test
+    public void testClearMultiple()
+    {
+        Configuration config = helper.setUpMultiConfig();
+        config.clear();
+
+        assertTrue("configuration is not cleared", config.isEmpty());
+    }
+
+    /**
+     * Tests whether a commit is performed after a clear operation.
+     */
+    @Test
+    public void testClearCommit()
+    {
+        helper.setAutoCommit(false);
+        Configuration config = helper.setUpConfig();
+        config.clear();
+        assertTrue("configuration is not cleared", config.isEmpty());
+    }
+
+    @Test
+    public void testGetKeysSingle()
+    {
+        Configuration config = setUpConfig();
+        Iterator<String> it = config.getKeys();
+
+        assertEquals("1st key", "key1", it.next());
+        assertEquals("2nd key", "key2", it.next());
+    }
+
+    @Test
+    public void testGetKeysMultiple()
+    {
+        Configuration config = helper.setUpMultiConfig();
+        Iterator<String> it = config.getKeys();
+
+        assertEquals("1st key", "key1", it.next());
+        assertEquals("2nd key", "key2", it.next());
+    }
+
+    @Test
+    public void testContainsKeySingle()
+    {
+        Configuration config = setUpConfig();
+        assertTrue("missing key1", config.containsKey("key1"));
+        assertTrue("missing key2", config.containsKey("key2"));
+    }
+
+    @Test
+    public void testContainsKeyMultiple()
+    {
+        Configuration config = helper.setUpMultiConfig();
+        assertTrue("missing key1", config.containsKey("key1"));
+        assertTrue("missing key2", config.containsKey("key2"));
+    }
+
+    @Test
+    public void testIsEmptySingle()
+    {
+        Configuration config1 = setUpConfig();
+        assertFalse("The configuration is empty", config1.isEmpty());
+    }
+
+    @Test
+    public void testIsEmptyMultiple()
+    {
+        Configuration config1 = helper.setUpMultiConfig();
+        assertFalse("The configuration named 'test' is empty", config1.isEmpty());
+
+        Configuration config2 = new DatabaseConfiguration(helper.getDatasource(), DatabaseConfigurationTestHelper.TABLE_MULTI, DatabaseConfigurationTestHelper.COL_NAME, DatabaseConfigurationTestHelper.COL_KEY, DatabaseConfigurationTestHelper.COL_VALUE, "testIsEmpty");
+        assertTrue("The configuration named 'testIsEmpty' is not empty", config2.isEmpty());
+    }
+
+    @Test
+    public void testGetList()
+    {
+        Configuration config1 = new DatabaseConfiguration(helper.getDatasource(), "configurationList", DatabaseConfigurationTestHelper.COL_KEY, DatabaseConfigurationTestHelper.COL_VALUE);
+        List<Object> list = config1.getList("key3");
+        assertEquals(3,list.size());
+    }
+
+    @Test
+    public void testGetKeys()
+    {
+        Configuration config1 = new DatabaseConfiguration(helper.getDatasource(), "configurationList", DatabaseConfigurationTestHelper.COL_KEY, DatabaseConfigurationTestHelper.COL_VALUE);
+        Iterator<String> i = config1.getKeys();
+        assertTrue(i.hasNext());
+        Object key = i.next();
+        assertEquals("key3",key.toString());
+        assertFalse(i.hasNext());
+    }
+
+    @Test
+    public void testClearSubset()
+    {
+        Configuration config = setUpConfig();
+
+        Configuration subset = config.subset("key1");
+        subset.clear();
+
+        assertTrue("the subset is not empty", subset.isEmpty());
+        assertFalse("the parent configuration is empty", config.isEmpty());
+    }
+
+    /**
+     * Tests whether the configuration has already an error listener registered
+     * that is used for logging.
+     */
+    @Test
+    public void testLogErrorListener()
+    {
+        DatabaseConfiguration config = new DatabaseConfiguration(helper.getDatasource(), DatabaseConfigurationTestHelper.TABLE, DatabaseConfigurationTestHelper.COL_KEY, DatabaseConfigurationTestHelper.COL_VALUE);
+        assertEquals("No error listener registered", 1, config.getErrorListeners().size());
+    }
+
+    /**
+     * Tests handling of errors in getProperty().
+     */
+    @Test
+    public void testGetPropertyError()
+    {
+        setUpErrorConfig().getProperty("key1");
+        checkErrorListener(AbstractConfiguration.EVENT_READ_PROPERTY, "key1", null);
+    }
+
+    /**
+     * Tests handling of errors in addPropertyDirect().
+     */
+    @Test
+    public void testAddPropertyError()
+    {
+        setUpErrorConfig().addProperty("key1", "value");
+        checkErrorListener(AbstractConfiguration.EVENT_ADD_PROPERTY, "key1", "value");
+    }
+
+    /**
+     * Tests handling of errors in isEmpty().
+     */
+    @Test
+    public void testIsEmptyError()
+    {
+        assertTrue("Wrong return value for failure", setUpErrorConfig().isEmpty());
+        checkErrorListener(AbstractConfiguration.EVENT_READ_PROPERTY, null, null);
+    }
+
+    /**
+     * Tests handling of errors in containsKey().
+     */
+    @Test
+    public void testContainsKeyError()
+    {
+        assertFalse("Wrong return value for failure", setUpErrorConfig().containsKey("key1"));
+        checkErrorListener(AbstractConfiguration.EVENT_READ_PROPERTY, "key1", null);
+    }
+
+    /**
+     * Tests handling of errors in clearProperty().
+     */
+    @Test
+    public void testClearPropertyError()
+    {
+        setUpErrorConfig().clearProperty("key1");
+        checkErrorListener(AbstractConfiguration.EVENT_CLEAR_PROPERTY, "key1", null);
+    }
+
+    /**
+     * Tests handling of errors in clear().
+     */
+    @Test
+    public void testClearError()
+    {
+        setUpErrorConfig().clear();
+        checkErrorListener(AbstractConfiguration.EVENT_CLEAR, null, null);
+    }
+
+    /**
+     * Tests handling of errors in getKeys().
+     */
+    @Test
+    public void testGetKeysError()
+    {
+        Iterator<String> it = setUpErrorConfig().getKeys();
+        checkErrorListener(AbstractConfiguration.EVENT_READ_PROPERTY, null, null);
+        assertFalse("Iteration is not empty", it.hasNext());
+    }
+
+    /**
+     * Tests obtaining a property as list whose value contains the list
+     * delimiter. Multiple values should be returned.
+     */
+    @Test
+    public void testGetListWithDelimiter()
+    {
+        DatabaseConfiguration config = setUpConfig();
+        config.setListDelimiter(';');
+        List<Object> values = config.getList("keyMulti");
+        assertEquals("Wrong number of list elements", 3, values.size());
+        assertEquals("Wrong list element 0", "a", values.get(0));
+        assertEquals("Wrong list element 2", "c", values.get(2));
+    }
+
+    /**
+     * Tests obtaining a property whose value contains the list delimiter when
+     * delimiter parsing is disabled.
+     */
+    @Test
+    public void testGetListWithDelimiterParsingDisabled()
+    {
+        DatabaseConfiguration config = setUpConfig();
+        config.setListDelimiter(';');
+        config.setDelimiterParsingDisabled(true);
+        assertEquals("Wrong value of property", "a;b;c", config.getString("keyMulti"));
+    }
+
+    /**
+     * Tests adding a property containing the list delimiter. When this property
+     * is queried multiple values should be returned.
+     */
+    @Test
+    public void testAddWithDelimiter()
+    {
+        DatabaseConfiguration config = setUpConfig();
+        config.setListDelimiter(';');
+        config.addProperty("keyList", "1;2;3");
+        String[] values = config.getStringArray("keyList");
+        assertEquals("Wrong number of property values", 3, values.length);
+        assertEquals("Wrong value at index 1", "2", values[1]);
+    }
+
+    /**
+     * Tests setProperty() if the property value contains the list delimiter.
+     */
+    @Test
+    public void testSetPropertyWithDelimiter()
+    {
+        DatabaseConfiguration config = helper.setUpMultiConfig();
+        config.setListDelimiter(';');
+        config.setProperty("keyList", "1;2;3");
+        String[] values = config.getStringArray("keyList");
+        assertEquals("Wrong number of property values", 3, values.length);
+        assertEquals("Wrong value at index 1", "2", values[1]);
+    }
+
+    /**
+     * A specialized database configuration implementation that can be
+     * configured to throw an exception when obtaining a connection. This way
+     * database exceptions can be simulated.
+     */
+    static class PotentialErrorDatabaseConfiguration extends DatabaseConfiguration
+    {
+        /** A flag whether a getConnection() call should fail. */
+        boolean failOnConnect;
+
+        public PotentialErrorDatabaseConfiguration(DataSource datasource,
+                String table, String keyColumn, String valueColumn)
+        {
+            super(datasource, table, keyColumn, valueColumn);
+        }
+
+        @Override
+        protected Connection getConnection() throws SQLException
+        {
+            if (failOnConnect)
+            {
+                throw new SQLException("Simulated DB error");
+            }
+            return super.getConnection();
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestDefaultConfigurationBuilder.java b/src/test/java/org/apache/commons/configuration/TestDefaultConfigurationBuilder.java
new file mode 100644
index 0000000..3947778
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestDefaultConfigurationBuilder.java
@@ -0,0 +1,1279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.StringWriter;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.URL;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.configuration.beanutils.BeanHelper;
+import org.apache.commons.configuration.event.ConfigurationListenerTestImpl;
+import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.DefaultConfigurationNode;
+import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
+import org.apache.commons.lang.SystemUtils;
+import org.apache.commons.lang.text.StrLookup;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.logging.impl.Log4JLogger;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.SimpleLayout;
+import org.apache.log4j.WriterAppender;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for DefaultConfigurationBuilder.
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: TestDefaultConfigurationBuilder.java 1301997 2012-03-17 20:32:02Z sebb $
+ */
+public class TestDefaultConfigurationBuilder
+{
+    /** Test configuration definition file. */
+    private static final File TEST_FILE = ConfigurationAssert
+            .getTestFile("testDigesterConfiguration.xml");
+
+    private static final File ADDITIONAL_FILE = ConfigurationAssert
+            .getTestFile("testDigesterConfiguration2.xml");
+
+    private static final File OPTIONAL_FILE = ConfigurationAssert
+            .getTestFile("testDigesterOptionalConfiguration.xml");
+
+    private static final File OPTIONALEX_FILE = ConfigurationAssert
+            .getTestFile("testDigesterOptionalConfigurationEx.xml");
+
+    private static final File MULTI_FILE = ConfigurationAssert
+            .getTestFile("testDigesterConfiguration3.xml");
+
+    private static final File INIT_FILE = ConfigurationAssert
+            .getTestFile("testComplexInitialization.xml");
+
+    private static final File CLASS_FILE = ConfigurationAssert
+            .getTestFile("testExtendedClass.xml");
+
+    private static final File PROVIDER_FILE = ConfigurationAssert
+            .getTestFile("testConfigurationProvider.xml");
+
+    private static final File EXTENDED_PROVIDER_FILE = ConfigurationAssert
+            .getTestFile("testExtendedXMLConfigurationProvider.xml");
+
+    private static final File GLOBAL_LOOKUP_FILE = ConfigurationAssert
+            .getTestFile("testGlobalLookup.xml");
+
+    private static final File SYSTEM_PROPS_FILE = ConfigurationAssert
+            .getTestFile("testSystemProperties.xml");
+
+    private static final File VALIDATION_FILE = ConfigurationAssert
+            .getTestFile("testValidation.xml");
+
+    private static final File VALIDATION3_FILE = ConfigurationAssert
+            .getTestFile("testValidation3.xml");
+
+    private static final File MULTI_TENENT_FILE = ConfigurationAssert
+            .getTestFile("testMultiTenentConfigurationBuilder.xml");
+
+    private static final File EXPRESSION_FILE = ConfigurationAssert
+            .getTestFile("testExpression.xml");
+
+    /** Constant for the name of an optional configuration.*/
+    private static final String OPTIONAL_NAME = "optionalConfig";
+
+    /** Stores the object to be tested. */
+    DefaultConfigurationBuilder factory;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        System
+                .setProperty("java.naming.factory.initial",
+                        "org.apache.commons.configuration.MockInitialContextFactory");
+        System.setProperty("test_file_xml", "test.xml");
+        System.setProperty("test_file_combine", "testcombine1.xml");
+        factory = new DefaultConfigurationBuilder();
+        factory.clearErrorListeners();  // avoid exception messages
+    }
+
+    /**
+     * Tests the isReservedNode() method of ConfigurationDeclaration.
+     */
+    @Test
+    public void testConfigurationDeclarationIsReserved()
+    {
+        DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
+                factory, factory);
+        DefaultConfigurationNode parent = new DefaultConfigurationNode();
+        DefaultConfigurationNode nd = new DefaultConfigurationNode("at");
+        parent.addAttribute(nd);
+        assertTrue("Attribute at not recognized", decl.isReservedNode(nd));
+        nd = new DefaultConfigurationNode("optional");
+        parent.addAttribute(nd);
+        assertTrue("Attribute optional not recognized", decl.isReservedNode(nd));
+        nd = new DefaultConfigurationNode("config-class");
+        parent.addAttribute(nd);
+        assertTrue("Inherited attribute not recognized", decl
+                .isReservedNode(nd));
+        nd = new DefaultConfigurationNode("different");
+        parent.addAttribute(nd);
+        assertFalse("Wrong reserved attribute", decl.isReservedNode(nd));
+        nd = new DefaultConfigurationNode("at");
+        parent.addChild(nd);
+        assertFalse("Node type not evaluated", decl.isReservedNode(nd));
+    }
+
+    /**
+     * Tests if the at attribute is correctly detected as reserved attribute.
+     */
+    @Test
+    public void testConfigurationDeclarationIsReservedAt()
+    {
+        checkOldReservedAttribute("at");
+    }
+
+    /**
+     * Tests if the optional attribute is correctly detected as reserved
+     * attribute.
+     */
+    @Test
+    public void testConfigurationDeclarationIsReservedOptional()
+    {
+        checkOldReservedAttribute("optional");
+    }
+
+    /**
+     * Tests if special reserved attributes are recognized by the
+     * isReservedNode() method. For compatibility reasons the attributes "at"
+     * and "optional" are also treated as reserved attributes, but only if there
+     * are no corresponding attributes with the "config-" prefix.
+     *
+     * @param name the attribute name
+     */
+    private void checkOldReservedAttribute(String name)
+    {
+        DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
+                factory, factory);
+        DefaultConfigurationNode parent = new DefaultConfigurationNode();
+        DefaultConfigurationNode nd = new DefaultConfigurationNode("config-"
+                + name);
+        parent.addAttribute(nd);
+        assertTrue("config-" + name + " attribute not recognized", decl
+                .isReservedNode(nd));
+        DefaultConfigurationNode nd2 = new DefaultConfigurationNode(name);
+        parent.addAttribute(nd2);
+        assertFalse(name + " is reserved though config- exists", decl
+                .isReservedNode(nd2));
+        assertTrue("config- attribute not recognized when " + name + " exists",
+                decl.isReservedNode(nd));
+    }
+
+    /**
+     * Tests access to certain reserved attributes of a
+     * ConfigurationDeclaration.
+     */
+    public void testConfigurationDeclarationGetAttributes()
+    {
+        factory.addProperty("xml.fileName", "test.xml");
+        DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
+                factory, factory.configurationAt("xml"));
+        assertNull("Found an at attribute", decl.getAt());
+        assertFalse("Found an optional attribute", decl.isOptional());
+        factory.addProperty("xml[@config-at]", "test1");
+        assertEquals("Wrong value of at attribute", "test1", decl.getAt());
+        factory.addProperty("xml[@at]", "test2");
+        assertEquals("Wrong value of config-at attribute", "test1", decl.getAt());
+        factory.clearProperty("xml[@config-at]");
+        assertEquals("Old at attribute not detected", "test2", decl.getAt());
+        factory.addProperty("xml[@config-optional]", "true");
+        assertTrue("Wrong value of optional attribute", decl.isOptional());
+        factory.addProperty("xml[@optional]", "false");
+        assertTrue("Wrong value of config-optional attribute", decl.isOptional());
+        factory.clearProperty("xml[@config-optional]");
+        factory.setProperty("xml[@optional]", Boolean.TRUE);
+        assertTrue("Old optional attribute not detected", decl.isOptional());
+    }
+
+    /**
+     * Tests whether an invalid value of an optional attribute is detected.
+     */
+    @Test(expected = ConfigurationRuntimeException.class)
+    public void testConfigurationDeclarationOptionalAttributeInvalid()
+    {
+        factory.addProperty("xml.fileName", "test.xml");
+        DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
+                factory, factory.configurationAt("xml"));
+        factory.setProperty("xml[@optional]", "invalid value");
+        decl.isOptional();
+    }
+
+    /**
+     * Tests adding a new configuration provider.
+     */
+    @Test
+    public void testAddConfigurationProvider()
+    {
+        DefaultConfigurationBuilder.ConfigurationProvider provider = new DefaultConfigurationBuilder.ConfigurationProvider();
+        assertNull("Provider already registered", factory
+                .providerForTag("test"));
+        factory.addConfigurationProvider("test", provider);
+        assertSame("Provider not registered", provider, factory
+                .providerForTag("test"));
+    }
+
+    /**
+     * Tries to register a null configuration provider. This should cause an
+     * exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testAddConfigurationProviderNull()
+    {
+        factory.addConfigurationProvider("test", null);
+    }
+
+    /**
+     * Tries to register a configuration provider for a null tag. This should
+     * cause an exception to be thrown.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testAddConfigurationProviderNullTag()
+    {
+        factory.addConfigurationProvider(null,
+                new DefaultConfigurationBuilder.ConfigurationProvider());
+    }
+
+    /**
+     * Tests removing configuration providers.
+     */
+    @Test
+    public void testRemoveConfigurationProvider()
+    {
+        assertNull("Removing unknown provider", factory
+                .removeConfigurationProvider("test"));
+        assertNull("Removing provider for null tag", factory
+                .removeConfigurationProvider(null));
+        DefaultConfigurationBuilder.ConfigurationProvider provider = new DefaultConfigurationBuilder.ConfigurationProvider();
+        factory.addConfigurationProvider("test", provider);
+        assertSame("Failed to remove provider", provider, factory
+                .removeConfigurationProvider("test"));
+        assertNull("Provider still registered", factory.providerForTag("test"));
+    }
+
+    /**
+     * Tests creating a configuration object from a configuration declaration.
+     */
+    @Test
+    public void testConfigurationBeanFactoryCreateBean()
+    {
+        factory.addConfigurationProvider("test",
+                new DefaultConfigurationBuilder.ConfigurationProvider(
+                        PropertiesConfiguration.class));
+        factory.addProperty("test[@throwExceptionOnMissing]", "true");
+        DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
+                factory, factory.configurationAt("test"));
+        PropertiesConfiguration conf = (PropertiesConfiguration) BeanHelper
+                .createBean(decl);
+        assertTrue("Property was not initialized", conf
+                .isThrowExceptionOnMissing());
+    }
+
+    /**
+     * Tests creating a configuration object from an unknown tag. This should
+     * cause an exception.
+     */
+    @Test(expected = ConfigurationRuntimeException.class)
+    public void testConfigurationBeanFactoryCreateUnknownTag()
+    {
+        factory.addProperty("test[@throwExceptionOnMissing]", "true");
+        DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
+                factory, factory.configurationAt("test"));
+        BeanHelper.createBean(decl);
+    }
+
+    /**
+     * Tests loading a simple configuration definition file.
+     */
+    @Test
+    public void testLoadConfiguration() throws ConfigurationException
+    {
+        factory.setFile(TEST_FILE);
+        checkConfiguration();
+    }
+
+    /**
+     * Tests the file constructor.
+     */
+    @Test
+    public void testLoadConfigurationFromFile() throws ConfigurationException
+    {
+        factory = new DefaultConfigurationBuilder(TEST_FILE);
+        checkConfiguration();
+    }
+
+    /**
+     * Tests the file name constructor.
+     */
+    @Test
+    public void testLoadConfigurationFromFileName()
+            throws ConfigurationException
+    {
+        factory = new DefaultConfigurationBuilder(TEST_FILE.getAbsolutePath());
+        checkConfiguration();
+    }
+
+    /**
+     * Tests the URL constructor.
+     */
+    @Test
+    public void testLoadConfigurationFromURL() throws Exception
+    {
+        factory = new DefaultConfigurationBuilder(TEST_FILE.toURI().toURL());
+        checkConfiguration();
+    }
+
+    /**
+     * Tests if the configuration was correctly created by the factory.
+     */
+    private void checkConfiguration() throws ConfigurationException
+    {
+        CombinedConfiguration compositeConfiguration = (CombinedConfiguration) factory
+                .getConfiguration();
+
+        assertEquals("Number of configurations", 3, compositeConfiguration
+                .getNumberOfConfigurations());
+        assertEquals(PropertiesConfiguration.class, compositeConfiguration
+                .getConfiguration(0).getClass());
+        assertEquals(XMLPropertiesConfiguration.class, compositeConfiguration
+                .getConfiguration(1).getClass());
+        assertEquals(XMLConfiguration.class, compositeConfiguration
+                .getConfiguration(2).getClass());
+
+        // check the first configuration
+        PropertiesConfiguration pc = (PropertiesConfiguration) compositeConfiguration
+                .getConfiguration(0);
+        assertNotNull("Make sure we have a fileName: " + pc.getFileName(), pc
+                .getFileName());
+
+        // check some properties
+        checkProperties(compositeConfiguration);
+    }
+
+    /**
+     * Checks if the passed in configuration contains the expected properties.
+     *
+     * @param compositeConfiguration the configuration to check
+     */
+    private void checkProperties(Configuration compositeConfiguration)
+    {
+        assertTrue("Make sure we have loaded our key", compositeConfiguration
+                .getBoolean("test.boolean"));
+        assertEquals("I'm complex!", compositeConfiguration
+                .getProperty("element2.subelement.subsubelement"));
+        assertEquals("property in the XMLPropertiesConfiguration", "value1",
+                compositeConfiguration.getProperty("key1"));
+    }
+
+    /**
+     * Tests loading a configuration definition file with an additional section.
+     */
+    @Test
+    public void testLoadAdditional() throws ConfigurationException
+    {
+        factory.setFile(ADDITIONAL_FILE);
+        CombinedConfiguration compositeConfiguration = (CombinedConfiguration) factory
+                .getConfiguration();
+        assertEquals("Verify how many configs", 2, compositeConfiguration
+                .getNumberOfConfigurations());
+
+        // Test if union was constructed correctly
+        Object prop = compositeConfiguration.getProperty("tables.table.name");
+        assertTrue(prop instanceof Collection);
+        assertEquals(3, ((Collection<?>) prop).size());
+        assertEquals("users", compositeConfiguration
+                .getProperty("tables.table(0).name"));
+        assertEquals("documents", compositeConfiguration
+                .getProperty("tables.table(1).name"));
+        assertEquals("tasks", compositeConfiguration
+                .getProperty("tables.table(2).name"));
+
+        prop = compositeConfiguration
+                .getProperty("tables.table.fields.field.name");
+        assertTrue(prop instanceof Collection);
+        assertEquals(17, ((Collection<?>) prop).size());
+
+        assertEquals("smtp.mydomain.org", compositeConfiguration
+                .getString("mail.host.smtp"));
+        assertEquals("pop3.mydomain.org", compositeConfiguration
+                .getString("mail.host.pop"));
+
+        // This was overriden
+        assertEquals("masterOfPost", compositeConfiguration
+                .getString("mail.account.user"));
+        assertEquals("topsecret", compositeConfiguration
+                .getString("mail.account.psswd"));
+
+        // This was overriden, too, but not in additional section
+        assertEquals("enhanced factory", compositeConfiguration
+                .getString("test.configuration"));
+    }
+
+    /**
+     * Tests whether a default log error listener is registered at the builder
+     * instance.
+     */
+    @Test
+    public void testLogErrorListener()
+    {
+        assertEquals("No default error listener registered", 1,
+                new DefaultConfigurationBuilder().getErrorListeners().size());
+    }
+
+    /**
+     * Tests loading a definition file that contains optional configurations.
+     */
+    @Test
+    public void testLoadOptional() throws Exception
+    {
+        factory.setURL(OPTIONAL_FILE.toURI().toURL());
+        Configuration config = factory.getConfiguration();
+        assertTrue(config.getBoolean("test.boolean"));
+        assertEquals("value", config.getProperty("element"));
+    }
+
+    /**
+     * Tests whether loading a failing optional configuration causes an error
+     * event.
+     */
+    @Test
+    public void testLoadOptionalErrorEvent() throws Exception
+    {
+        factory.clearErrorListeners();
+        ConfigurationErrorListenerImpl listener = new ConfigurationErrorListenerImpl();
+        factory.addErrorListener(listener);
+        prepareOptionalTest("configuration", false);
+        listener.verify(DefaultConfigurationBuilder.EVENT_ERR_LOAD_OPTIONAL,
+                OPTIONAL_NAME, null);
+    }
+
+    /**
+     * Tests loading a definition file with optional and non optional
+     * configuration sources. One non optional does not exist, so this should
+     * cause an exception.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testLoadOptionalWithException() throws ConfigurationException
+    {
+        factory.setFile(OPTIONALEX_FILE);
+        factory.getConfiguration();
+    }
+
+    /**
+     * Tries to load a configuration file with an optional, non file-based
+     * configuration. The optional attribute should work for other configuration
+     * classes, too.
+     */
+    @Test
+    public void testLoadOptionalNonFileBased() throws ConfigurationException
+    {
+        CombinedConfiguration config = prepareOptionalTest("configuration", false);
+        assertTrue("Configuration not empty", config.isEmpty());
+        assertEquals("Wrong number of configurations", 0, config
+                .getNumberOfConfigurations());
+    }
+
+    /**
+     * Tests an optional, non existing configuration with the forceCreate
+     * attribute. This configuration should be added to the resulting
+     * configuration.
+     */
+    @Test
+    public void testLoadOptionalForceCreate() throws ConfigurationException
+    {
+        factory.setBasePath(TEST_FILE.getParent());
+        CombinedConfiguration config = prepareOptionalTest("xml", true);
+        assertEquals("Wrong number of configurations", 1, config
+                .getNumberOfConfigurations());
+        FileConfiguration fc = (FileConfiguration) config
+                .getConfiguration(OPTIONAL_NAME);
+        assertNotNull("Optional config not found", fc);
+        assertEquals("File name was not set", "nonExisting.xml", fc
+                .getFileName());
+        assertNotNull("Base path was not set", fc.getBasePath());
+    }
+
+    /**
+     * Tests loading an embedded optional configuration builder with the force
+     * create attribute.
+     */
+    @Test
+    public void testLoadOptionalBuilderForceCreate()
+            throws ConfigurationException
+    {
+        CombinedConfiguration config = prepareOptionalTest("configuration",
+                true);
+        assertEquals("Wrong number of configurations", 1, config
+                .getNumberOfConfigurations());
+        assertTrue(
+                "Wrong optional configuration type",
+                config.getConfiguration(OPTIONAL_NAME) instanceof CombinedConfiguration);
+    }
+
+    /**
+     * Tests loading an optional configuration with the force create attribute
+     * set. The provider will always throw an exception. In this case the
+     * configuration will not be added to the resulting combined configuration.
+     */
+    @Test
+    public void testLoadOptionalForceCreateWithException()
+            throws ConfigurationException
+    {
+        factory.addConfigurationProvider("test",
+                new DefaultConfigurationBuilder.ConfigurationBuilderProvider()
+                {
+                    // Throw an exception here, too
+                    @Override
+                    public AbstractConfiguration getEmptyConfiguration(
+                            DefaultConfigurationBuilder.ConfigurationDeclaration decl) throws Exception
+                    {
+                        throw new Exception("Unable to create configuration!");
+                    }
+                });
+        CombinedConfiguration config = prepareOptionalTest("test", true);
+        assertEquals("Optional configuration could be created", 0, config
+                .getNumberOfConfigurations());
+    }
+
+    /**
+     * Prepares a test for loading a configuration definition file with an
+     * optional configuration declaration.
+     *
+     * @param tag the tag name with the optional configuration
+     * @param force the forceCreate attribute
+     * @return the combined configuration obtained from the builder
+     * @throws ConfigurationException if an error occurs
+     */
+    private CombinedConfiguration prepareOptionalTest(String tag, boolean force)
+            throws ConfigurationException
+    {
+        String prefix = "override." + tag;
+        factory.addProperty(prefix + "[@fileName]", "nonExisting.xml");
+        factory.addProperty(prefix + "[@config-optional]", Boolean.TRUE);
+        factory.addProperty(prefix + "[@config-name]", OPTIONAL_NAME);
+        if (force)
+        {
+            factory.addProperty(prefix + "[@config-forceCreate]", Boolean.TRUE);
+        }
+        return factory.getConfiguration(false);
+    }
+
+    /**
+     * Tests whether the error log message caused by an optional configuration
+     * can be suppressed if a child builder is involved.
+     */
+    @Test
+    public void testLoadOptionalChildBuilderSuppressErrorLog()
+            throws ConfigurationException
+    {
+        factory.addProperty("override.configuration[@fileName]",
+                OPTIONAL_FILE.getAbsolutePath());
+        // a special invocation handler which checks that the warn() method of
+        // a logger is not called
+        InvocationHandler handler = new InvocationHandler()
+        {
+            public Object invoke(Object proxy, Method method, Object[] args)
+                    throws Throwable
+            {
+                String methodName = method.getName();
+                if (methodName.startsWith("is"))
+                {
+                    return Boolean.TRUE;
+                }
+                if ("warn".equals(methodName))
+                {
+                    fail("Unexpected log output!");
+                }
+                return null;
+            }
+        };
+        factory.setLogger((Log) Proxy.newProxyInstance(getClass()
+                .getClassLoader(), new Class[] {
+            Log.class
+        }, handler));
+        factory.getConfiguration(false);
+    }
+
+    /**
+     * Tests loading a definition file with multiple different sources.
+     */
+    @Test
+    public void testLoadDifferentSources() throws ConfigurationException
+    {
+        factory.setFile(MULTI_FILE);
+        Configuration config = factory.getConfiguration();
+        assertFalse(config.isEmpty());
+        assertTrue(config instanceof CombinedConfiguration);
+        CombinedConfiguration cc = (CombinedConfiguration) config;
+        assertEquals("Wrong number of configurations", 1, cc
+                .getNumberOfConfigurations());
+
+        assertNotNull(config
+                .getProperty("tables.table(0).fields.field(2).name"));
+        assertNotNull(config.getProperty("element2.subelement.subsubelement"));
+        assertEquals("value", config.getProperty("element3"));
+        assertEquals("foo", config.getProperty("element3[@name]"));
+        assertNotNull(config.getProperty("mail.account.user"));
+
+        // test JNDIConfiguration
+        assertNotNull(config.getProperty("test.onlyinjndi"));
+        assertTrue(config.getBoolean("test.onlyinjndi"));
+
+        Configuration subset = config.subset("test");
+        assertNotNull(subset.getProperty("onlyinjndi"));
+        assertTrue(subset.getBoolean("onlyinjndi"));
+
+        // test SystemConfiguration
+        assertNotNull(config.getProperty("java.version"));
+        assertEquals(System.getProperty("java.version"), config
+                .getString("java.version"));
+
+        // test INIConfiguration
+        assertEquals("Property from ini file not found", "yes",
+                config.getString("testini.loaded"));
+
+        // test environment configuration
+        EnvironmentConfiguration envConf = new EnvironmentConfiguration();
+        for (Iterator<String> it = envConf.getKeys(); it.hasNext();)
+        {
+            String key = it.next();
+            String combinedKey = "env." + key;
+            assertEquals("Wrong value for env property " + key,
+                    envConf.getString(key), config.getString(combinedKey));
+        }
+    }
+
+    /**
+     * Tests if the base path is correctly evaluated.
+     */
+    @Test
+    public void testSetConfigurationBasePath() throws ConfigurationException
+    {
+        factory.addProperty("properties[@fileName]", "test.properties");
+        File deepDir = new File(ConfigurationAssert.TEST_DIR, "config/deep");
+        factory.setConfigurationBasePath(deepDir.getAbsolutePath());
+
+        Configuration config = factory.getConfiguration(false);
+        assertEquals("Wrong property value", "somevalue", config
+                .getString("somekey"));
+    }
+
+    /**
+     * Tests reading a configuration definition file that contains complex
+     * initialization of properties of the declared configuration sources.
+     */
+    @Test
+    public void testComplexInitialization() throws ConfigurationException
+    {
+        factory.setFile(INIT_FILE);
+        CombinedConfiguration cc = (CombinedConfiguration) factory
+                .getConfiguration();
+
+        assertEquals("System property not found", "test.xml",
+                cc.getString("test_file_xml"));
+        PropertiesConfiguration c1 = (PropertiesConfiguration) cc
+                .getConfiguration(1);
+        assertTrue(
+                "Reloading strategy was not set",
+                c1.getReloadingStrategy() instanceof FileChangedReloadingStrategy);
+        assertEquals("Refresh delay was not set", 10000,
+                ((FileChangedReloadingStrategy) c1.getReloadingStrategy())
+                        .getRefreshDelay());
+
+        Configuration xmlConf = cc.getConfiguration("xml");
+        assertEquals("Property not found", "I'm complex!", xmlConf
+                .getString("element2/subelement/subsubelement"));
+        assertEquals("List index not found", "two", xmlConf
+                .getString("list[0]/item[1]"));
+        assertEquals("Property in combiner file not found", "yellow", cc
+                .getString("/gui/selcolor"));
+
+        assertTrue("Delimiter flag was not set", cc
+                .isDelimiterParsingDisabled());
+        assertTrue("Expression engine was not set",
+                cc.getExpressionEngine() instanceof XPathExpressionEngine);
+    }
+
+    /**
+     * Tests if the returned combined configuration has the expected structure.
+     */
+    @Test
+    public void testCombinedConfigurationStructure() throws ConfigurationException
+    {
+        factory.setFile(INIT_FILE);
+        CombinedConfiguration cc = (CombinedConfiguration) factory
+                .getConfiguration();
+        assertNotNull("Properties configuration not found", cc
+                .getConfiguration("properties"));
+        assertNotNull("XML configuration not found", cc.getConfiguration("xml"));
+        assertEquals("Wrong number of contained configs", 4, cc
+                .getNumberOfConfigurations());
+
+        CombinedConfiguration cc2 = (CombinedConfiguration) cc
+                .getConfiguration(DefaultConfigurationBuilder.ADDITIONAL_NAME);
+        assertNotNull("No additional configuration found", cc2);
+        Set<String> names = cc2.getConfigurationNames();
+        assertEquals("Wrong number of contained additional configs", 2, names
+                .size());
+        assertTrue("Config 1 not contained", names.contains("combiner1"));
+        assertTrue("Config 2 not contained", names.contains("combiner2"));
+    }
+
+    /**
+     * Helper method for testing the attributes of a combined configuration
+     * created by the builder.
+     *
+     * @param cc the configuration to be checked
+     */
+    private void checkCombinedConfigAttrs(CombinedConfiguration cc)
+    {
+        assertTrue("Wrong delimiter parsing flag",
+                cc.isDelimiterParsingDisabled());
+        assertTrue("Wrong reload check", cc.isForceReloadCheck());
+        assertTrue("Wrong ignore reload ex flag", cc.isIgnoreReloadExceptions());
+    }
+
+    /**
+     * Tests whether attributes are correctly set on the combined configurations
+     * for the override and additional sections.
+     */
+    @Test
+    public void testCombinedConfigurationAttributes() throws ConfigurationException
+    {
+        factory.setFile(INIT_FILE);
+        CombinedConfiguration cc = (CombinedConfiguration) factory
+                .getConfiguration();
+        checkCombinedConfigAttrs(cc);
+        CombinedConfiguration cc2 = (CombinedConfiguration) cc
+                .getConfiguration(DefaultConfigurationBuilder.ADDITIONAL_NAME);
+        checkCombinedConfigAttrs(cc2);
+    }
+
+    /**
+     * Tests the structure of the returned combined configuration if there is no
+     * additional section.
+     */
+    @Test
+    public void testCombinedConfigurationNoAdditional()
+            throws ConfigurationException
+    {
+        factory.setFile(TEST_FILE);
+        CombinedConfiguration cc = factory.getConfiguration(true);
+        assertNull("Additional configuration was found", cc
+                .getConfiguration(DefaultConfigurationBuilder.ADDITIONAL_NAME));
+    }
+
+    /**
+     * Tests whether the list node definition was correctly processed.
+     */
+    @Test
+    public void testCombinedConfigurationListNodes()
+            throws ConfigurationException
+    {
+        factory.setFile(INIT_FILE);
+        CombinedConfiguration cc = factory.getConfiguration(true);
+        Set<String> listNodes = cc.getNodeCombiner().getListNodes();
+        assertEquals("Wrong number of list nodes", 2, listNodes.size());
+        assertTrue("table node not a list node", listNodes.contains("table"));
+        assertTrue("list node not a list node", listNodes.contains("list"));
+
+        CombinedConfiguration cca = (CombinedConfiguration) cc
+                .getConfiguration(DefaultConfigurationBuilder.ADDITIONAL_NAME);
+        listNodes = cca.getNodeCombiner().getListNodes();
+        assertTrue("Found list nodes for additional combiner", listNodes
+                .isEmpty());
+    }
+
+    /**
+     * Tests whether a configuration builder can itself be declared in a
+     * configuration definition file.
+     */
+    @Test
+    public void testConfigurationBuilderProvider()
+            throws ConfigurationException
+    {
+        factory.addProperty("override.configuration[@fileName]", TEST_FILE
+                .getAbsolutePath());
+        CombinedConfiguration cc = factory.getConfiguration(false);
+        assertEquals("Wrong number of configurations", 1, cc
+                .getNumberOfConfigurations());
+        checkProperties(cc);
+    }
+
+    /**
+     * Tests whether settings of the builder are propagated to child builders.
+     */
+    @Test
+    public void testConfigurationBuilderProviderInheritProperties()
+            throws Exception
+    {
+        factory.addProperty("override.configuration[@fileName]",
+                TEST_FILE.getAbsolutePath());
+        factory.setBasePath("conf");
+        factory.setAttributeSplittingDisabled(true);
+        factory.setDelimiterParsingDisabled(true);
+        factory.setListDelimiter('/');
+        factory.setThrowExceptionOnMissing(true);
+        Log log = LogFactory.getLog(getClass());
+        factory.setLogger(log);
+        factory.clearErrorListeners();
+        factory.clearConfigurationListeners();
+        ConfigurationListenerTestImpl l =
+                new ConfigurationListenerTestImpl(factory);
+        factory.addConfigurationListener(l);
+        DefaultConfigurationBuilder.ConfigurationDeclaration decl =
+                new DefaultConfigurationBuilder.ConfigurationDeclaration(
+                        factory,
+                        factory.configurationAt("override.configuration"));
+        DefaultConfigurationBuilder.ConfigurationBuilderProvider provider =
+                new DefaultConfigurationBuilder.ConfigurationBuilderProvider();
+        DefaultConfigurationBuilder child =
+                (DefaultConfigurationBuilder) provider.createBean(
+                        provider.fetchConfigurationClass(), decl, null);
+        assertEquals("Wrong base path", factory.getBasePath(),
+                child.getBasePath());
+        assertEquals("Wrong attribute splitting flag",
+                factory.isAttributeSplittingDisabled(),
+                child.isAttributeSplittingDisabled());
+        assertEquals("Wrong delimiter parsing flag",
+                factory.isDelimiterParsingDisabled(),
+                child.isDelimiterParsingDisabled());
+        assertEquals("Wrong list delimiter", factory.getListDelimiter(),
+                child.getListDelimiter());
+        assertEquals("Wrong exception flag",
+                factory.isThrowExceptionOnMissing(),
+                child.isThrowExceptionOnMissing());
+        assertSame("Wrong logger", log, child.getLogger());
+        assertTrue("Got error listeners", child.getErrorListeners().isEmpty());
+        assertEquals("Wrong number of listeners", 1, child
+                .getConfigurationListeners().size());
+        assertEquals("Wrong listener", l, child.getConfigurationListeners()
+                .iterator().next());
+    }
+
+    /**
+     * Tests whether properties of the parent configuration can be overridden.
+     */
+    @Test
+    public void testConfigurationBuilderProviderOverrideProperties()
+            throws Exception
+    {
+        factory.addProperty("override.configuration[@fileName]",
+                TEST_FILE.getAbsolutePath());
+        factory.addProperty("override.configuration[@basePath]", "base");
+        factory.addProperty("override.configuration[@throwExceptionOnMissing]",
+                "false");
+        factory.setBasePath("conf");
+        factory.setThrowExceptionOnMissing(true);
+        DefaultConfigurationBuilder.ConfigurationDeclaration decl =
+                new DefaultConfigurationBuilder.ConfigurationDeclaration(
+                        factory,
+                        factory.configurationAt("override.configuration"));
+        DefaultConfigurationBuilder.ConfigurationBuilderProvider provider =
+                new DefaultConfigurationBuilder.ConfigurationBuilderProvider();
+        DefaultConfigurationBuilder child =
+                (DefaultConfigurationBuilder) provider.createBean(
+                        provider.fetchConfigurationClass(), decl, null);
+        assertEquals("Wrong base path", "base", child.getBasePath());
+        assertFalse("Wrong exception flag", child.isThrowExceptionOnMissing());
+    }
+
+    /**
+     * Tests whether XML settings can be inherited.
+     */
+    @Test
+    public void testLoadXMLWithSettings() throws Exception
+    {
+        File confDir = new File("conf");
+        File targetDir = new File("target");
+        File testXMLValidationSource = new File(confDir,
+                "testValidateInvalid.xml");
+        File testSavedXML = new File(targetDir, "testSave.xml");
+        File testSavedFactory = new File(targetDir, "testSaveFactory.xml");
+        URL dtdFile = getClass().getResource("/properties.dtd");
+        final String publicId = "http://commons.apache.org/test.dtd";
+
+        XMLConfiguration config = new XMLConfiguration("testDtd.xml");
+        config.setPublicID(publicId);
+        config.save(testSavedXML);
+        factory.addProperty("xml[@fileName]", testSavedXML.getAbsolutePath());
+        factory.addProperty("xml(0)[@validating]", "true");
+        factory.addProperty("xml(-1)[@fileName]", testXMLValidationSource
+                .getAbsolutePath());
+        factory.addProperty("xml(1)[@config-optional]", "true");
+        factory.addProperty("xml(1)[@validating]", "true");
+        factory.save(testSavedFactory);
+
+        factory = new DefaultConfigurationBuilder();
+        factory.setFile(testSavedFactory);
+        factory.registerEntityId(publicId, dtdFile);
+        factory.clearErrorListeners();
+        Configuration c = factory.getConfiguration();
+        assertEquals("Wrong property value", "value1", c.getString("entry(0)"));
+        assertFalse("Invalid XML source was loaded", c
+                .containsKey("table.name"));
+
+        testSavedXML.delete();
+        testSavedFactory.delete();
+    }
+
+    /**
+     * Tests loading a configuration definition file that defines a custom
+     * result class.
+     */
+    @Test
+    public void testExtendedClass() throws ConfigurationException
+    {
+        factory.setFile(CLASS_FILE);
+        CombinedConfiguration cc = factory.getConfiguration(true);
+        assertEquals("Extended", cc.getProperty("test"));
+        assertTrue("Wrong result class: " + cc.getClass(),
+                cc instanceof ExtendedCombinedConfiguration);
+    }
+
+    /**
+     * Tests loading a configuration definition file that defines new providers.
+     */
+    @Test
+    public void testConfigurationProvider() throws ConfigurationException
+    {
+        factory.setFile(PROVIDER_FILE);
+        factory.getConfiguration(true);
+        DefaultConfigurationBuilder.ConfigurationProvider provider = factory
+                .providerForTag("test");
+        assertNotNull("Provider 'test' not registered", provider);
+    }
+
+    /**
+     * Tests loading a configuration definition file that defines new providers.
+     */
+    @Test
+    public void testExtendedXMLConfigurationProvider() throws ConfigurationException
+    {
+        factory.setFile(EXTENDED_PROVIDER_FILE);
+        CombinedConfiguration cc = factory.getConfiguration(true);
+        DefaultConfigurationBuilder.ConfigurationProvider provider = factory
+                .providerForTag("test");
+        assertNotNull("Provider 'test' not registered", provider);
+        Configuration config = cc.getConfiguration("xml");
+        assertNotNull("Test configuration not present", config);
+        assertTrue("Configuration is not ExtendedXMLConfiguration, is " +
+                config.getClass().getName(), config instanceof ExtendedXMLConfiguration);
+    }
+
+    @Test
+    public void testGlobalLookup() throws Exception
+    {
+        factory.setFile(GLOBAL_LOOKUP_FILE);
+        CombinedConfiguration cc = factory.getConfiguration(true);
+        String value = cc.getInterpolator().lookup("test:test_key");
+        assertNotNull("The test key was not located", value);
+        assertEquals("Incorrect value retrieved","test.value",value);
+    }
+
+    @Test
+    public void testSystemProperties() throws Exception
+    {
+        factory.setFile(SYSTEM_PROPS_FILE);
+        factory.getConfiguration(true);
+        String value = System.getProperty("key1");
+        assertNotNull("The test key was not located", value);
+        assertEquals("Incorrect value retrieved","value1",value);
+    }
+
+    @Test
+    public void testValidation() throws Exception
+    {
+        factory.setFile(VALIDATION_FILE);
+        factory.getConfiguration(true);
+        String value = System.getProperty("key1");
+        assertNotNull("The test key was not located", value);
+        assertEquals("Incorrect value retrieved","value1",value);
+    }
+
+    @Test
+    public void testValidation3() throws Exception
+    {
+        System.getProperties().remove("Id");
+        factory.setFile(VALIDATION3_FILE);
+        CombinedConfiguration config = factory.getConfiguration(true);
+        String value = config.getString("Employee/Name");
+        assertNotNull("The test key was not located", value);
+        assertEquals("Incorrect value retrieved","John Doe",value);
+        System.setProperty("Id", "1001");
+        value = config.getString("Employee/Name");
+        assertNotNull("The test key was not located", value);
+        assertEquals("Incorrect value retrieved","Jane Doe",value);
+    }
+
+    @Test
+    public void testMultiTenentConfiguration() throws Exception
+    {
+        factory.setFile(MULTI_TENENT_FILE);
+        System.getProperties().remove("Id");
+
+        CombinedConfiguration config = factory.getConfiguration(true);
+        assertTrue("Incorrect configuration", config instanceof DynamicCombinedConfiguration);
+
+        verify("1001", config, 15);
+        verify("1002", config, 25);
+        verify("1003", config, 35);
+        verify("1004", config, 50);
+        verify("1005", config, 50);
+    }
+
+    @Test
+    public void testMultiTenentConfiguration2() throws Exception
+    {
+        factory.setFile(MULTI_TENENT_FILE);
+        System.setProperty("Id", "1004");
+
+        CombinedConfiguration config = factory.getConfiguration(true);
+        assertTrue("Incorrect configuration", config instanceof DynamicCombinedConfiguration);
+
+        verify("1001", config, 15);
+        verify("1002", config, 25);
+        verify("1003", config, 35);
+        verify("1004", config, 50);
+        verify("1005", config, 50);
+    }
+
+    @Test
+    public void testMultiTenentConfiguration3() throws Exception
+    {
+        factory.setFile(MULTI_TENENT_FILE);
+        StringWriter writer = new StringWriter();
+        WriterAppender app = new WriterAppender(new SimpleLayout(), writer);
+        Log log = LogFactory.getLog("TestLogger");
+        Logger logger = ((Log4JLogger)log).getLogger();
+        logger.addAppender(app);
+        logger.setLevel(Level.DEBUG);
+        logger.setAdditivity(false);
+
+        System.setProperty("Id", "1005");
+
+        CombinedConfiguration config = factory.getConfiguration(true);
+        assertTrue("Incorrect configuration", config instanceof DynamicCombinedConfiguration);
+
+        verify("1001", config, 15);
+        String xml = writer.getBuffer().toString();
+        assertNotNull("No XML returned", xml);
+        assertTrue("Incorect configuration data", xml.indexOf("<rowsPerPage>15</rowsPerPage>") >= 0);
+        logger.removeAppender(app);
+        logger.setLevel(Level.OFF);
+        verify("1002", config, 25);
+        verify("1003", config, 35);
+        verify("1004", config, 50);
+        verify("1005", config, 50);
+    }
+
+    @Test
+    public void testMultiTenantConfigurationAt() throws Exception
+    {
+        factory.setFile(MULTI_TENENT_FILE);
+        System.setProperty("Id", "1001");
+        CombinedConfiguration config = factory.getConfiguration(true);
+        HierarchicalConfiguration sub1 = config.configurationAt("Channels/Channel[@id='1']");
+        assertEquals("My Channel", sub1.getString("Name"));
+        assertEquals("test 1 data", sub1.getString("ChannelData"));
+        HierarchicalConfiguration sub2 = config.configurationAt("Channels/Channel[@id='2']");
+        assertEquals("Channel 2", sub2.getString("Name"));
+        assertEquals("more test 2 data", sub2.getString("MoreChannelData"));
+    }
+
+    @Test
+    public void testMerge() throws Exception
+    {
+        factory.setFile(MULTI_TENENT_FILE);
+        System.setProperty("Id", "1004");
+        Map<String, String> map = new HashMap<String, String>();
+        map.put("default", "${colors.header4}");
+        map.put("background", "#40404040");
+        map.put("text", "#000000");
+        map.put("header", "#444444");
+
+        CombinedConfiguration config = factory.getConfiguration(true);
+        assertTrue("Incorrect configuration", config instanceof DynamicCombinedConfiguration);
+
+        List<HierarchicalConfiguration> list = config.configurationsAt("colors/*");
+        Iterator<HierarchicalConfiguration> iter = list.iterator();
+        while (iter.hasNext())
+        {
+            SubnodeConfiguration sub = (SubnodeConfiguration)iter.next();
+            ConfigurationNode node = sub.getRootNode();
+            String value = (node.getValue() == null) ? "null" : node.getValue().toString();
+            if (map.containsKey(node.getName()))
+            {
+                assertEquals(map.get(node.getName()), value);
+            }
+        }
+
+    }
+
+    @Test
+    public void testDelimiterParsingDisabled() throws Exception
+    {
+        factory.setFile(MULTI_TENENT_FILE);
+        System.setProperty("Id", "1004");
+
+        CombinedConfiguration config = factory.getConfiguration(true);
+        assertTrue("Incorrect configuration", config instanceof DynamicCombinedConfiguration);
+
+        assertEquals("a,b,c", config.getString("split/list3/@values"));
+        assertEquals(0, config.getMaxIndex("split/list3/@values"));
+        assertEquals("a\\,b\\,c", config.getString("split/list4/@values"));
+        assertEquals("a,b,c", config.getString("split/list1"));
+        assertEquals(0, config.getMaxIndex("split/list1"));
+        assertEquals("a\\,b\\,c", config.getString("split/list2"));
+    }
+
+    @Test
+    public void testExpression() throws Exception
+    {
+        if (SystemUtils.isJavaVersionAtLeast(150))
+        {
+            factory.setFile(EXPRESSION_FILE);
+            factory.setAttributeSplittingDisabled(true);
+            System.getProperties().remove("Id");
+            org.slf4j.MDC.clear();
+
+            CombinedConfiguration config = factory.getConfiguration(true);
+            assertTrue("Incorrect configuration",
+                    config instanceof DynamicCombinedConfiguration);
+
+            verify("1001", config, 15);
+        }
+    }
+
+    /**
+     * Tests whether variable substitution works across multiple child
+     * configurations. This test is related to CONFIGURATION-481.
+     */
+    @Test
+    public void testInterpolationOverMultipleSources()
+            throws ConfigurationException
+    {
+        File testFile =
+                ConfigurationAssert.getTestFile("testInterpolationBuilder.xml");
+        factory.setFile(testFile);
+        CombinedConfiguration combConfig = factory.getConfiguration(true);
+        assertEquals("Wrong value", "abc-product",
+                combConfig.getString("products.product.desc"));
+        XMLConfiguration xmlConfig =
+                (XMLConfiguration) combConfig.getConfiguration("test");
+        assertEquals("Wrong value from XML config", "abc-product",
+                xmlConfig.getString("products/product/desc"));
+        SubnodeConfiguration subConfig =
+                xmlConfig
+                        .configurationAt("products/product[@name='abc']", true);
+        assertEquals("Wrong value from sub config", "abc-product",
+                subConfig.getString("desc"));
+    }
+
+    private void verify(String key, CombinedConfiguration config, int rows)
+    {
+        System.setProperty("Id", key);
+        org.slf4j.MDC.put("Id", key);
+        int actual = config.getInt("rowsPerPage");
+        assertTrue("expected: " + rows + " actual: " + actual, actual == rows);
+    }
+
+
+    /**
+     * A specialized combined configuration implementation used for testing
+     * custom result classes.
+     */
+    public static class ExtendedCombinedConfiguration extends
+            CombinedConfiguration
+    {
+        /**
+         * The serial version UID.
+         */
+        private static final long serialVersionUID = 4678031745085083392L;
+
+        @Override
+        public Object getProperty(String key)
+        {
+            if (key.equals("test"))
+            {
+                return "Extended";
+            }
+            return super.getProperty(key);
+        }
+    }
+
+    public static class ExtendedXMLConfiguration extends XMLConfiguration
+    {
+        private static final long serialVersionUID = 1L;
+
+        public ExtendedXMLConfiguration()
+        {
+        }
+
+    }
+
+    public static class TestLookup extends StrLookup
+    {
+        Map<String, String> map = new HashMap<String, String>();
+
+        public TestLookup()
+        {
+            map.put("test_file_xml", "test.xml");
+            map.put("test_file_combine", "testcombine1.xml");
+            map.put("test_key", "test.value");
+        }
+
+        @Override
+        public String lookup(String key)
+        {
+            if (key == null)
+            {
+                return null;
+            }
+            return map.get(key);
+
+        }
+    }
+}
+
diff --git a/src/test/java/org/apache/commons/configuration/TestDynamicCombinedConfiguration.java b/src/test/java/org/apache/commons/configuration/TestDynamicCombinedConfiguration.java
new file mode 100644
index 0000000..97a4289
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestDynamicCombinedConfiguration.java
@@ -0,0 +1,371 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+
+import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
+import org.apache.commons.lang.text.StrLookup;
+import org.junit.Test;
+
+public class TestDynamicCombinedConfiguration
+{
+    private static String PATTERN = "${sys:Id}";
+    private static String PATTERN1 = "target/test-classes/testMultiConfiguration_${sys:Id}.xml";
+    private static String DEFAULT_FILE = "target/test-classes/testMultiConfiguration_default.xml";
+    private static final File MULTI_TENENT_FILE = new File(
+            "conf/testMultiTenentConfigurationBuilder4.xml");
+    private static final File MULTI_DYNAMIC_FILE = new File(
+            "conf/testMultiTenentConfigurationBuilder5.xml");
+
+    /** Constant for the number of test threads. */
+    private static final int THREAD_COUNT = 3;
+
+    /** Constant for the number of loops in the multi-thread tests. */
+    private static final int LOOP_COUNT = 100;
+
+    @Test
+    public void testConfiguration() throws Exception
+    {
+        DynamicCombinedConfiguration config = new DynamicCombinedConfiguration();
+        XPathExpressionEngine engine = new XPathExpressionEngine();
+        config.setExpressionEngine(engine);
+        config.setKeyPattern(PATTERN);
+        config.setDelimiterParsingDisabled(true);
+        MultiFileHierarchicalConfiguration multi = new MultiFileHierarchicalConfiguration(PATTERN1);
+        multi.setExpressionEngine(engine);
+        config.addConfiguration(multi, "Multi");
+        XMLConfiguration xml = new XMLConfiguration();
+        xml.setExpressionEngine(engine);
+        xml.setDelimiterParsingDisabled(true);
+        xml.setFile(new File(DEFAULT_FILE));
+        xml.load();
+        config.addConfiguration(xml, "Default");
+
+        verify("1001", config, 15);
+        verify("1002", config, 25);
+        verify("1003", config, 35);
+        verify("1004", config, 50);
+        assertEquals("a,b,c", config.getString("split/list3/@values"));
+        assertEquals(0, config.getMaxIndex("split/list3/@values"));
+        assertEquals("a\\,b\\,c", config.getString("split/list4/@values"));
+        assertEquals("a,b,c", config.getString("split/list1"));
+        assertEquals(0, config.getMaxIndex("split/list1"));
+        assertEquals("a\\,b\\,c", config.getString("split/list2"));
+    }
+
+    @Test
+    public void testConcurrentGetAndReload() throws Exception
+    {
+        System.getProperties().remove("Id");
+        DefaultConfigurationBuilder factory = new DefaultConfigurationBuilder();
+        factory.setFile(MULTI_TENENT_FILE);
+        CombinedConfiguration config = factory.getConfiguration(true);
+
+        assertEquals(config.getString("rowsPerPage"), "50");
+        Thread testThreads[] = new Thread[THREAD_COUNT];
+        int failures[] = new int[THREAD_COUNT];
+
+        for (int i = 0; i < testThreads.length; ++i)
+        {
+            testThreads[i] = new ReloadThread(config, failures, i, LOOP_COUNT, false, null, "50");
+            testThreads[i].start();
+        }
+
+        int totalFailures = 0;
+        for (int i = 0; i < testThreads.length; ++i)
+        {
+            testThreads[i].join();
+            totalFailures += failures[i];
+        }
+        assertTrue(totalFailures + " failures Occurred", totalFailures == 0);
+    }
+
+    @Test
+    public void testConcurrentGetAndReload2() throws Exception
+    {
+        System.getProperties().remove("Id");
+        DefaultConfigurationBuilder factory = new DefaultConfigurationBuilder();
+        factory.setFile(MULTI_TENENT_FILE);
+        CombinedConfiguration config = factory.getConfiguration(true);
+
+        assertEquals(config.getString("rowsPerPage"), "50");
+
+        Thread testThreads[] = new Thread[THREAD_COUNT];
+        int failures[] = new int[THREAD_COUNT];
+        System.setProperty("Id", "2002");
+        assertEquals(config.getString("rowsPerPage"), "25");
+        for (int i = 0; i < testThreads.length; ++i)
+        {
+            testThreads[i] = new ReloadThread(config, failures, i, LOOP_COUNT, false, null, "25");
+            testThreads[i].start();
+        }
+
+        int totalFailures = 0;
+        for (int i = 0; i < testThreads.length; ++i)
+        {
+            testThreads[i].join();
+            totalFailures += failures[i];
+        }
+        System.getProperties().remove("Id");
+        assertTrue(totalFailures + " failures Occurred", totalFailures == 0);
+    }
+
+    @Test
+    public void testConcurrentGetAndReloadMultipleClients() throws Exception
+    {
+        System.getProperties().remove("Id");
+        DefaultConfigurationBuilder factory = new DefaultConfigurationBuilder();
+        factory.setFile(MULTI_TENENT_FILE);
+        CombinedConfiguration config = factory.getConfiguration(true);
+
+        assertEquals(config.getString("rowsPerPage"), "50");
+
+        Thread testThreads[] = new Thread[THREAD_COUNT];
+        int failures[] = new int[THREAD_COUNT];
+        String[] ids = new String[] {null, "2002", "3001", "3002", "3003"};
+        String[] expected = new String[] {"50", "25", "15", "25", "50"};
+        for (int i = 0; i < testThreads.length; ++i)
+        {
+            testThreads[i] = new ReloadThread(config, failures, i, LOOP_COUNT, true, ids[i], expected[i]);
+            testThreads[i].start();
+        }
+
+        int totalFailures = 0;
+        for (int i = 0; i < testThreads.length; ++i)
+        {
+            testThreads[i].join();
+            totalFailures += failures[i];
+        }
+        System.getProperties().remove("Id");
+        if (totalFailures != 0)
+        {
+            System.out.println("Failures:");
+            for (int i = 0; i < testThreads.length; ++i)
+            {
+                System.out.println("Thread " + i + " " + failures[i]);
+            }
+        }
+        assertTrue(totalFailures + " failures Occurred", totalFailures == 0);
+    }
+
+    @Test
+  public void testConcurrentGetAndReloadFile() throws Exception
+    {
+        final int threadCount = 25;
+        System.getProperties().remove("Id");
+        // create a new configuration
+        File input = new File("target/test-classes/testMultiDynamic_default.xml");
+        File output = new File("target/test-classes/testwrite/testMultiDynamic_default.xml");
+        output.delete();
+        output.getParentFile().mkdir();
+        copyFile(input, output);
+
+        DefaultConfigurationBuilder factory = new DefaultConfigurationBuilder();
+        factory.setFile(MULTI_DYNAMIC_FILE);
+        CombinedConfiguration config = factory.getConfiguration(true);
+
+        assertEquals(config.getString("Product/FIIndex/FI[@id='123456781']"), "ID0001");
+
+        ReaderThread testThreads[] = new ReaderThread[threadCount];
+        for (int i = 0; i < testThreads.length; ++i)
+        {
+            testThreads[i] = new ReaderThread(config);
+            testThreads[i].start();
+        }
+
+        Thread.sleep(2000);
+
+        input = new File("target/test-classes/testMultiDynamic_default2.xml");
+        copyFile(input, output);
+
+        Thread.sleep(2000);
+        String id = config.getString("Product/FIIndex/FI[@id='123456782']");
+        assertNotNull("File did not reload, id is null", id);
+        String rows = config.getString("rowsPerPage");
+        assertTrue("Incorrect value for rowsPerPage", "25".equals(rows));
+
+        for (int i = 0; i < testThreads.length; ++i)
+        {
+            testThreads[i].shutdown();
+            testThreads[i].join();
+        }
+        for (int i = 0; i < testThreads.length; ++i)
+        {
+            assertFalse(testThreads[i].failed());
+        }
+        assertEquals("ID0002", config.getString("Product/FIIndex/FI[@id='123456782']"));
+        output.delete();
+    }
+
+
+    private class ReloadThread extends Thread
+    {
+        CombinedConfiguration combined;
+        int[] failures;
+        int index;
+        int count;
+        String expected;
+        String id;
+        boolean useId;
+
+        ReloadThread(CombinedConfiguration config, int[] failures, int index, int count,
+                     boolean useId, String id, String expected)
+        {
+            combined = config;
+            this.failures = failures;
+            this.index = index;
+            this.count = count;
+            this.expected = expected;
+            this.id = id;
+            this.useId = useId;
+        }
+        @Override
+        public void run()
+        {
+            failures[index] = 0;
+
+            if (useId)
+            {
+                ThreadLookup.setId(id);
+            }
+            for (int i = 0; i < count; i++)
+            {
+                try
+                {
+                    String value = combined.getString("rowsPerPage", null);
+                    if (value == null || !value.equals(expected))
+                    {
+                        ++failures[index];
+                    }
+                }
+                catch (Exception ex)
+                {
+                    ++failures[index];
+                }
+            }
+        }
+    }
+
+    private class ReaderThread extends Thread
+    {
+        private boolean running = true;
+        private boolean failed = false;
+        CombinedConfiguration combined;
+
+        public ReaderThread(CombinedConfiguration c)
+        {
+            combined = c;
+        }
+
+        @Override
+        public void run()
+        {
+            while (running)
+            {
+                String bcId = combined.getString("Product/FIIndex/FI[@id='123456781']");
+                if ("ID0001".equalsIgnoreCase(bcId))
+                {
+                    if (failed)
+                    {
+                        System.out.println("Thread failed, but recovered");
+                    }
+                    failed = false;
+                }
+                else
+                {
+                    failed = true;
+                }
+            }
+        }
+
+        public boolean failed()
+        {
+            return failed;
+        }
+
+        public void shutdown()
+        {
+            running = false;
+        }
+
+    }
+
+    private void verify(String key, DynamicCombinedConfiguration config, int rows)
+    {
+        System.setProperty("Id", key);
+        assertTrue(config.getInt("rowsPerPage") == rows);
+    }
+
+    private void copyFile(File input, File output) throws IOException
+    {
+        Reader reader = new FileReader(input);
+        Writer writer = new FileWriter(output);
+        char[] buffer = new char[4096];
+        int n = 0;
+        while (-1 != (n = reader.read(buffer)))
+        {
+            writer.write(buffer, 0, n);
+        }
+        reader.close();
+        writer.close();
+    }
+
+    public static class ThreadLookup extends StrLookup
+    {
+        private static ThreadLocal<String> id = new ThreadLocal<String>();
+
+
+
+        public ThreadLookup()
+        {
+
+        }
+
+        public static void setId(String value)
+        {
+            id.set(value);
+        }
+
+        @Override
+        public String lookup(String key)
+        {
+            if (key == null || !key.equals("Id"))
+            {
+                return null;
+            }
+            String value = System.getProperty("Id");
+            if (value != null)
+            {
+                return value;
+            }
+            return id.get();
+
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestEnvironmentConfiguration.java b/src/test/java/org/apache/commons/configuration/TestEnvironmentConfiguration.java
new file mode 100644
index 0000000..be54b05
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestEnvironmentConfiguration.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Iterator;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for EnvironmentConfiguration.
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: TestEnvironmentConfiguration.java 1224637 2011-12-25 19:47:21Z oheger $
+ */
+public class TestEnvironmentConfiguration
+{
+    /** Stores the configuration to be tested. */
+    private EnvironmentConfiguration config;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        config = new EnvironmentConfiguration();
+    }
+
+    /**
+     * Tests whether a newly created configuration contains some properties. (We
+     * expect that at least some properties are set in each environment.)
+     */
+    @Test
+    public void testInit()
+    {
+        boolean found = false;
+        assertFalse("No properties found", config.isEmpty());
+        for (Iterator<String> it = config.getKeys(); it.hasNext();)
+        {
+            String key = it.next();
+            assertTrue("Key not found: " + key, config.containsKey(key));
+            assertNotNull("No value for property " + key, config.getString(key));
+            found = true;
+        }
+        assertTrue("No property keys returned", found);
+    }
+
+    /**
+     * Tests removing properties. This should not be possible.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testClearProperty()
+    {
+        String key = config.getKeys().next();
+        config.clearProperty(key);
+    }
+
+    /**
+     * Tests removing all properties. This should not be possible.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testClear()
+    {
+        config.clear();
+    }
+
+    /**
+     * Tries to add another property. This should cause an exception.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testAddProperty()
+    {
+        config.addProperty("JAVA_HOME", "C:\\java");
+    }
+
+    /**
+     * Tries to set the value of a property. This should cause an exception.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testSetProperty()
+    {
+        config.setProperty("JAVA_HOME", "C:\\java");
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestEqualBehaviour.java b/src/test/java/org/apache/commons/configuration/TestEqualBehaviour.java
new file mode 100644
index 0000000..9f8f2e8
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestEqualBehaviour.java
@@ -0,0 +1,258 @@
+package org.apache.commons.configuration;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.junit.Test;
+
+/**
+ * Compare the behavior of various methods between CompositeConfiguration
+ * and normal (Properties) Configuration
+ *
+ * @version $Id: TestEqualBehaviour.java 1224638 2011-12-25 19:51:09Z oheger $
+ */
+public class TestEqualBehaviour
+{
+    private Configuration setupSimpleConfiguration()
+            throws Exception
+    {
+        String simpleConfigurationFile = ConfigurationAssert.getTestFile("testEqual.properties").getAbsolutePath();
+        return new PropertiesConfiguration(simpleConfigurationFile);
+    }
+
+    @SuppressWarnings("deprecation")
+    private Configuration setupCompositeConfiguration()
+            throws Exception
+    {
+        String compositeConfigurationFile = ConfigurationAssert.getTestFile("testEqualDigester.xml").getAbsolutePath();
+
+        ConfigurationFactory configurationFactory = new ConfigurationFactory();
+        configurationFactory.setConfigurationFileName(compositeConfigurationFile);
+        return configurationFactory.getConfiguration();
+    }
+
+    /**
+     * Checks whether two configurations have the same size,
+     * the same key sequence and contain the same key -> value mappings
+     */
+    private void checkEquality(String msg, Configuration c1, Configuration c2)
+    {
+        Iterator<String> it1 = c1.getKeys();
+        Iterator<String> it2 = c2.getKeys();
+
+        while(it1.hasNext() && it2.hasNext())
+        {
+            String key1 = it1.next();
+            String key2 = it2.next();
+            assertEquals(msg + ", Keys: ", key1, key2);
+            assertEquals(msg + ", Contains: ", c1.containsKey(key1), c2.containsKey(key2));
+        }
+        assertEquals(msg + ", Iterator: ", it1.hasNext(), it2.hasNext());
+    }
+
+    /**
+     * Checks whether two configurations have the same key -> value mapping
+     */
+    private void checkSameKey(String msg, String key, Configuration c1, Configuration c2)
+    {
+        String [] s1 = c1.getStringArray(key);
+        String [] s2 = c2.getStringArray(key);
+
+        assertEquals(msg + ", length: ", s1.length, s2.length);
+
+        for (int i = 0; i < s1.length ; i++)
+        {
+            assertEquals(msg + ", String Array: ", s1[i], s2[i]);
+        }
+
+        List<Object> list1 = c1.getList(key);
+        List<Object> list2 = c2.getList(key);
+
+        assertEquals(msg + ", Size: ", list1.size(), list2.size());
+
+        Iterator<Object> it1 = list1.iterator();
+        Iterator<Object> it2 = list2.iterator();
+
+        while(it1.hasNext() && it2.hasNext())
+        {
+            String val1 = (String) it1.next();
+            String val2 = (String) it2.next();
+            assertEquals(msg + ", List: ", val1, val2);
+        }
+        assertEquals(msg + ", Iterator End: ", it1.hasNext(), it2.hasNext());
+    }
+
+    /**
+     * Are both configurations equal after loading?
+     */
+    @Test
+    public void testLoading() throws Exception
+    {
+        Configuration simple = setupSimpleConfiguration();
+        Configuration composite = setupCompositeConfiguration();
+
+        checkEquality("testLoading", simple, composite);
+    }
+
+    /**
+     * If we delete a key, does it vanish? Does it leave all
+     * the other keys unchanged? How about an unset key?
+     */
+    @Test
+    public void testDeletingExisting() throws Exception
+    {
+        Configuration simple = setupSimpleConfiguration();
+        Configuration composite = setupCompositeConfiguration();
+
+        String key = "clear.property";
+
+        assertTrue(simple.containsKey(key));
+        assertEquals(simple.containsKey(key), composite.containsKey(key));
+
+        simple.clearProperty(key);
+        composite.clearProperty(key);
+
+        assertFalse(simple.containsKey(key));
+        assertEquals(simple.containsKey(key), composite.containsKey(key));
+
+        checkEquality("testDeletingExisting", simple, composite);
+    }
+
+    @Test
+    public void testDeletingNonExisting() throws Exception
+    {
+        Configuration simple = setupSimpleConfiguration();
+        Configuration composite = setupCompositeConfiguration();
+
+        String key = "nonexisting.clear.property";
+
+        assertFalse(simple.containsKey(key));
+        assertEquals(simple.containsKey(key), composite.containsKey(key));
+
+        simple.clearProperty(key);
+        composite.clearProperty(key);
+
+        assertFalse(simple.containsKey(key));
+        assertEquals(simple.containsKey(key), composite.containsKey(key));
+
+        checkEquality("testDeletingNonExisting", simple, composite);
+    }
+
+    /**
+     * If we set a key, does it work? How about an existing
+     * key? Can we change it?
+     */
+    @Test
+    public void testSettingNonExisting() throws Exception
+    {
+        Configuration simple = setupSimpleConfiguration();
+        Configuration composite = setupCompositeConfiguration();
+
+        String key = "nonexisting.property";
+        String value = "new value";
+
+        assertFalse(simple.containsKey(key));
+        assertEquals(simple.containsKey(key), composite.containsKey(key));
+
+        simple.setProperty(key, value);
+        composite.setProperty(key, value);
+
+        assertTrue(simple.containsKey(key));
+        assertEquals(simple.containsKey(key), composite.containsKey(key));
+
+        checkSameKey("testSettingNonExisting", key, simple, composite);
+        checkEquality("testSettingNonExisting", simple, composite);
+    }
+
+    @Test
+    public void testSettingExisting() throws Exception
+    {
+        Configuration simple = setupSimpleConfiguration();
+        Configuration composite = setupCompositeConfiguration();
+
+        String key = "existing.property";
+        String value = "new value";
+
+        assertTrue(simple.containsKey(key));
+        assertFalse(simple.getString(key).equals(value));
+        assertEquals(simple.containsKey(key), composite.containsKey(key));
+
+        simple.setProperty(key, value);
+        composite.setProperty(key, value);
+
+        assertTrue(simple.containsKey(key));
+        assertEquals(simple.getString(key), value);
+        assertEquals(simple.containsKey(key), composite.containsKey(key));
+
+        checkSameKey("testSettingExisting", key, simple, composite);
+        checkEquality("testSettingExisting", simple, composite);
+    }
+
+    /**
+     * If we add a key, does it work?
+     */
+    @Test
+    public void testAddingUnset() throws Exception
+    {
+        Configuration simple = setupSimpleConfiguration();
+        Configuration composite = setupCompositeConfiguration();
+
+        String key = "nonexisting.property";
+        String value = "new value";
+
+        assertFalse(simple.containsKey(key));
+        assertEquals(simple.containsKey(key), composite.containsKey(key));
+
+        simple.addProperty(key, value);
+        composite.addProperty(key, value);
+
+        checkSameKey("testAddingUnset", key, simple, composite);
+        checkEquality("testAddingUnset", simple, composite);
+    }
+
+    /**
+     * If we add a to an existing key, does it work?
+     */
+    @Test
+    public void testAddingSet() throws Exception
+    {
+        Configuration simple = setupSimpleConfiguration();
+        Configuration composite = setupCompositeConfiguration();
+
+        String key = "existing.property";
+        String value = "new value";
+
+        assertTrue(simple.containsKey(key));
+        assertEquals(simple.containsKey(key), composite.containsKey(key));
+
+        simple.addProperty(key, value);
+        composite.addProperty(key, value);
+
+        assertTrue(simple.containsKey(key));
+        assertEquals(simple.containsKey(key), composite.containsKey(key));
+
+        checkSameKey("testAddingSet", key, simple, composite);
+        checkEquality("testAddingSet", simple, composite);
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestEqualsProperty.java b/src/test/java/org/apache/commons/configuration/TestEqualsProperty.java
new file mode 100644
index 0000000..9830aeb
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestEqualsProperty.java
@@ -0,0 +1,42 @@
+package org.apache.commons.configuration;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+/**
+ * test if properties that contain a "=" will be loaded correctly.
+ *
+ * @version $Id: TestEqualsProperty.java 1224639 2011-12-25 19:52:46Z oheger $
+ */
+public class TestEqualsProperty
+{
+    /** The File that we test with */
+    private String testProperties = ConfigurationAssert.getTestFile("test.properties").getAbsolutePath();
+
+    @Test
+    public void testEquals() throws Exception
+    {
+        PropertiesConfiguration conf = new PropertiesConfiguration(testProperties);
+
+        String equals = conf.getString("test.equals");
+        assertEquals("value=one", equals);
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestFileConfiguration.java b/src/test/java/org/apache/commons/configuration/TestFileConfiguration.java
new file mode 100644
index 0000000..60a1191
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestFileConfiguration.java
@@ -0,0 +1,652 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.URL;
+import java.util.Iterator;
+import java.util.Properties;
+
+import org.apache.commons.configuration.reloading.FileAlwaysReloadingStrategy;
+import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * @author Emmanuel Bourg
+ * @version $Id: TestFileConfiguration.java 1231721 2012-01-15 18:32:07Z oheger $
+ */
+public class TestFileConfiguration
+{
+    /** Constant for the name of a test file.*/
+    private static final String TEST_FILENAME = "test.properties";
+
+    /** Constant for a test file.*/
+    private static final File TEST_FILE = ConfigurationAssert.getTestFile(TEST_FILENAME);
+
+    /** Constant for a test output file. */
+    private static final File OUT_FILE = new File(
+            "target/test-resources/foo/bar/test.properties");
+
+    /** Constant for the name of a resource to be resolved.*/
+    private static final String RESOURCE_NAME = "config/deep/deeptest.properties";
+
+    /** Helper object for managing temporary files. */
+    @Rule
+    public TemporaryFolder folder = new TemporaryFolder();
+
+    /**
+     * Initializes the test environment. This implementation ensures that the
+     * test output file does not exist.
+     */
+    @Before
+    public void setUp() throws Exception
+    {
+        removeOutFile();
+    }
+
+    /**
+     * Performs cleanup after a test case. This implementation removes temporary
+     * files that have been created.
+     */
+    protected void tearDown() throws Exception
+    {
+        removeOutFile();
+    }
+
+    /**
+     * Removes a file if it exists.
+     *
+     * @param file the file to be removed
+     */
+    private static void removeFile(File file)
+    {
+        if (file.exists())
+        {
+            assertTrue("Cannot remove file: " + file, file.delete());
+        }
+    }
+
+    /**
+     * Removes the test output file if it exists. Its parent directories are
+     * also removed.
+     */
+    private static void removeOutFile()
+    {
+        removeFile(OUT_FILE);
+        File parent = OUT_FILE.getParentFile();
+        removeFile(parent);
+        parent = parent.getParentFile();
+        removeFile(parent);
+    }
+
+    @Test
+    public void testSetURL() throws Exception
+    {
+        // http URL
+        FileConfiguration config = new PropertiesConfiguration();
+        config.setURL(new URL("http://commons.apache.org/configuration/index.html"));
+
+        assertEquals("base path", "http://commons.apache.org/configuration/", config.getBasePath());
+        assertEquals("file name", "index.html", config.getFileName());
+
+        // file URL - This url is invalid, a valid url would be file:///temp/test.properties.
+        config.setURL(new URL("file:/temp/test.properties"));
+        assertEquals("base path", "file:///temp/", config.getBasePath());
+        assertEquals("file name", "test.properties", config.getFileName());
+    }
+
+    @Test
+    public void testSetURLWithParams() throws Exception
+    {
+        FileConfiguration config = new PropertiesConfiguration();
+        URL url = new URL("http://issues.apache.org/bugzilla/show_bug.cgi?id=37886");
+        config.setURL(url);
+        assertEquals("Base path incorrect", "http://issues.apache.org/bugzilla/", config.getBasePath());
+        assertEquals("File name incorrect", "show_bug.cgi", config.getFileName());
+        assertEquals("URL was not correctly stored", url, config.getURL());
+    }
+
+    @Test
+    public void testLocations() throws Exception
+    {
+        PropertiesConfiguration config = new PropertiesConfiguration();
+
+        File directory = ConfigurationAssert.TEST_DIR;
+        File file = TEST_FILE;
+        config.setFile(file);
+        assertEquals(directory.getAbsolutePath(), config.getBasePath());
+        assertEquals(TEST_FILENAME, config.getFileName());
+        assertEquals(file.getAbsolutePath(), config.getPath());
+
+        config.setPath(ConfigurationAssert.TEST_DIR_NAME + File.separator + TEST_FILENAME);
+        assertEquals(TEST_FILENAME, config.getFileName());
+        assertEquals(directory.getAbsolutePath(), config.getBasePath());
+        assertEquals(file.getAbsolutePath(), config.getPath());
+        assertEquals(file.toURI().toURL(), config.getURL());
+
+        config.setBasePath(null);
+        config.setFileName(TEST_FILENAME);
+        assertNull(config.getBasePath());
+        assertEquals(TEST_FILENAME, config.getFileName());
+    }
+
+    @Test
+    public void testCreateFile1() throws Exception
+    {
+        assertFalse("The file should not exist", OUT_FILE.exists());
+
+        FileConfiguration config = new PropertiesConfiguration(OUT_FILE);
+        config.save();
+
+        assertTrue("The file doesn't exist", OUT_FILE.exists());
+    }
+
+    @Test
+    public void testCreateFile2() throws Exception
+    {
+        FileConfiguration config = new PropertiesConfiguration();
+        config.setFile(OUT_FILE);
+        config.save();
+
+        assertTrue("The file doesn't exist", OUT_FILE.exists());
+    }
+
+    @Test
+    public void testCreateFile3() throws Exception
+    {
+        FileConfiguration config = new PropertiesConfiguration();
+        config.save(OUT_FILE);
+
+        assertTrue("The file doesn't exist", OUT_FILE.exists());
+    }
+
+    /**
+     * Tests collaboration with ConfigurationFactory: Is the base path set on
+     * loading is valid in file based configurations?
+     *
+     * @throws Exception if an error occurs
+     */
+    @SuppressWarnings("deprecation")
+    @Test
+    public void testWithConfigurationFactory() throws Exception
+    {
+        File file = folder.newFile();
+
+        ConfigurationFactory factory = new ConfigurationFactory();
+        factory.setConfigurationURL(ConfigurationAssert.getTestURL(
+                "testDigesterConfiguration2.xml"));
+        CompositeConfiguration cc =
+                (CompositeConfiguration) factory.getConfiguration();
+        PropertiesConfiguration config = null;
+        for (int i = 0; config == null; i++)
+        {
+            if (cc.getConfiguration(i) instanceof PropertiesConfiguration)
+            {
+                config = (PropertiesConfiguration) cc.getConfiguration(i);
+            }
+        }
+
+        config.setProperty("test", "yes");
+        config.save(file);
+        assertTrue(file.exists());
+        config = new PropertiesConfiguration();
+        config.setFile(file);
+        config.load();
+
+        assertEquals("yes", config.getProperty("test"));
+        assertEquals("masterOfPost", config.getProperty("mail.account.user"));
+    }
+
+    /**
+     * Tests if invalid URLs cause an exception.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testSaveInvalidURL() throws Exception
+    {
+        FileConfiguration config = new PropertiesConfiguration();
+        config.save(new URL("http://jakarta.apache.org/test.properties"));
+    }
+
+    /**
+     * Tests if an invalid URL string causes an exception.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testSaveInvalidURLString() throws ConfigurationException
+    {
+        FileConfiguration config = new PropertiesConfiguration();
+        config.save("http://www.apache.org/test.properties");
+    }
+
+    /**
+     * Tests if the URL used by the load() method is also used by save().
+     */
+    @Test
+    public void testFileOverwrite() throws Exception
+    {
+        FileOutputStream out = null;
+        FileInputStream in = null;
+        File tempFile = null;
+        try
+        {
+            tempFile = folder.newFile();
+            Properties props = new Properties();
+            props.setProperty("1", "one");
+            out = new FileOutputStream(tempFile);
+            props.store(out, "TestFileOverwrite");
+            out.close();
+            out = null;
+            FileConfiguration config = new PropertiesConfiguration(tempFile);
+            config.load();
+            String value = config.getString("1");
+            assertTrue("one".equals(value));
+            config.setProperty("1", "two");
+            config.save();
+            props = new Properties();
+            in = new FileInputStream(tempFile);
+            props.load(in);
+            String value2 = props.getProperty("1");
+            assertTrue("two".equals(value2));
+        }
+        finally
+        {
+            if (out != null)
+            {
+                try
+                {
+                    out.close();
+                }
+                catch (IOException ioex)
+                {
+                    ioex.printStackTrace();
+                }
+            }
+            if (in != null)
+            {
+                try
+                {
+                    in.close();
+                }
+                catch (IOException ioex)
+                {
+                    ioex.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * Tests setting a file changed reloading strategy together with the auto
+     * save feature.
+     */
+    @Test
+    public void testReloadingWithAutoSave() throws Exception
+    {
+        File configFile = folder.newFile();
+        PrintWriter out = null;
+
+        try
+        {
+            out = new PrintWriter(new FileWriter(configFile));
+            out.println("a = one");
+            out.close();
+            out = null;
+
+            PropertiesConfiguration config = new PropertiesConfiguration(
+                    configFile);
+            config.setReloadingStrategy(new FileChangedReloadingStrategy());
+            config.setAutoSave(true);
+
+            assertEquals("one", config.getProperty("a"));
+            config.setProperty("b", "two");
+            assertEquals("one", config.getProperty("a"));
+        }
+        finally
+        {
+            if (out != null)
+            {
+                out.close();
+            }
+        }
+    }
+
+    /**
+     * Tests loading and saving a configuration file with a complicated path
+     * name including spaces. (related to issue 35210)
+     */
+    @Test
+    public void testPathWithSpaces() throws Exception
+    {
+        File path = folder.newFolder("path with spaces");
+        File confFile = new File(path, "config-test.properties");
+        PrintWriter out = null;
+
+        try
+        {
+            out = new PrintWriter(new FileWriter(confFile));
+            out.println("saved = false");
+            out.close();
+            out = null;
+
+            URL url = confFile.toURI().toURL();
+            PropertiesConfiguration config = new PropertiesConfiguration(url);
+            config.load();
+            assertFalse(config.getBoolean("saved"));
+
+            config.setProperty("saved", Boolean.TRUE);
+            config.save();
+            config = new PropertiesConfiguration();
+            config.setFile(confFile);
+            config.load();
+            assertTrue(config.getBoolean("saved"));
+        }
+        finally
+        {
+            if (out != null)
+            {
+                out.close();
+            }
+        }
+    }
+
+    /**
+     * Tests whether file names containing a "+" character are handled
+     * correctly. This test is related to CONFIGURATION-415.
+     */
+    @Test
+    public void testPathWithPlus() throws ConfigurationException, IOException
+    {
+        File saveFile = folder.newFile("test+config.properties");
+        FileConfiguration config = new PropertiesConfiguration(saveFile);
+        config.addProperty("test", Boolean.TRUE);
+        config.save();
+        File configFile = config.getFile();
+        assertEquals("Wrong configuration file", saveFile, configFile);
+    }
+
+    /**
+     * Tests the getFile() method.
+     */
+    @Test
+    public void testGetFile() throws ConfigurationException
+    {
+        FileConfiguration config = new PropertiesConfiguration();
+        assertNull(config.getFile());
+        File file = TEST_FILE.getAbsoluteFile();
+        config.setFile(file);
+        assertEquals(file, config.getFile());
+        config.load();
+        assertEquals(file, config.getFile());
+    }
+
+    /**
+     * Tests whether getFile() returns a valid file after a configuration has
+     * been loaded.
+     */
+    @Test
+    public void testGetFileAfterLoad() throws ConfigurationException,
+            IOException
+    {
+        FileConfiguration config = new PropertiesConfiguration();
+        config.load(TEST_FILE.getAbsolutePath());
+        assertNotNull("No source URL set", config.getURL());
+        assertEquals("Wrong source file", TEST_FILE.getCanonicalFile(), config
+                .getFile().getCanonicalFile());
+    }
+
+    /**
+     * Tests whether calling load() multiple times changes the source. This
+     * should not be the case.
+     */
+    @Test
+    public void testLoadMultiple() throws ConfigurationException
+    {
+        FileConfiguration config = new PropertiesConfiguration();
+        config.load(TEST_FILE.getAbsolutePath());
+        URL srcUrl = config.getURL();
+        File srcFile = config.getFile();
+        File file2 = ConfigurationAssert.getTestFile("testEqual.properties");
+        config.load(file2.getAbsolutePath());
+        assertEquals("Source URL was changed", srcUrl, config.getURL());
+        assertEquals("Source file was changed", srcFile, config.getFile());
+    }
+
+    @Test(expected = ConfigurationException.class)
+    public void testSaveWithoutFileNameFile() throws Exception
+    {
+        FileConfiguration config = new PropertiesConfiguration();
+        config.load(TEST_FILE);
+        config.save();
+    }
+
+    @Test(expected = ConfigurationException.class)
+    public void testSaveWithoutFileNameURL() throws Exception
+    {
+        FileConfiguration config = new PropertiesConfiguration();
+        config.load(TEST_FILE.toURI().toURL());
+        config.save();
+    }
+
+    /**
+     * Checks that loading a directory instead of a file throws an exception.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testLoadDirectoryString() throws ConfigurationException
+    {
+        PropertiesConfiguration config = new PropertiesConfiguration();
+        config.load("target");
+    }
+
+    /**
+     * Tests that it is not possible to load a directory using the load() method
+     * which expects a File.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testLoadDirectoryFile() throws ConfigurationException
+    {
+        PropertiesConfiguration config = new PropertiesConfiguration();
+        config.load(new File("target"));
+    }
+
+    /**
+     * Tests that it is not possible to load a directory using the String constructor.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testLoadDirectoryConstrString() throws ConfigurationException
+    {
+        new PropertiesConfiguration("target");
+    }
+
+    /**
+     * Tests that it is not possible to load a directory using the File constructor.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testLoadDirectoryConstrFile() throws ConfigurationException
+    {
+        new PropertiesConfiguration(new File("target"));
+    }
+
+    /**
+     * Tests whether the constructor behaves the same as setFileName() when the
+     * configuration source is in the classpath.
+     */
+    @Test
+    public void testInitFromClassPath() throws ConfigurationException
+    {
+        PropertiesConfiguration config1 = new PropertiesConfiguration();
+        config1.setFileName(RESOURCE_NAME);
+        config1.load();
+        PropertiesConfiguration config2 = new PropertiesConfiguration(
+                RESOURCE_NAME);
+        compare(config1, config2);
+    }
+
+    /**
+     * Tests the loading of configuration file in a Combined configuration
+     * when the configuration source is in the classpath.
+     */
+    @Test
+    public void testLoadFromClassPath() throws ConfigurationException
+    {
+        DefaultConfigurationBuilder cf =
+            new DefaultConfigurationBuilder("config/deep/testFileFromClasspath.xml");
+        CombinedConfiguration config = cf.getConfiguration(true);
+        Configuration config1 = config.getConfiguration("propConf");
+        Configuration config2 = config.getConfiguration("propConfDeep");
+        compare(config1, config2);
+    }
+
+    /**
+     * Tests cloning a file based configuration.
+     */
+    @Test
+    public void testClone() throws ConfigurationException
+    {
+        PropertiesConfiguration config = new PropertiesConfiguration(
+                RESOURCE_NAME);
+        PropertiesConfiguration copy = (PropertiesConfiguration) config.clone();
+        compare(config, copy);
+        assertNull("URL was not reset", copy.getURL());
+        assertNull("Base path was not reset", copy.getBasePath());
+        assertNull("File name was not reset", copy.getFileName());
+        assertNotSame("Reloading strategy was not reset", config
+                .getReloadingStrategy(), copy.getReloadingStrategy());
+    }
+
+    /**
+     * Tests whether an error log listener was registered at the configuration.
+     */
+    @Test
+    public void testLogErrorListener()
+    {
+        PropertiesConfiguration config = new PropertiesConfiguration();
+        assertEquals("No error log listener registered", 1, config
+                .getErrorListeners().size());
+    }
+
+    /**
+     * Tests handling of errors in the reload() method.
+     */
+    @Test
+    public void testReloadError() throws ConfigurationException
+    {
+        ConfigurationErrorListenerImpl l = new ConfigurationErrorListenerImpl();
+        PropertiesConfiguration config = new PropertiesConfiguration(
+                RESOURCE_NAME);
+        config.clearErrorListeners();
+        config.addErrorListener(l);
+        config.setReloadingStrategy(new FileAlwaysReloadingStrategy());
+        config.getString("test");
+        config.setFileName("Not existing file");
+        config.getString("test");
+        l.verify(AbstractFileConfiguration.EVENT_RELOAD, null, null);
+        assertNotNull("Exception is not set", l.getLastEvent().getCause());
+    }
+
+    /**
+     * Tests iterating over the keys of a non hierarchical file-based
+     * configuration while a reload happens. This test is related to
+     * CONFIGURATION-347.
+     */
+    @Test
+    public void testIterationWithReloadFlat() throws ConfigurationException
+    {
+        PropertiesConfiguration config = new PropertiesConfiguration(TEST_FILE);
+        checkIterationWithReload(config);
+    }
+
+    /**
+     * Tests iterating over the keys of a hierarchical file-based configuration
+     * while a reload happens. This test is related to CONFIGURATION-347.
+     */
+    @Test
+    public void testIterationWithReloadHierarchical()
+            throws ConfigurationException
+    {
+        XMLConfiguration config = new XMLConfiguration("test.xml");
+        checkIterationWithReload(config);
+    }
+
+    /**
+     * Tests whether a configuration can be refreshed.
+     */
+    @Test
+    public void testRefresh() throws ConfigurationException
+    {
+        PropertiesConfiguration config = new PropertiesConfiguration(TEST_FILE);
+        assertEquals("Wrong value", 10, config.getInt("test.integer"));
+        config.setProperty("test.integer", new Integer(42));
+        assertEquals("Wrong value after update", 42,
+                config.getInt("test.integer"));
+        config.refresh();
+        assertEquals("Wrong value after refresh", 10,
+                config.getInt("test.integer"));
+    }
+
+    /**
+     * Tests refresh if the configuration is not associated with a file.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testRefreshNoFile() throws ConfigurationException
+    {
+        PropertiesConfiguration config = new PropertiesConfiguration();
+        config.refresh();
+    }
+
+    /**
+     * Helper method for testing an iteration over the keys of a file-based
+     * configuration while a reload happens.
+     *
+     * @param config the configuration to test
+     */
+    private void checkIterationWithReload(FileConfiguration config)
+    {
+        config.setReloadingStrategy(new FileAlwaysReloadingStrategy());
+        for (Iterator<String> it = config.getKeys(); it.hasNext();)
+        {
+            String key = it.next();
+            assertNotNull("No value for key " + key, config.getProperty(key));
+        }
+    }
+
+    /**
+     * Helper method for comparing the content of two configuration objects.
+     *
+     * @param config1 the first configuration
+     * @param config2 the second configuration
+     */
+    private void compare(Configuration config1, Configuration config2)
+    {
+        StrictConfigurationComparator cc = new StrictConfigurationComparator();
+        assertTrue("Configurations are different", cc.compare(config1, config2));
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/commons/configuration/TestHierarchicalConfiguration.java b/src/test/java/org/apache/commons/configuration/TestHierarchicalConfiguration.java
new file mode 100644
index 0000000..60c101a
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestHierarchicalConfiguration.java
@@ -0,0 +1,1252 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.configuration.HierarchicalConfiguration.Node;
+import org.apache.commons.configuration.event.ConfigurationEvent;
+import org.apache.commons.configuration.event.ConfigurationListener;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.DefaultConfigurationKey;
+import org.apache.commons.configuration.tree.DefaultConfigurationNode;
+import org.apache.commons.configuration.tree.DefaultExpressionEngine;
+import org.apache.commons.configuration.tree.ExpressionEngine;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for HierarchicalConfiguration.
+ *
+ * @version $Id: TestHierarchicalConfiguration.java 1231749 2012-01-15 20:48:56Z oheger $
+ */
+public class TestHierarchicalConfiguration
+{
+    private static String[] tables = { "users", "documents" };
+
+    private static String[][] fields =
+    {
+        { "uid", "uname", "firstName", "lastName", "email" },
+        { "docid", "name", "creationDate", "authorID", "version" }
+    };
+
+    private HierarchicalConfiguration config;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        /**
+         * Initialize the configuration with the following structure:
+         *
+         * tables
+         *      table
+         *         name
+         *         fields
+         *             field
+         *                 name
+         *             field
+         *                 name
+         */
+        config = new HierarchicalConfiguration();
+        HierarchicalConfiguration.Node nodeTables = createNode("tables", null);
+        for(int i = 0; i < tables.length; i++)
+        {
+            HierarchicalConfiguration.Node nodeTable = createNode("table", null);
+            nodeTables.addChild(nodeTable);
+            HierarchicalConfiguration.Node nodeName = createNode("name", tables[i]);
+            nodeTable.addChild(nodeName);
+            HierarchicalConfiguration.Node nodeFields = createNode("fields", null);
+            nodeTable.addChild(nodeFields);
+
+            for (int j = 0; j < fields[i].length; j++)
+            {
+                nodeFields.addChild(createFieldNode(fields[i][j]));
+            }
+        }
+
+        config.getRoot().addChild(nodeTables);
+    }
+
+    @Test
+    public void testSetRoot()
+    {
+        config.setRoot(new HierarchicalConfiguration.Node("test"));
+        assertTrue(config.isEmpty());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetRootNull()
+    {
+        config.setRoot(null);
+    }
+
+    @Test
+    public void testSetRootNode()
+    {
+        config.setRootNode(new DefaultConfigurationNode("testNode"));
+        assertNotSame("Same root node", config.getRootNode(), config.getRoot());
+        assertEquals("Wrong name of root node", "testNode", config.getRoot().getName());
+
+        config.setRootNode(new HierarchicalConfiguration.Node("test"));
+        assertSame("Wrong root node", config.getRootNode(), config.getRoot());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetRootNodeNull()
+    {
+        config.setRootNode(null);
+    }
+
+    @Test
+    public void testIsEmpty()
+    {
+        assertFalse(config.isEmpty());
+        HierarchicalConfiguration conf2 = new HierarchicalConfiguration();
+        assertTrue(conf2.isEmpty());
+        HierarchicalConfiguration.Node child1 = new HierarchicalConfiguration.Node("child1");
+        HierarchicalConfiguration.Node child2 = new HierarchicalConfiguration.Node("child2");
+        child1.addChild(child2);
+        conf2.getRoot().addChild(child1);
+        assertTrue(conf2.isEmpty());
+    }
+
+    @Test
+    public void testGetProperty()
+    {
+        assertNull(config.getProperty("tables.table.resultset"));
+        assertNull(config.getProperty("tables.table.fields.field"));
+
+        Object prop = config.getProperty("tables.table(0).fields.field.name");
+        assertNotNull(prop);
+        assertTrue(prop instanceof Collection);
+        assertEquals(5, ((Collection<?>) prop).size());
+
+        prop = config.getProperty("tables.table.fields.field.name");
+        assertNotNull(prop);
+        assertTrue(prop instanceof Collection);
+        assertEquals(10, ((Collection<?>) prop).size());
+
+        prop = config.getProperty("tables.table.fields.field(3).name");
+        assertNotNull(prop);
+        assertTrue(prop instanceof Collection);
+        assertEquals(2, ((Collection<?>) prop).size());
+
+        prop = config.getProperty("tables.table(1).fields.field(2).name");
+        assertNotNull(prop);
+        assertEquals("creationDate", prop.toString());
+    }
+
+    @Test
+    public void testSetProperty()
+    {
+        config.setProperty("tables.table(0).name", "resources");
+        assertEquals("resources", config.getString("tables.table(0).name"));
+        config.setProperty("tables.table.name", "tab1,tab2");
+        assertEquals("tab1", config.getString("tables.table(0).name"));
+        assertEquals("tab2", config.getString("tables.table(1).name"));
+
+        config.setProperty("test.items.item", new int[] { 2, 4, 8, 16 });
+        assertEquals(3, config.getMaxIndex("test.items.item"));
+        assertEquals(8, config.getInt("test.items.item(2)"));
+        config.setProperty("test.items.item(2)", new Integer(6));
+        assertEquals(6, config.getInt("test.items.item(2)"));
+        config.setProperty("test.items.item(2)", new int[] { 7, 9, 11 });
+        assertEquals(5, config.getMaxIndex("test.items.item"));
+
+        config.setProperty("test", Boolean.TRUE);
+        config.setProperty("test.items", "01/01/05");
+        assertEquals(5, config.getMaxIndex("test.items.item"));
+        assertTrue(config.getBoolean("test"));
+        assertEquals("01/01/05", config.getProperty("test.items"));
+
+        config.setProperty("test.items.item", new Integer(42));
+        assertEquals(0, config.getMaxIndex("test.items.item"));
+        assertEquals(42, config.getInt("test.items.item"));
+    }
+
+    @Test
+    public void testClear()
+    {
+        config.setProperty(null, "value");
+        config.addProperty("[@attr]", "defined");
+        config.clear();
+        assertTrue("Configuration not empty", config.isEmpty());
+    }
+
+    @Test
+    public void testClearProperty()
+    {
+        config.clearProperty("tables.table(0).fields.field(0).name");
+        assertEquals("uname", config.getProperty("tables.table(0).fields.field(0).name"));
+        config.clearProperty("tables.table(0).name");
+        assertFalse(config.containsKey("tables.table(0).name"));
+        assertEquals("firstName", config.getProperty("tables.table(0).fields.field(1).name"));
+        assertEquals("documents", config.getProperty("tables.table.name"));
+        config.clearProperty("tables.table");
+        assertEquals("documents", config.getProperty("tables.table.name"));
+
+        config.addProperty("test", "first");
+        config.addProperty("test.level", "second");
+        config.clearProperty("test");
+        assertEquals("second", config.getString("test.level"));
+        assertFalse(config.containsKey("test"));
+    }
+
+    @Test
+    public void testClearTree()
+    {
+        Object prop = config.getProperty("tables.table(0).fields.field.name");
+        assertNotNull(prop);
+        config.clearTree("tables.table(0).fields.field(3)");
+        prop = config.getProperty("tables.table(0).fields.field.name");
+        assertNotNull(prop);
+        assertTrue(prop instanceof Collection);
+        assertEquals(4, ((Collection<?>) prop).size());
+
+        config.clearTree("tables.table(0).fields");
+        assertNull(config.getProperty("tables.table(0).fields.field.name"));
+        prop = config.getProperty("tables.table.fields.field.name");
+        assertNotNull(prop);
+        assertTrue(prop instanceof Collection);
+        assertEquals(5, ((Collection<?>) prop).size());
+
+        config.clearTree("tables.table(1)");
+        assertNull(config.getProperty("tables.table.fields.field.name"));
+    }
+
+    /**
+     * Tests removing more complex node structures.
+     */
+    @Test
+    public void testClearTreeComplex()
+    {
+        final int count = 5;
+        // create the structure
+        for (int idx = 0; idx < count; idx++)
+        {
+            config.addProperty("indexList.index(-1)[@default]", Boolean.FALSE);
+            config.addProperty("indexList.index[@name]", "test" + idx);
+            config.addProperty("indexList.index.dir", "testDir" + idx);
+        }
+        assertEquals("Wrong number of nodes", count - 1, config
+                .getMaxIndex("indexList.index[@name]"));
+
+        // Remove a sub tree
+        boolean found = false;
+        for (int idx = 0; true; idx++)
+        {
+            String name = config.getString("indexList.index(" + idx
+                    + ")[@name]");
+            if (name == null)
+            {
+                break;
+            }
+            if ("test3".equals(name))
+            {
+                assertEquals("Wrong dir", "testDir3", config
+                        .getString("indexList.index(" + idx + ").dir"));
+                config.clearTree("indexList.index(" + idx + ")");
+                found = true;
+            }
+        }
+        assertTrue("Key to remove not found", found);
+        assertEquals("Wrong number of nodes after remove", count - 2, config
+                .getMaxIndex("indexList.index[@name]"));
+        assertEquals("Wrong number of dir nodes after remove", count - 2,
+                config.getMaxIndex("indexList.index.dir"));
+
+        // Verify
+        for (int idx = 0; true; idx++)
+        {
+            String name = config.getString("indexList.index(" + idx
+                    + ")[@name]");
+            if (name == null)
+            {
+                break;
+            }
+            if ("test3".equals(name))
+            {
+                fail("Key was not removed!");
+            }
+        }
+    }
+
+    /**
+     * Tests the clearTree() method on a hierarchical structure of nodes. This
+     * is a test case for CONFIGURATION-293.
+     */
+    @Test
+    public void testClearTreeHierarchy()
+    {
+        config.addProperty("a.b.c", "c");
+        config.addProperty("a.b.c.d", "d");
+        config.addProperty("a.b.c.d.e", "e");
+        config.clearTree("a.b.c");
+        assertFalse("Property not removed", config.containsKey("a.b.c"));
+        assertFalse("Sub property not removed", config.containsKey("a.b.c.d"));
+    }
+
+    @Test
+    public void testContainsKey()
+    {
+        assertTrue(config.containsKey("tables.table(0).name"));
+        assertTrue(config.containsKey("tables.table(1).name"));
+        assertFalse(config.containsKey("tables.table(2).name"));
+
+        assertTrue(config.containsKey("tables.table(0).fields.field.name"));
+        assertFalse(config.containsKey("tables.table(0).fields.field"));
+        config.clearTree("tables.table(0).fields");
+        assertFalse(config.containsKey("tables.table(0).fields.field.name"));
+
+        assertTrue(config.containsKey("tables.table.fields.field.name"));
+    }
+
+    @Test
+    public void testGetKeys()
+    {
+        List<String> keys = new ArrayList<String>();
+        for (Iterator<String> it = config.getKeys(); it.hasNext();)
+        {
+            keys.add(it.next());
+        }
+
+        assertEquals(2, keys.size());
+        assertTrue(keys.contains("tables.table.name"));
+        assertTrue(keys.contains("tables.table.fields.field.name"));
+
+        // test the order of the keys returned
+        config.addProperty("order.key1", "value1");
+        config.addProperty("order.key2", "value2");
+        config.addProperty("order.key3", "value3");
+
+        Iterator<String> it = config.getKeys("order");
+        assertEquals("1st key", "order.key1", it.next());
+        assertEquals("2nd key", "order.key2", it.next());
+        assertEquals("3rd key", "order.key3", it.next());
+    }
+
+    @Test
+    public void testGetKeysString()
+    {
+        // add some more properties to make it more interesting
+        config.addProperty("tables.table(0).fields.field(1).type", "VARCHAR");
+        config.addProperty("tables.table(0)[@type]", "system");
+        config.addProperty("tables.table(0).size", "42");
+        config.addProperty("tables.table(0).fields.field(0).size", "128");
+        config.addProperty("connections.connection.param.url", "url1");
+        config.addProperty("connections.connection.param.user", "me");
+        config.addProperty("connections.connection.param.pwd", "secret");
+        config.addProperty("connections.connection(-1).param.url", "url2");
+        config.addProperty("connections.connection(1).param.user", "guest");
+
+        checkKeys("tables.table(1)", new String[] { "name", "fields.field.name" });
+        checkKeys("tables.table(0)",
+                new String[] { "name", "fields.field.name", "tables.table(0)[@type]", "size", "fields.field.type", "fields.field.size" });
+        checkKeys("connections.connection(0).param",
+                new String[] {"url", "user", "pwd" });
+        checkKeys("connections.connection(1).param",
+                new String[] {"url", "user" });
+    }
+
+    /**
+     * Tests getKeys() with a prefix when the prefix matches exactly a key.
+     */
+    @Test
+    public void testGetKeysWithKeyAsPrefix()
+    {
+        config.addProperty("order.key1", "value1");
+        config.addProperty("order.key2", "value2");
+        Iterator<String> it = config.getKeys("order.key1");
+        assertTrue("no key found", it.hasNext());
+        assertEquals("1st key", "order.key1", it.next());
+        assertFalse("more keys than expected", it.hasNext());
+    }
+
+    /**
+     * Tests getKeys() with a prefix when the prefix matches exactly a key, and
+     * there are multiple keys starting with this prefix.
+     */
+    @Test
+    public void testGetKeysWithKeyAsPrefixMultiple()
+    {
+        config.addProperty("order.key1", "value1");
+        config.addProperty("order.key1.test", "value2");
+        config.addProperty("order.key1.test.complex", "value2");
+        Iterator<String> it = config.getKeys("order.key1");
+        assertEquals("Wrong key 1", "order.key1", it.next());
+        assertEquals("Wrong key 2", "order.key1.test", it.next());
+        assertEquals("Wrong key 3", "order.key1.test.complex", it.next());
+        assertFalse("More keys than expected", it.hasNext());
+    }
+
+    @Test
+    public void testAddProperty()
+    {
+        config.addProperty("tables.table(0).fields.field(-1).name", "phone");
+        Object prop = config.getProperty("tables.table(0).fields.field.name");
+        assertNotNull(prop);
+        assertTrue(prop instanceof Collection);
+        assertEquals(6, ((Collection<?>) prop).size());
+
+        config.addProperty("tables.table(0).fields.field.name", "fax");
+        prop = config.getProperty("tables.table.fields.field(5).name");
+        assertNotNull(prop);
+        assertTrue(prop instanceof List);
+        List<?> list = (List<?>) prop;
+        assertEquals("phone", list.get(0));
+        assertEquals("fax", list.get(1));
+
+        config.addProperty("tables.table(-1).name", "config");
+        prop = config.getProperty("tables.table.name");
+        assertNotNull(prop);
+        assertTrue(prop instanceof Collection);
+        assertEquals(3, ((Collection<?>) prop).size());
+        config.addProperty("tables.table(2).fields.field(0).name", "cid");
+        config.addProperty("tables.table(2).fields.field(-1).name",
+        "confName");
+        prop = config.getProperty("tables.table(2).fields.field.name");
+        assertNotNull(prop);
+        assertTrue(prop instanceof Collection);
+        assertEquals(2, ((Collection<?>) prop).size());
+        assertEquals("confName",
+        config.getProperty("tables.table(2).fields.field(1).name"));
+
+        config.addProperty("connection.user", "scott");
+        config.addProperty("connection.passwd", "tiger");
+        assertEquals("tiger", config.getProperty("connection.passwd"));
+
+        DefaultConfigurationKey key = createConfigurationKey();
+        key.append("tables").append("table").appendIndex(0);
+        key.appendAttribute("tableType");
+        config.addProperty(key.toString(), "system");
+        assertEquals("system", config.getProperty(key.toString()));
+    }
+
+    /**
+     * Creates a {@code DefaultConfigurationKey} object.
+     *
+     * @return the new key object
+     */
+    private static DefaultConfigurationKey createConfigurationKey()
+    {
+        return new DefaultConfigurationKey(new DefaultExpressionEngine());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testAddPropertyInvalidKey()
+    {
+        config.addProperty(".", "InvalidKey");
+    }
+
+    @Test
+    public void testGetMaxIndex()
+    {
+        assertEquals(4, config.getMaxIndex("tables.table(0).fields.field"));
+        assertEquals(4, config.getMaxIndex("tables.table(1).fields.field"));
+        assertEquals(1, config.getMaxIndex("tables.table"));
+        assertEquals(1, config.getMaxIndex("tables.table.name"));
+        assertEquals(0, config.getMaxIndex("tables.table(0).name"));
+        assertEquals(0, config.getMaxIndex("tables.table(1).fields.field(1)"));
+        assertEquals(-1, config.getMaxIndex("tables.table(2).fields"));
+
+        int maxIdx = config.getMaxIndex("tables.table(0).fields.field.name");
+        for(int i = 0; i <= maxIdx; i++)
+        {
+            DefaultConfigurationKey key =
+                    new DefaultConfigurationKey(new DefaultExpressionEngine(),
+                            "tables.table(0).fields");
+            key.append("field").appendIndex(i).append("name");
+            assertNotNull(config.getProperty(key.toString()));
+        }
+    }
+
+    @Test
+    public void testSubset()
+    {
+        // test the subset on the first table
+        Configuration subset = config.subset("tables.table(0)");
+        assertEquals(tables[0], subset.getProperty("name"));
+
+        Object prop = subset.getProperty("fields.field.name");
+        assertNotNull(prop);
+        assertTrue(prop instanceof Collection);
+        assertEquals(5, ((Collection<?>) prop).size());
+
+        for (int i = 0; i < fields[0].length; i++)
+        {
+            DefaultConfigurationKey key = createConfigurationKey();
+            key.append("fields").append("field").appendIndex(i);
+            key.append("name");
+            assertEquals(fields[0][i], subset.getProperty(key.toString()));
+        }
+
+        // test the subset on the second table
+        assertTrue("subset is not empty", config.subset("tables.table(2)").isEmpty());
+
+        // test the subset on the fields
+        subset = config.subset("tables.table.fields.field");
+        prop = subset.getProperty("name");
+        assertTrue("prop is not a collection", prop instanceof Collection);
+        assertEquals(10, ((Collection<?>) prop).size());
+
+        assertEquals(fields[0][0], subset.getProperty("name(0)"));
+
+        // test the subset on the field names
+        subset = config.subset("tables.table.fields.field.name");
+        assertTrue("subset is not empty", subset.isEmpty());
+    }
+
+    /**
+     * Tests the subset() method when the specified node has a value. This value
+     * must be available in the subset, too. Related to CONFIGURATION-295.
+     */
+    @Test
+    public void testSubsetNodeWithValue()
+    {
+        config.setProperty("tables.table(0).fields", "My fields");
+        Configuration subset = config.subset("tables.table(0).fields");
+        assertEquals("Wrong field name", fields[0][0], subset
+                .getString("field(0).name"));
+        assertEquals("Wrong value of root", "My fields", subset.getString(""));
+    }
+
+    /**
+     * Tests the subset() method when the specified key selects multiple keys.
+     * The resulting root node should have a value only if exactly one of the
+     * selected nodes has a value. Related to CONFIGURATION-295.
+     */
+    @Test
+    public void testSubsetMultipleNodesWithValues()
+    {
+        config.setProperty("tables.table(0).fields", "My fields");
+        Configuration subset = config.subset("tables.table.fields");
+        assertEquals("Wrong value of root", "My fields", subset.getString(""));
+        config.setProperty("tables.table(1).fields", "My other fields");
+        subset = config.subset("tables.table.fields");
+        assertNull("Root value is not null though there are multiple values",
+                subset.getString(""));
+    }
+
+    /**
+     * Tests the configurationAt() method to obtain a configuration for a sub
+     * tree.
+     */
+    @Test
+    public void testConfigurationAt()
+    {
+        HierarchicalConfiguration subConfig = config
+                .configurationAt("tables.table(1)");
+        assertEquals("Wrong table name", tables[1], subConfig.getString("name"));
+        List<Object> lstFlds = subConfig.getList("fields.field.name");
+        assertEquals("Wrong number of fields", fields[1].length, lstFlds.size());
+        for (int i = 0; i < fields[1].length; i++)
+        {
+            assertEquals("Wrong field at position " + i, fields[1][i], lstFlds
+                    .get(i));
+        }
+
+        subConfig.setProperty("name", "testTable");
+        assertEquals("Change not visible in parent", "testTable", config
+                .getString("tables.table(1).name"));
+        config.setProperty("tables.table(1).fields.field(2).name", "testField");
+        assertEquals("Change not visible in sub config", "testField", subConfig
+                .getString("fields.field(2).name"));
+    }
+
+    /**
+     * Tests the configurationAt() method when the passed in key does not exist.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testConfigurationAtUnknownSubTree()
+    {
+        config.configurationAt("non.existing.key");
+    }
+
+    /**
+     * Tests the configurationAt() method when the passed in key selects
+     * multiple nodes. This should cause an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testConfigurationAtMultipleNodes()
+    {
+        config.configurationAt("tables.table.name");
+    }
+
+    /**
+     * Tests whether a sub configuration obtained by configurationAt() can be
+     * cleared.
+     */
+    @Test
+    public void testConfigurationAtClear()
+    {
+        config.addProperty("test.sub.test", "fail");
+        assertEquals("Wrong index (1)", 0, config.getMaxIndex("test"));
+        SubnodeConfiguration sub = config.configurationAt("test.sub");
+        assertEquals("Wrong value", "fail", sub.getString("test"));
+        sub.clear();
+        assertNull("Key still found", config.getString("test.sub.key"));
+        sub.setProperty("test", "success");
+        assertEquals("Property not set", "success",
+                config.getString("test.sub.test"));
+        assertEquals("Wrong index (2)", 0, config.getMaxIndex("test"));
+    }
+
+    /**
+     * Tests the configurationsAt() method.
+     */
+    @Test
+    public void testConfigurationsAt()
+    {
+        List<HierarchicalConfiguration> lstFlds = config.configurationsAt("tables.table(1).fields.field");
+        assertEquals("Wrong size of fields", fields[1].length, lstFlds.size());
+        for (int i = 0; i < fields[1].length; i++)
+        {
+            HierarchicalConfiguration sub = lstFlds.get(i);
+            assertEquals("Wrong field at position " + i, fields[1][i], sub
+                    .getString("name"));
+        }
+    }
+
+    /**
+     * Tests the configurationsAt() method when the passed in key does not
+     * select any sub nodes.
+     */
+    @Test
+    public void testConfigurationsAtEmpty()
+    {
+        assertTrue("List is not empty", config.configurationsAt("unknown.key")
+                .isEmpty());
+    }
+
+    @Test
+    public void testClone()
+    {
+        Configuration copy = (Configuration) config.clone();
+        assertTrue(copy instanceof HierarchicalConfiguration);
+        checkContent(copy);
+    }
+
+    /**
+     * Tests whether registered event handlers are handled correctly when a
+     * configuration is cloned. They should not be registered at the clone.
+     */
+    @Test
+    public void testCloneWithEventListeners()
+    {
+        config.addConfigurationListener(new ConfigurationListener()
+        {
+            public void configurationChanged(ConfigurationEvent event)
+            {
+                // just a dummy
+            }
+        });
+        HierarchicalConfiguration copy = (HierarchicalConfiguration) config
+                .clone();
+        assertTrue("Event listener registered at clone", copy
+                .getConfigurationListeners().isEmpty());
+    }
+
+    @Test
+    public void testAddNodes()
+    {
+        Collection<ConfigurationNode> nodes = new ArrayList<ConfigurationNode>();
+        nodes.add(createFieldNode("birthDate"));
+        nodes.add(createFieldNode("lastLogin"));
+        nodes.add(createFieldNode("language"));
+        config.addNodes("tables.table(0).fields", nodes);
+        assertEquals(7, config.getMaxIndex("tables.table(0).fields.field"));
+        assertEquals("birthDate", config.getString("tables.table(0).fields.field(5).name"));
+        assertEquals("lastLogin", config.getString("tables.table(0).fields.field(6).name"));
+        assertEquals("language", config.getString("tables.table(0).fields.field(7).name"));
+    }
+
+    /**
+     * Tests the addNodes() method when the provided key does not exist. In
+     * this case, a new node (or even a complete new branch) will be created.
+     */
+    @Test
+    public void testAddNodesForNonExistingKey()
+    {
+        Collection<ConfigurationNode> nodes = new ArrayList<ConfigurationNode>();
+        nodes.add(createNode("usr", "scott"));
+        Node nd = createNode("pwd", "tiger");
+        nd.setAttribute(true);
+        nodes.add(nd);
+        config.addNodes("database.connection.settings", nodes);
+
+        assertEquals("Usr node not found", "scott", config.getString("database.connection.settings.usr"));
+        assertEquals("Pwd node not found", "tiger", config.getString("database.connection.settings[@pwd]"));
+    }
+
+    /**
+     * Tests the addNodes() method when the new nodes should be added to an
+     * attribute node. This is not allowed.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testAddNodesWithAttributeKey()
+    {
+        Collection<ConfigurationNode> nodes = new ArrayList<ConfigurationNode>();
+        nodes.add(createNode("testNode", "yes"));
+        config.addNodes("database.connection[@settings]", nodes);
+    }
+
+    /**
+     * Tests copying nodes from one configuration to another one.
+     */
+    @Test
+    public void testAddNodesCopy()
+    {
+        HierarchicalConfiguration configDest = new HierarchicalConfiguration();
+        configDest.addProperty("test", "TEST");
+        Collection<ConfigurationNode> nodes = config.getRootNode().getChildren();
+        assertEquals("Wrong number of children", 1, nodes.size());
+        configDest.addNodes("newNodes", nodes);
+        for (int i = 0; i < tables.length; i++)
+        {
+            String keyTab = "newNodes.tables.table(" + i + ").";
+            assertEquals("Table " + i + " not found", tables[i], configDest
+                    .getString(keyTab + "name"));
+            for (int j = 0; j < fields[i].length; j++)
+            {
+                assertEquals("Invalid field " + j + " in table " + i,
+                        fields[i][j], configDest.getString(keyTab
+                                + "fields.field(" + j + ").name"));
+            }
+        }
+    }
+
+    /**
+     * Tests adding an attribute node with the addNodes() method.
+     */
+    @Test
+    public void testAddNodesAttributeNode()
+    {
+        Collection<ConfigurationNode> nodes = new ArrayList<ConfigurationNode>();
+        ConfigurationNode nd = createNode("length", "10");
+        nd.setAttribute(true);
+        nodes.add(nd);
+        config.addNodes("tables.table(0).fields.field(1)", nodes);
+        assertEquals("Attribute was not added", "10", config
+                .getString("tables.table(0).fields.field(1)[@length]"));
+    }
+
+    /**
+     * Tests removing children from a configuration node.
+     */
+    @Test
+    public void testNodeRemove()
+    {
+        HierarchicalConfiguration.Node node = new HierarchicalConfiguration.Node(
+                "parent", "test");
+        assertFalse(node.hasChildren());
+        node.removeChildren(); // should have no effect
+        assertFalse(node.remove("child"));
+
+        node.addChild(createNode("test", "test"));
+        assertTrue(node.hasChildren());
+        assertTrue(node.remove("test"));
+        assertFalse(node.hasChildren());
+
+        for (int i = 0; i < 10; i++)
+        {
+            node.addChild(createNode("child" + i, "test" + i));
+        }
+        assertTrue(node.hasChildren());
+        assertFalse(node.remove("child"));
+        assertTrue(node.remove("child2"));
+        assertTrue(node.getChildren("child2").isEmpty());
+
+        HierarchicalConfiguration.Node child = createNode("child0", "testChild");
+        assertFalse(node.remove(child));
+        node.addChild(child);
+        assertTrue(node.remove(child));
+        assertEquals(1, node.getChildren("child0").size());
+        assertEquals("test0", ((HierarchicalConfiguration.Node) node
+                .getChildren("child0").get(0)).getValue());
+
+        assertTrue(node.remove("child0"));
+        assertFalse(node.remove(child));
+
+        node.removeChildren();
+        assertTrue(node.getChildren().isEmpty());
+        assertFalse(node.remove(child));
+    }
+
+    /**
+     * Tests the visitor mechanism.
+     */
+    @Test
+    public void testNodeVisitor()
+    {
+        CountVisitor v = new CountVisitor();
+        config.getRoot().visit(v, null);
+        assertEquals("Wrong number of visits", 28, v.beforeCount);
+        assertEquals("Different number of before and after visits",
+                v.beforeCount, v.afterCount);
+    }
+
+    /**
+     * Tests the visitor mechanism if a ConfigurationKey is passed in.
+     */
+    @Test
+    public void testNodeVisitorKeys()
+    {
+        CountVisitor v = new CountVisitor();
+        @SuppressWarnings("deprecation")
+        ConfigurationKey configKey = new ConfigurationKey();
+        config.getRoot().visit(v, configKey);
+        for (Iterator<String> it = config.getKeys(); it.hasNext();)
+        {
+            String key = it.next();
+            assertTrue("Key not found in before keys: " + key, v.beforeKeys
+                    .contains(key));
+            assertTrue("Key not found in after keys: " + key, v.afterKeys
+                    .contains(key));
+        }
+    }
+
+    /**
+     * Tests setting a custom expression engine, which uses a slightly different
+     * syntax.
+     */
+    @Test
+    public void testSetExpressionEngine()
+    {
+        config.setExpressionEngine(null);
+        assertNotNull("Expression engine is null", config.getExpressionEngine());
+        assertSame("Default engine is not used", HierarchicalConfiguration
+                .getDefaultExpressionEngine(), config.getExpressionEngine());
+
+        config.setExpressionEngine(createAlternativeExpressionEngine());
+        checkAlternativeSyntax();
+    }
+
+    /**
+     * Tests setting the default expression engine. This should impact all
+     * configuration instances that do not have their own engine.
+     */
+    @Test
+    public void testSetDefaultExpressionEngine()
+    {
+        ExpressionEngine engineOld = HierarchicalConfiguration.getDefaultExpressionEngine();
+        HierarchicalConfiguration
+                .setDefaultExpressionEngine(createAlternativeExpressionEngine());
+        checkAlternativeSyntax();
+        HierarchicalConfiguration.setDefaultExpressionEngine(engineOld);
+    }
+
+    /**
+     * Tests setting the default expression engine to null. This should not be
+     * allowed.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetDefaultExpressionEngineNull()
+    {
+        HierarchicalConfiguration.setDefaultExpressionEngine(null);
+    }
+
+    /**
+     * Tests the copy constructor.
+     */
+    @Test
+    public void testInitCopy()
+    {
+        HierarchicalConfiguration copy = new HierarchicalConfiguration(config);
+        checkContent(copy);
+    }
+
+    /**
+     * Tests whether the nodes of a copied configuration are independent from
+     * the source configuration.
+     */
+    @Test
+    public void testInitCopyUpdate()
+    {
+        HierarchicalConfiguration copy = new HierarchicalConfiguration(config);
+        config.setProperty("tables.table(0).name", "NewTable");
+        checkContent(copy);
+    }
+
+    /**
+     * Tests interpolation facilities.
+     */
+    @Test
+    public void testInterpolation()
+    {
+        config.addProperty("base.dir", "/home/foo");
+        config.addProperty("test.absolute.dir.dir1", "${base.dir}/path1");
+        config.addProperty("test.absolute.dir.dir2", "${base.dir}/path2");
+        config.addProperty("test.absolute.dir.dir3", "${base.dir}/path3");
+
+        Configuration sub = config.subset("test.absolute.dir");
+        for (int i = 1; i < 4; i++)
+        {
+            assertEquals("Wrong interpolation in parent", "/home/foo/path" + i,
+                    config.getString("test.absolute.dir.dir" + i));
+            assertEquals("Wrong interpolation in subnode",
+                    "/home/foo/path" + i, sub.getString("dir" + i));
+        }
+    }
+
+    /**
+     * Basic interpolation tests.
+     */
+    @Test
+    public void testInterpolationBasic()
+    {
+        InterpolationTestHelper.testInterpolation(config);
+    }
+
+    /**
+     * Tests multiple levels of interpolation.
+     */
+    @Test
+    public void testInterpolationMultipleLevels()
+    {
+        InterpolationTestHelper.testMultipleInterpolation(config);
+    }
+
+    /**
+     * Tests an invalid interpolation that causes an endless loop.
+     */
+    @Test
+    public void testInterpolationLoop()
+    {
+        InterpolationTestHelper.testInterpolationLoop(config);
+    }
+
+    /**
+     * Tests interpolation with a subset.
+     */
+    @Test
+    public void testInterpolationSubset()
+    {
+        InterpolationTestHelper.testInterpolationSubset(config);
+    }
+
+    /**
+     * Tests whether interpolation with a subset configuration works over
+     * multiple layers.
+     */
+    @Test
+    public void testInterpolationSubsetMultipleLayers()
+    {
+        config.clear();
+        config.addProperty("var", "value");
+        config.addProperty("prop2.prop[@attr]", "${var}");
+        Configuration sub1 = config.subset("prop2");
+        Configuration sub2 = sub1.subset("prop");
+        assertEquals("Wrong value", "value", sub2.getString("[@attr]"));
+    }
+
+    /**
+     * Tests interpolation of a variable, which cannot be resolved.
+     */
+    @Test
+    public void testInterpolationUnknownProperty()
+    {
+        InterpolationTestHelper.testInterpolationUnknownProperty(config);
+    }
+
+    /**
+     * Tests interpolation with system properties.
+     */
+    @Test
+    public void testInterpolationSysProperties()
+    {
+        InterpolationTestHelper.testInterpolationSystemProperties(config);
+    }
+
+    /**
+     * Tests interpolation with constant values.
+     */
+    @Test
+    public void testInterpolationConstants()
+    {
+        InterpolationTestHelper.testInterpolationConstants(config);
+    }
+
+    /**
+     * Tests escaping variables.
+     */
+    @Test
+    public void testInterpolationEscaped()
+    {
+        InterpolationTestHelper.testInterpolationEscaped(config);
+    }
+
+    /**
+     * Tests manipulating the interpolator.
+     */
+    @Test
+    public void testInterpolator()
+    {
+        InterpolationTestHelper.testGetInterpolator(config);
+    }
+
+    /**
+     * Tests obtaining a configuration with all variables substituted.
+     */
+    @Test
+    public void testInterpolatedConfiguration()
+    {
+        HierarchicalConfiguration c = (HierarchicalConfiguration) InterpolationTestHelper
+                .testInterpolatedConfiguration(config);
+
+        // tests whether the hierarchical structure has been maintained
+        config = c;
+        testGetProperty();
+    }
+
+    /**
+     * Tests the copy constructor when a null reference is passed.
+     */
+    @Test
+    public void testInitCopyNull()
+    {
+        HierarchicalConfiguration copy = new HierarchicalConfiguration(null);
+        assertTrue("Configuration not empty", copy.isEmpty());
+    }
+
+    /**
+     * Tests the parents of nodes when setRootNode() is involved. This is
+     * related to CONFIGURATION-334.
+     */
+    @Test
+    public void testNodeParentsAfterSetRootNode()
+    {
+        DefaultConfigurationNode root = new DefaultConfigurationNode();
+        DefaultConfigurationNode child1 = new DefaultConfigurationNode(
+                "child1", "test1");
+        root.addChild(child1);
+        config.setRootNode(root);
+        config.addProperty("child2", "test2");
+        List<ConfigurationNode> nodes = config.getExpressionEngine().query(config.getRootNode(),
+                "child2");
+        assertEquals("Wrong number of result nodes", 1, nodes.size());
+        ConfigurationNode child2 = nodes.get(0);
+        assertEquals("Different parent nodes", child1.getParentNode(), child2
+                .getParentNode());
+    }
+
+    /**
+     * Tests calling getRoot() after a root node was set using setRootNode() and
+     * further child nodes have been added. The newly add child nodes should be
+     * present in the root node returned.
+     */
+    @Test
+    public void testGetRootAfterSetRootNode()
+    {
+        DefaultConfigurationNode root = new DefaultConfigurationNode();
+        DefaultConfigurationNode child1 = new DefaultConfigurationNode(
+                "child1", "test1");
+        root.addChild(child1);
+        config.setRootNode(root);
+        config.addProperty("child2", "test2");
+        ConfigurationNode oldRoot = config.getRoot();
+        assertEquals("Wrong number of children", 2, oldRoot.getChildrenCount());
+    }
+
+    /**
+     * Tests whether keys that contains brackets can be used.
+     */
+    @Test
+    public void testGetPropertyKeyWithBrackets()
+    {
+        final String key = "test.directory.platform(x86)";
+        config.addProperty(key, "C:\\Temp");
+        assertEquals("Wrong property value", "C:\\Temp", config.getString(key));
+    }
+
+    /**
+     * Helper method for testing the getKeys(String) method.
+     *
+     * @param prefix the key to pass into getKeys()
+     * @param expected the expected result
+     */
+    private void checkKeys(String prefix, String[] expected)
+    {
+        Set<String> values = new HashSet<String>();
+        for(int i = 0; i < expected.length; i++)
+        {
+            values.add((expected[i].startsWith(prefix)) ? expected[i] :  prefix + "." + expected[i]);
+        }
+
+        Iterator<String> itKeys = config.getKeys(prefix);
+        while(itKeys.hasNext())
+        {
+            String key = itKeys.next();
+            if(!values.contains(key))
+            {
+                fail("Found unexpected key: " + key);
+            }
+            else
+            {
+                values.remove(key);
+            }
+        }
+
+        assertTrue("Remaining keys " + values, values.isEmpty());
+    }
+
+    /**
+     * Helper method for checking keys using an alternative syntax.
+     */
+    private void checkAlternativeSyntax()
+    {
+        assertNull(config.getProperty("tables/table/resultset"));
+        assertNull(config.getProperty("tables/table/fields/field"));
+
+        Object prop = config.getProperty("tables/table[0]/fields/field/name");
+        assertNotNull(prop);
+        assertTrue(prop instanceof Collection);
+        assertEquals(5, ((Collection<?>) prop).size());
+
+        prop = config.getProperty("tables/table/fields/field/name");
+        assertNotNull(prop);
+        assertTrue(prop instanceof Collection);
+        assertEquals(10, ((Collection<?>) prop).size());
+
+        prop = config.getProperty("tables/table/fields/field[3]/name");
+        assertNotNull(prop);
+        assertTrue(prop instanceof Collection);
+        assertEquals(2, ((Collection<?>) prop).size());
+
+        prop = config.getProperty("tables/table[1]/fields/field[2]/name");
+        assertNotNull(prop);
+        assertEquals("creationDate", prop.toString());
+
+        Set<String> keys = new HashSet<String>();
+        CollectionUtils.addAll(keys, config.getKeys());
+        assertEquals("Wrong number of defined keys", 2, keys.size());
+        assertTrue("Key not found", keys.contains("tables/table/name"));
+        assertTrue("Key not found", keys
+                .contains("tables/table/fields/field/name"));
+    }
+
+    /**
+     * Checks the content of the passed in configuration object. Used by some
+     * tests that copy a configuration.
+     *
+     * @param c the configuration to check
+     */
+    private void checkContent(Configuration c)
+    {
+        for (int i = 0; i < tables.length; i++)
+        {
+            assertEquals(tables[i], c.getString("tables.table(" + i + ").name"));
+            for (int j = 0; j < fields[i].length; j++)
+            {
+                assertEquals(fields[i][j], c.getString("tables.table(" + i
+                        + ").fields.field(" + j + ").name"));
+            }
+        }
+    }
+
+    private ExpressionEngine createAlternativeExpressionEngine()
+    {
+        DefaultExpressionEngine engine = new DefaultExpressionEngine();
+        engine.setPropertyDelimiter("/");
+        engine.setIndexStart("[");
+        engine.setIndexEnd("]");
+        return engine;
+    }
+
+    /**
+     * Helper method for creating a field node with its children.
+     *
+     * @param name the name of the field
+     * @return the field node
+     */
+    private static HierarchicalConfiguration.Node createFieldNode(String name)
+    {
+        HierarchicalConfiguration.Node fld = createNode("field", null);
+        fld.addChild(createNode("name", name));
+        return fld;
+    }
+
+    /**
+     * Helper method for creating a configuration node.
+     * @param name the node's name
+     * @param value the node's value
+     * @return the new node
+     */
+    private static HierarchicalConfiguration.Node createNode(String name, Object value)
+    {
+        HierarchicalConfiguration.Node node = new HierarchicalConfiguration.Node(name);
+        node.setValue(value);
+        return node;
+    }
+
+    /**
+     * A test visitor implementation for checking whether all visitor methods
+     * are correctly called.
+     */
+    @SuppressWarnings("deprecation")
+    static class CountVisitor extends HierarchicalConfiguration.NodeVisitor
+    {
+        /** The number of invocations of visitBeforeChildren(). */
+        int beforeCount;
+
+        /** The number of invocations of visitAfterChildren(). */
+        int afterCount;
+
+        /** A set with the keys passed to visitBeforeChildren(). */
+        final Set<String> beforeKeys = new HashSet<String>();
+
+        /** A set with the keys passed to visitAfterChildren(). */
+        final Set<String> afterKeys = new HashSet<String>();
+
+        @Override
+        public void visitAfterChildren(Node node, ConfigurationKey key)
+        {
+            super.visitAfterChildren(node, key);
+            afterCount++;
+            if (key != null)
+            {
+                afterKeys.add(key.toString());
+            }
+        }
+
+        @Override
+        public void visitBeforeChildren(Node node, ConfigurationKey key)
+        {
+            super.visitBeforeChildren(node, key);
+            beforeCount++;
+            if (key != null)
+            {
+                beforeKeys.add(key.toString());
+            }
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestHierarchicalConfigurationXMLReader.java b/src/test/java/org/apache/commons/configuration/TestHierarchicalConfigurationXMLReader.java
new file mode 100644
index 0000000..52a1639
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestHierarchicalConfigurationXMLReader.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.sax.SAXSource;
+
+import org.apache.commons.jxpath.JXPathContext;
+import org.junit.Before;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
+
+/**
+ * Test class for HierarchicalConfigurationXMLReader.
+ *
+ * @version $Id: TestHierarchicalConfigurationXMLReader.java 1224644 2011-12-25 20:34:22Z oheger $
+ */
+public class TestHierarchicalConfigurationXMLReader
+{
+    private static final String TEST_FILE = ConfigurationAssert.getTestFile(
+            "testHierarchicalXMLConfiguration.xml").getAbsolutePath();
+
+    private HierarchicalConfigurationXMLReader parser;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        XMLConfiguration config = new XMLConfiguration();
+        config.setFileName(TEST_FILE);
+        config.load();
+        parser = new HierarchicalConfigurationXMLReader(config);
+    }
+
+    @Test
+    public void testParse() throws Exception
+    {
+        SAXSource source = new SAXSource(parser, new InputSource());
+        DOMResult result = new DOMResult();
+        Transformer trans = TransformerFactory.newInstance().newTransformer();
+        trans.transform(source, result);
+        Node root = ((Document) result.getNode()).getDocumentElement();
+        JXPathContext ctx = JXPathContext.newContext(root);
+
+        assertEquals("Wrong name of root element", "database", root.getNodeName());
+        assertEquals("Wrong number of children of root", 1, ctx.selectNodes(
+                "/*").size());
+        assertEquals("Wrong number of tables", 2, ctx.selectNodes(
+                "/tables/table").size());
+        assertEquals("Wrong name of first table", "users", ctx
+                .getValue("/tables/table[1]/name"));
+        assertEquals("Wrong number of fields in first table", 5, ctx
+                .selectNodes("/tables/table[1]/fields/field").size());
+        assertEquals("Wrong attribute value", "system", ctx
+                .getValue("/tables/table[1]/@tableType"));
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestHierarchicalINIConfiguration.java b/src/test/java/org/apache/commons/configuration/TestHierarchicalINIConfiguration.java
new file mode 100644
index 0000000..d232d85
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestHierarchicalINIConfiguration.java
@@ -0,0 +1,971 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.After;
+import org.junit.Test;
+
+/**
+ * Test class for HierarchicalINIConfiguration.
+ *
+ * @author <a
+ *         href="http://commons.apache.org/configuration/team-list.html">Commons
+ *         Configuration team</a>
+ * @version $Id: TestHierarchicalINIConfiguration.java 1234362 2012-01-21 16:59:48Z oheger $
+ */
+public class TestHierarchicalINIConfiguration
+{
+    private static String LINE_SEPARATOR = System.getProperty("line.separator");
+
+    /** Constant for the content of an ini file. */
+    private static final String INI_DATA = "[section1]" + LINE_SEPARATOR
+            + "var1 = foo" + LINE_SEPARATOR + "var2 = 451" + LINE_SEPARATOR
+            + LINE_SEPARATOR + "[section2]" + LINE_SEPARATOR + "var1 = 123.45"
+            + LINE_SEPARATOR + "var2 = bar" + LINE_SEPARATOR + LINE_SEPARATOR
+            + "[section3]" + LINE_SEPARATOR + "var1 = true" + LINE_SEPARATOR
+            + "interpolated = ${section3.var1}" + LINE_SEPARATOR
+            + "multi = foo" + LINE_SEPARATOR + "multi = bar" + LINE_SEPARATOR
+            + LINE_SEPARATOR;
+
+    private static final String INI_DATA2 = "[section4]" + LINE_SEPARATOR
+            + "var1 = \"quoted value\"" + LINE_SEPARATOR
+            + "var2 = \"quoted value\\nwith \\\"quotes\\\"\"" + LINE_SEPARATOR
+            + "var3 = 123 ; comment" + LINE_SEPARATOR
+            + "var4 = \"1;2;3\" ; comment" + LINE_SEPARATOR
+            + "var5 = '\\'quoted\\' \"value\"' ; comment" + LINE_SEPARATOR
+            + "var6 = \"\"" + LINE_SEPARATOR;
+
+    private static final String INI_DATA3 = "[section5]" + LINE_SEPARATOR
+            + "multiLine = one \\" + LINE_SEPARATOR
+            + "    two      \\" + LINE_SEPARATOR
+            + " three" + LINE_SEPARATOR
+            + "singleLine = C:\\Temp\\" + LINE_SEPARATOR
+            + "multiQuoted = one \\" + LINE_SEPARATOR
+            + "\"  two  \" \\" + LINE_SEPARATOR
+            + "  three" + LINE_SEPARATOR
+            + "multiComment = one \\ ; a comment" + LINE_SEPARATOR
+            + "two" + LINE_SEPARATOR
+            + "multiQuotedComment = \" one \" \\ ; comment" + LINE_SEPARATOR
+            + "two" + LINE_SEPARATOR
+            + "noFirstLine = \\" + LINE_SEPARATOR
+            + "  line 2" + LINE_SEPARATOR
+            + "continueNoLine = one \\" + LINE_SEPARATOR;
+
+    private static final String INI_DATA_SEPARATORS = "[section]"
+            + LINE_SEPARATOR + "var1 = value1" + LINE_SEPARATOR
+            + "var2 : value2" + LINE_SEPARATOR
+            + "var3=value3" + LINE_SEPARATOR
+            + "var4:value4" + LINE_SEPARATOR
+            + "var5 : value=5" + LINE_SEPARATOR
+            + "var:6=value" + LINE_SEPARATOR
+            + "var:7=\"value7\"" + LINE_SEPARATOR
+            + "var:8 =  \"value8\"" + LINE_SEPARATOR;
+
+    /** An ini file that contains only a property in the global section. */
+    private static final String INI_DATA_GLOBAL_ONLY = "globalVar = testGlobal"
+            + LINE_SEPARATOR + LINE_SEPARATOR;
+
+    /** An ini file with a global section. */
+    private static final String INI_DATA_GLOBAL = INI_DATA_GLOBAL_ONLY
+            + INI_DATA;
+
+    /** A test ini file. */
+    private static final File TEST_FILE = new File("target/test.ini");
+
+    @After
+    public void tearDown() throws Exception
+    {
+        if (TEST_FILE.exists())
+        {
+            assertTrue("Cannot remove test file: " + TEST_FILE, TEST_FILE
+                    .delete());
+        }
+    }
+
+    /**
+     * Creates a HierarchicalINIConfiguration object that is initialized from
+     * the given data.
+     *
+     * @param data the data of the configuration (an ini file as string)
+     * @return the initialized configuration
+     * @throws ConfigurationException if an error occurs
+     */
+    private static HierarchicalINIConfiguration setUpConfig(String data)
+            throws ConfigurationException
+    {
+        HierarchicalINIConfiguration instance = new HierarchicalINIConfiguration();
+        load(instance, data);
+        return instance;
+    }
+
+    /**
+     * Loads the specified content into the given configuration instance.
+     *
+     * @param instance the configuration
+     * @param data the data to be loaded
+     * @throws ConfigurationException if an error occurs
+     */
+    private static void load(HierarchicalINIConfiguration instance, String data)
+            throws ConfigurationException
+    {
+        StringReader reader = new StringReader(data);
+        instance.load(reader);
+        reader.close();
+    }
+
+    /**
+     * Writes a test ini file.
+     *
+     * @param content the content of the file
+     * @throws IOException if an error occurs
+     */
+    private static void writeTestFile(String content) throws IOException
+    {
+        PrintWriter out = new PrintWriter(new FileWriter(TEST_FILE));
+        try
+        {
+            out.println(content);
+        }
+        finally
+        {
+            out.close();
+        }
+    }
+
+    /**
+     * Test of save method, of class {@link HierarchicalINIConfiguration}.
+     */
+    @Test
+    public void testSave() throws Exception
+    {
+        Writer writer = new StringWriter();
+        HierarchicalINIConfiguration instance = new HierarchicalINIConfiguration();
+        instance.addProperty("section1.var1", "foo");
+        instance.addProperty("section1.var2", "451");
+        instance.addProperty("section2.var1", "123.45");
+        instance.addProperty("section2.var2", "bar");
+        instance.addProperty("section3.var1", "true");
+        instance.addProperty("section3.interpolated", "${section3.var1}");
+        instance.addProperty("section3.multi", "foo");
+        instance.addProperty("section3.multi", "bar");
+        instance.save(writer);
+
+        assertEquals("Wrong content of ini file", INI_DATA, writer.toString());
+    }
+
+    /**
+     * Helper method for testing a save operation. This method constructs a
+     * configuration from the specified content string. Then it saves this
+     * configuration and checks whether the result matches the original content.
+     *
+     * @param content the content of the configuration
+     * @throws ConfigurationException if an error occurs
+     */
+    private void checkSave(String content) throws ConfigurationException
+    {
+        HierarchicalINIConfiguration config = setUpConfig(content);
+        StringWriter writer = new StringWriter();
+        config.save(writer);
+        assertEquals("Wrong content of ini file", content, writer.toString());
+    }
+
+    /**
+     * Tests saving a configuration that contains a global section.
+     */
+    @Test
+    public void testSaveWithGlobalSection() throws ConfigurationException
+    {
+        checkSave(INI_DATA_GLOBAL);
+    }
+
+    /**
+     * Tests whether a configuration that contains only a global section can be
+     * saved correctly.
+     */
+    @Test
+    public void testSaveWithOnlyGlobalSection() throws ConfigurationException
+    {
+        checkSave(INI_DATA_GLOBAL_ONLY);
+    }
+
+    /**
+     * Test of load method, of class {@link HierarchicalINIConfiguration}.
+     */
+    @Test
+    public void testLoad() throws Exception
+    {
+        checkLoad(INI_DATA);
+    }
+
+    /**
+     * Tests the load() method when the alternative value separator is used (a
+     * ':' for '=').
+     */
+    @Test
+    public void testLoadAlternativeSeparator() throws Exception
+    {
+        checkLoad(INI_DATA.replace('=', ':'));
+    }
+
+    /**
+     * Tests loading a configuration from a File.
+     */
+    @Test
+    public void testLoadFile() throws ConfigurationException, IOException
+    {
+        writeTestFile(INI_DATA);
+        HierarchicalINIConfiguration config = new HierarchicalINIConfiguration(
+                TEST_FILE);
+        checkContent(config);
+    }
+
+    /**
+     * Tests loading a configuration from a file name.
+     */
+    @Test
+    public void testLoadFileName() throws ConfigurationException, IOException
+    {
+        writeTestFile(INI_DATA);
+        HierarchicalINIConfiguration config = new HierarchicalINIConfiguration(
+                TEST_FILE.getAbsolutePath());
+        checkContent(config);
+    }
+
+    /**
+     * Tests loading a configuration from a URL.
+     */
+    @Test
+    public void testLoadURL() throws ConfigurationException, IOException
+    {
+        writeTestFile(INI_DATA);
+        HierarchicalINIConfiguration config = new HierarchicalINIConfiguration(
+                TEST_FILE.toURI().toURL());
+        checkContent(config);
+    }
+
+    /**
+     * Tests the values of some properties to ensure that the configuration was
+     * correctly loaded.
+     *
+     * @param instance the configuration to check
+     */
+    private void checkContent(HierarchicalINIConfiguration instance)
+    {
+        assertTrue(instance.getString("section1.var1").equals("foo"));
+        assertTrue(instance.getInt("section1.var2") == 451);
+        assertTrue(instance.getDouble("section2.var1") == 123.45);
+        assertTrue(instance.getString("section2.var2").equals("bar"));
+        assertTrue(instance.getBoolean("section3.var1"));
+        assertTrue(instance.getSections().size() == 3);
+    }
+
+    /**
+     * Helper method for testing the load operation. Loads the specified content
+     * into a configuration and then checks some properties.
+     *
+     * @param data the data to load
+     */
+    private void checkLoad(String data) throws ConfigurationException
+    {
+        HierarchicalINIConfiguration instance = setUpConfig(data);
+        checkContent(instance);
+    }
+
+    /**
+     * Test of isCommentLine method, of class
+     * {@link HierarchicalINIConfiguration}.
+     */
+    @Test
+    public void testIsCommentLine()
+    {
+        HierarchicalINIConfiguration instance = new HierarchicalINIConfiguration();
+        assertTrue(instance.isCommentLine("#comment1"));
+        assertTrue(instance.isCommentLine(";comment1"));
+        assertFalse(instance.isCommentLine("nocomment=true"));
+        assertFalse(instance.isCommentLine(null));
+    }
+
+    /**
+     * Test of isSectionLine method, of class
+     * {@link HierarchicalINIConfiguration}.
+     */
+    @Test
+    public void testIsSectionLine()
+    {
+        HierarchicalINIConfiguration instance = new HierarchicalINIConfiguration();
+        assertTrue(instance.isSectionLine("[section]"));
+        assertFalse(instance.isSectionLine("nosection=true"));
+        assertFalse(instance.isSectionLine(null));
+    }
+
+    /**
+     * Test of getSections method, of class {@link HierarchicalINIConfiguration}
+     * .
+     */
+    @Test
+    public void testGetSections()
+    {
+        HierarchicalINIConfiguration instance = new HierarchicalINIConfiguration();
+        instance.addProperty("test1.foo", "bar");
+        instance.addProperty("test2.foo", "abc");
+        Set<String> expResult = new HashSet<String>();
+        expResult.add("test1");
+        expResult.add("test2");
+        Set<String> result = instance.getSections();
+        assertEquals(expResult, result);
+    }
+
+    @Test
+    public void testQuotedValue() throws Exception
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA2);
+        assertEquals("value", "quoted value", config.getString("section4.var1"));
+    }
+
+    @Test
+    public void testQuotedValueWithQuotes() throws Exception
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA2);
+        assertEquals("value", "quoted value\\nwith \"quotes\"", config
+                .getString("section4.var2"));
+    }
+
+    @Test
+    public void testValueWithComment() throws Exception
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA2);
+        assertEquals("value", "123", config.getString("section4.var3"));
+    }
+
+    @Test
+    public void testQuotedValueWithComment() throws Exception
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA2);
+        assertEquals("value", "1;2;3", config.getString("section4.var4"));
+    }
+
+    @Test
+    public void testQuotedValueWithSingleQuotes() throws Exception
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA2);
+        assertEquals("value", "'quoted' \"value\"", config
+                .getString("section4.var5"));
+    }
+
+    @Test
+    public void testWriteValueWithCommentChar() throws Exception
+    {
+        HierarchicalINIConfiguration config = new HierarchicalINIConfiguration();
+        config.setProperty("section.key1", "1;2;3");
+
+        StringWriter writer = new StringWriter();
+        config.save(writer);
+
+        HierarchicalINIConfiguration config2 = new HierarchicalINIConfiguration();
+        config2.load(new StringReader(writer.toString()));
+
+        assertEquals("value", "1;2;3", config2.getString("section.key1"));
+    }
+
+    /**
+     * Tests whether whitespace is left unchanged for quoted values.
+     */
+    @Test
+    public void testQuotedValueWithWhitespace() throws Exception
+    {
+        final String content = "CmdPrompt = \" [test at cmd ~]$ \"";
+        HierarchicalINIConfiguration config = setUpConfig(content);
+        assertEquals("Wrong propert value", " [test at cmd ~]$ ", config
+                .getString("CmdPrompt"));
+    }
+
+    /**
+     * Tests a quoted value with space and a comment.
+     */
+    @Test
+    public void testQuotedValueWithWhitespaceAndComment() throws Exception
+    {
+        final String content = "CmdPrompt = \" [test at cmd ~]$ \" ; a comment";
+        HierarchicalINIConfiguration config = setUpConfig(content);
+        assertEquals("Wrong propert value", " [test at cmd ~]$ ", config
+                .getString("CmdPrompt"));
+    }
+
+    /**
+     * Tests an empty quoted value.
+     */
+    @Test
+    public void testQuotedValueEmpty() throws ConfigurationException
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA2);
+        assertEquals("Wrong value for empty property", "", config
+                .getString("section4.var6"));
+    }
+
+    /**
+     * Tests a property that has no value.
+     */
+    @Test
+    public void testGetPropertyNoValue() throws ConfigurationException
+    {
+        final String data = INI_DATA2 + LINE_SEPARATOR + "noValue ="
+                + LINE_SEPARATOR;
+        HierarchicalINIConfiguration config = setUpConfig(data);
+        assertEquals("Wrong value of key", "", config
+                .getString("section4.noValue"));
+    }
+
+    /**
+     * Tests a property that has no key.
+     */
+    @Test
+    public void testGetPropertyNoKey() throws ConfigurationException
+    {
+        final String data = INI_DATA2 + LINE_SEPARATOR + "= noKey"
+                + LINE_SEPARATOR;
+        HierarchicalINIConfiguration config = setUpConfig(data);
+        assertEquals("Cannot find property with no key", "noKey", config
+                .getString("section4. "));
+    }
+
+    /**
+     * Tests reading a property from the global section.
+     */
+    @Test
+    public void testGlobalProperty() throws ConfigurationException
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA_GLOBAL);
+        assertEquals("Wrong value of global property", "testGlobal", config
+                .getString("globalVar"));
+    }
+
+    /**
+     * Tests whether the specified configuration contains exactly the expected
+     * sections.
+     *
+     * @param config the configuration to check
+     * @param expected an array with the expected sections
+     */
+    private void checkSectionNames(HierarchicalINIConfiguration config,
+            String[] expected)
+    {
+        Set<String> sectionNames = config.getSections();
+        Iterator<String> it = sectionNames.iterator();
+        for (int idx = 0; idx < expected.length; idx++)
+        {
+            assertEquals("Wrong section at " + idx, expected[idx], it.next());
+        }
+        assertFalse("Too many sections", it.hasNext());
+    }
+
+    /**
+     * Tests the names of the sections returned by the configuration.
+     *
+     * @param data the data of the ini configuration
+     * @param expected the expected section names
+     * @return the configuration instance
+     */
+    private HierarchicalINIConfiguration checkSectionNames(String data,
+            String[] expected) throws ConfigurationException
+    {
+        HierarchicalINIConfiguration config = setUpConfig(data);
+        checkSectionNames(config, expected);
+        return config;
+    }
+
+    /**
+     * Tests querying the sections if a global section if available.
+     */
+    @Test
+    public void testGetSectionsWithGlobal() throws ConfigurationException
+    {
+        checkSectionNames(INI_DATA_GLOBAL, new String[] {
+                null, "section1", "section2", "section3"
+        });
+    }
+
+    /**
+     * Tests querying the sections if there is no global section.
+     */
+    @Test
+    public void testGetSectionsNoGlobal() throws ConfigurationException
+    {
+        checkSectionNames(INI_DATA, new String[] {
+                "section1", "section2", "section3"
+        });
+    }
+
+    /**
+     * Tests whether the sections of a configuration can be queried that
+     * contains only a global section.
+     */
+    @Test
+    public void testGetSectionsGlobalOnly() throws ConfigurationException
+    {
+        checkSectionNames(INI_DATA_GLOBAL_ONLY, new String[] {
+            null
+        });
+    }
+
+    /**
+     * Tests whether variables containing a dot are not misinterpreted as
+     * sections. This test is related to CONFIGURATION-327.
+     */
+    @Test
+    public void testGetSectionsDottedVar() throws ConfigurationException
+    {
+        final String data = "dotted.var = 1" + LINE_SEPARATOR + INI_DATA_GLOBAL;
+        HierarchicalINIConfiguration config = checkSectionNames(data,
+                new String[] {
+                        null, "section1", "section2", "section3"
+                });
+        assertEquals("Wrong value of dotted variable", 1, config
+                .getInt("dotted..var"));
+    }
+
+    /**
+     * Tests whether a section added later is also found by getSections().
+     */
+    @Test
+    public void testGetSectionsAdded() throws ConfigurationException
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA2);
+        config.addProperty("section5.test", Boolean.TRUE);
+        checkSectionNames(config, new String[] {
+                "section4", "section5"
+        });
+    }
+
+    /**
+     * Tests querying the properties of an existing section.
+     */
+    @Test
+    public void testGetSectionExisting() throws ConfigurationException
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA);
+        SubnodeConfiguration section = config.getSection("section1");
+        assertEquals("Wrong value of var1", "foo", section.getString("var1"));
+        assertEquals("Wrong value of var2", "451", section.getString("var2"));
+    }
+
+    /**
+     * Tests querying the properties of a section that was merged from two
+     * sections with the same name.
+     */
+    @Test
+    public void testGetSectionMerged() throws ConfigurationException
+    {
+        final String data = INI_DATA + "[section1]" + LINE_SEPARATOR
+                + "var3 = merged" + LINE_SEPARATOR;
+        HierarchicalINIConfiguration config = setUpConfig(data);
+        SubnodeConfiguration section = config.getSection("section1");
+        assertEquals("Wrong value of var1", "foo", section.getString("var1"));
+        assertEquals("Wrong value of var2", "451", section.getString("var2"));
+        assertEquals("Wrong value of var3", "merged", section.getString("var3"));
+    }
+
+    /**
+     * Tests querying the content of the global section.
+     */
+    @Test
+    public void testGetSectionGlobal() throws ConfigurationException
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA_GLOBAL);
+        SubnodeConfiguration section = config.getSection(null);
+        assertEquals("Wrong value of global variable", "testGlobal", section
+                .getString("globalVar"));
+    }
+
+    /**
+     * Tests concurrent access to the global section.
+     */
+    @Test
+    public void testGetSectionGloabalMultiThreaded()
+            throws ConfigurationException, InterruptedException
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA_GLOBAL);
+        final int threadCount = 10;
+        GlobalSectionTestThread[] threads = new GlobalSectionTestThread[threadCount];
+        for (int i = 0; i < threadCount; i++)
+        {
+            threads[i] = new GlobalSectionTestThread(config);
+            threads[i].start();
+        }
+        for (int i = 0; i < threadCount; i++)
+        {
+            threads[i].join();
+            assertFalse("Exception occurred", threads[i].error);
+        }
+    }
+
+    /**
+     * Tests querying the content of the global section if there is none.
+     */
+    @Test
+    public void testGetSectionGlobalNonExisting() throws ConfigurationException
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA);
+        SubnodeConfiguration section = config.getSection(null);
+        assertTrue("Sub config not empty", section.isEmpty());
+    }
+
+    /**
+     * Tests querying a non existing section.
+     */
+    @Test
+    public void testGetSectionNonExisting() throws ConfigurationException
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA);
+        SubnodeConfiguration section = config
+                .getSection("Non existing section");
+        assertTrue("Sub config not empty", section.isEmpty());
+    }
+
+    /**
+     * Tests a property whose value spans multiple lines.
+     */
+    @Test
+    public void testLineContinuation() throws ConfigurationException
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA3);
+        assertEquals("Wrong value", "one" + LINE_SEPARATOR + "two"
+                + LINE_SEPARATOR + "three", config
+                .getString("section5.multiLine"));
+    }
+
+    /**
+     * Tests a property value that ends on a backslash, which is no line
+     * continuation character.
+     */
+    @Test
+    public void testLineContinuationNone() throws ConfigurationException
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA3);
+        assertEquals("Wrong value", "C:\\Temp\\", config
+                .getString("section5.singleLine"));
+    }
+
+    /**
+     * Tests a property whose value spans multiple lines when quoting is
+     * involved. In this case whitespace must not be trimmed.
+     */
+    @Test
+    public void testLineContinuationQuoted() throws ConfigurationException
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA3);
+        assertEquals("Wrong value", "one" + LINE_SEPARATOR + "  two  "
+                + LINE_SEPARATOR + "three", config
+                .getString("section5.multiQuoted"));
+    }
+
+    /**
+     * Tests a property whose value spans multiple lines with a comment.
+     */
+    @Test
+    public void testLineContinuationComment() throws ConfigurationException
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA3);
+        assertEquals("Wrong value", "one" + LINE_SEPARATOR + "two", config
+                .getString("section5.multiComment"));
+    }
+
+    /**
+     * Tests a property with a quoted value spanning multiple lines and a
+     * comment.
+     */
+    @Test
+    public void testLineContinuationQuotedComment()
+            throws ConfigurationException
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA3);
+        assertEquals("Wrong value", " one " + LINE_SEPARATOR + "two", config
+                .getString("section5.multiQuotedComment"));
+    }
+
+    /**
+     * Tests a multi-line property value with an empty line.
+     */
+    @Test
+    public void testLineContinuationEmptyLine() throws ConfigurationException
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA3);
+        assertEquals("Wrong value", LINE_SEPARATOR + "line 2", config
+                .getString("section5.noFirstLine"));
+    }
+
+    /**
+     * Tests a line continuation at the end of the file.
+     */
+    @Test
+    public void testLineContinuationAtEnd() throws ConfigurationException
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA3);
+        assertEquals("Wrong value", "one" + LINE_SEPARATOR, config
+                .getString("section5.continueNoLine"));
+    }
+
+    /**
+     * Tests whether a configuration can be saved that contains section keys
+     * with delimiter characters. This test is related to CONFIGURATION-409.
+     */
+    @Test
+    public void testSaveKeysWithDelimiters() throws ConfigurationException
+    {
+        HierarchicalINIConfiguration conf = new HierarchicalINIConfiguration();
+        final String section = "Section..with..dots";
+        conf.addProperty(section + ".test1", "test1");
+        conf.addProperty(section + ".test2", "test2");
+        conf.save(TEST_FILE);
+        conf = new HierarchicalINIConfiguration();
+        conf.load(TEST_FILE);
+        assertEquals("Wrong value (1)", "test1", conf.getString(section + ".test1"));
+        assertEquals("Wrong value (2)", "test2", conf.getString(section + ".test2"));
+    }
+
+    /**
+     * Tests whether a value which contains a semicolon can be loaded
+     * successfully. This test is related to CONFIGURATION-434.
+     */
+    @Test
+    public void testValueWithSemicolon() throws ConfigurationException
+    {
+        final String path =
+                "C:\\Program Files\\jar\\manage.jar;"
+                        + "C:\\Program Files\\jar\\guiLauncher.jar";
+        final String content =
+                "[Environment]" + LINE_SEPARATOR + "Application Type=any"
+                        + LINE_SEPARATOR + "Class Path=" + path + "  ;comment"
+                        + LINE_SEPARATOR + "Path=" + path
+                        + "\t; another comment";
+        HierarchicalINIConfiguration config = setUpConfig(content);
+        assertEquals("Wrong class path", path,
+                config.getString("Environment.Class Path"));
+        assertEquals("Wrong path", path, config.getString("Environment.Path"));
+    }
+
+    /**
+     * Tests whether the different separators with or without whitespace are
+     * recognized.
+     */
+    @Test
+    public void testSeparators() throws ConfigurationException
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA_SEPARATORS);
+        for (int i = 1; i <= 4; i++)
+        {
+            assertEquals("Wrong value", "value" + i,
+                    config.getString("section.var" + i));
+        }
+    }
+
+    /**
+     * Tests property definitions containing multiple separators.
+     */
+    @Test
+    public void testMultipleSeparators() throws ConfigurationException
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA_SEPARATORS);
+        assertEquals("Wrong value for var5", "value=5",
+                config.getString("section.var5"));
+        assertEquals("Wrong value for var6", "6=value",
+                config.getString("section.var"));
+    }
+
+    /**
+     * Tests property definitions containing multiple separators that are
+     * quoted.
+     */
+    @Test
+    public void testMultipleSeparatorsQuoted() throws ConfigurationException
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA_SEPARATORS);
+        assertEquals("Wrong value for var7", "value7",
+                config.getString("section.var:7"));
+        assertEquals("Wrong value for var8", "value8",
+                config.getString("section.var:8"));
+    }
+
+    /**
+     * Tests whether a section that has been cleared can be manipulated and
+     * saved later.
+     */
+    @Test
+    public void testSaveClearedSection() throws ConfigurationException
+    {
+        final String data = "[section]\ntest = failed\n";
+        HierarchicalINIConfiguration config = setUpConfig(data);
+        SubnodeConfiguration sub = config.getSection("section");
+        assertFalse("No content", sub.isEmpty());
+        sub.clear();
+        sub.setProperty("test", "success");
+        StringWriter writer = new StringWriter();
+        config.save(writer);
+        HierarchicalConfiguration config2 = setUpConfig(writer.toString());
+        assertEquals("Wrong value", "success",
+                config2.getString("section.test"));
+    }
+
+    /**
+     * Tests whether a duplicate session is merged.
+     */
+    @Test
+    public void testMergeDuplicateSection() throws ConfigurationException
+    {
+        final String data =
+                "[section]\nvar1 = sec1\n\n" + "[section]\nvar2 = sec2\n";
+        HierarchicalINIConfiguration config = setUpConfig(data);
+        assertEquals("Wrong value 1", "sec1", config.getString("section.var1"));
+        assertEquals("Wrong value 2", "sec2", config.getString("section.var2"));
+        SubnodeConfiguration sub = config.getSection("section");
+        assertEquals("Wrong sub value 1", "sec1", sub.getString("var1"));
+        assertEquals("Wrong sub value 2", "sec2", sub.getString("var2"));
+        StringWriter writer = new StringWriter();
+        config.save(writer);
+        String content = writer.toString();
+        int pos = content.indexOf("[section]");
+        assertTrue("Section not found: " + content, pos >= 0);
+        assertTrue("Section found multiple times: " + content,
+                content.indexOf("[section]", pos + 1) < 0);
+    }
+
+    /**
+     * Tests whether a section that was created by getSection() can be
+     * manipulated.
+     */
+    @Test
+    public void testGetSectionNonExistingManipulate()
+            throws ConfigurationException
+    {
+        HierarchicalINIConfiguration config = setUpConfig(INI_DATA);
+        SubnodeConfiguration section = config.getSection("newSection");
+        section.addProperty("test", "success");
+        assertEquals("Main config not updated", "success",
+                config.getString("newSection.test"));
+        StringWriter writer = new StringWriter();
+        config.save(writer);
+        HierarchicalINIConfiguration config2 = setUpConfig(writer.toString());
+        section = config2.getSection("newSection");
+        assertEquals("Wrong value", "success", section.getString("test"));
+    }
+
+    /**
+     * Tests whether getSection() can deal with duplicate sections.
+     */
+    @Test
+    public void testGetSectionDuplicate()
+    {
+        HierarchicalINIConfiguration config =
+                new HierarchicalINIConfiguration();
+        config.addProperty("section.var1", "value1");
+        config.addProperty("section(-1).var2", "value2");
+        SubnodeConfiguration section = config.getSection("section");
+        Iterator<String> keys = section.getKeys();
+        assertEquals("Wrong key", "var1", keys.next());
+        assertFalse("Too many keys", keys.hasNext());
+    }
+
+    /**
+     * Tests whether the list delimiter character is recognized.
+     */
+    @Test
+    public void testValueWithDelimiters() throws ConfigurationException
+    {
+        HierarchicalINIConfiguration config =
+                setUpConfig("[test]" + LINE_SEPARATOR + "list=1,2,3"
+                        + LINE_SEPARATOR);
+        List<Object> list = config.getList("test.list");
+        assertEquals("Wrong number of elements", 3, list.size());
+        assertEquals("Wrong element at 1", "1", list.get(0));
+        assertEquals("Wrong element at 2", "2", list.get(1));
+        assertEquals("Wrong element at 3", "3", list.get(2));
+    }
+
+    /**
+     * Tests whether parsing of lists can be disabled.
+     */
+    @Test
+    public void testListParsingDisabled() throws ConfigurationException
+    {
+        HierarchicalINIConfiguration config =
+                new HierarchicalINIConfiguration();
+        config.setDelimiterParsingDisabled(true);
+        load(config, "[test]" + LINE_SEPARATOR + "nolist=1,2,3");
+        assertEquals("Wrong value", "1,2,3", config.getString("test.nolist"));
+    }
+
+    /**
+     * A thread class for testing concurrent access to the global section.
+     */
+    private static class GlobalSectionTestThread extends Thread
+    {
+        /** The configuration. */
+        private final HierarchicalINIConfiguration config;
+
+        /** A flag whether an error was found. */
+        volatile boolean error;
+
+        /**
+         * Creates a new instance of <code>GlobalSectionTestThread</code> and
+         * initializes it.
+         *
+         * @param conf the configuration object
+         */
+        public GlobalSectionTestThread(HierarchicalINIConfiguration conf)
+        {
+            config = conf;
+        }
+
+        /**
+         * Accesses the global section in a loop. If there is no correct
+         * synchronization, this can cause an exception.
+         */
+        @Override
+        public void run()
+        {
+            final int loopCount = 250;
+
+            for (int i = 0; i < loopCount && !error; i++)
+            {
+                try
+                {
+                    config.getSection(null);
+                }
+                catch (IllegalStateException istex)
+                {
+                    error = true;
+                }
+            }
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestHierarchicalXMLConfiguration.java b/src/test/java/org/apache/commons/configuration/TestHierarchicalXMLConfiguration.java
new file mode 100644
index 0000000..c7a49ca
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestHierarchicalXMLConfiguration.java
@@ -0,0 +1,296 @@
+package org.apache.commons.configuration;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Test class for XMLConfiguration. In addition to TestXMLConfiguration this
+ * class especially tests the hierarchical nature of this class and structured
+ * data access.
+ *
+ * @author Emmanuel Bourg
+ * @author Mark Woodman
+ * @version $Id: TestHierarchicalXMLConfiguration.java 1224764 2011-12-26 17:14:49Z oheger $
+ */
+public class TestHierarchicalXMLConfiguration
+{
+    /** Test resources directory. */
+    private static final String TEST_DIR = "conf";
+
+    /** Test file #1 **/
+    private static final String TEST_FILENAME = "testHierarchicalXMLConfiguration.xml";
+
+    /** Test file #2 **/
+    private static final String TEST_FILENAME2 = "testHierarchicalXMLConfiguration2.xml";
+
+    /** Test file path #1 **/
+    private static final String TEST_FILE = ConfigurationAssert.getTestFile(TEST_FILENAME).getAbsolutePath();
+
+    /** Test file path #2 **/
+    private static final String TEST_FILE2 = ConfigurationAssert.getTestFile(TEST_FILENAME2).getAbsolutePath();
+
+    /** Test file path #3.*/
+    private static final String TEST_FILE3 = ConfigurationAssert.getTestFile("test.xml").getAbsolutePath();
+
+    /** File name for saving.*/
+    private static final String TEST_SAVENAME = "testhierarchicalsave.xml";
+
+    /** Helper object for creating temporary files. */
+    @Rule
+    public TemporaryFolder folder = new TemporaryFolder();
+
+    /** Instance config used for tests. */
+    private XMLConfiguration config;
+
+    /** Fixture setup. */
+    @Before
+    public void setUp() throws Exception
+    {
+        config = new XMLConfiguration();
+    }
+
+    private void configTest(XMLConfiguration config)
+    {
+        assertEquals(1, config.getMaxIndex("tables.table"));
+        assertEquals("system", config.getProperty("tables.table(0)[@tableType]"));
+        assertEquals("application", config.getProperty("tables.table(1)[@tableType]"));
+
+        assertEquals("users", config.getProperty("tables.table(0).name"));
+        assertEquals("documents", config.getProperty("tables.table(1).name"));
+
+        Object prop = config.getProperty("tables.table.fields.field.name");
+        assertTrue(prop instanceof Collection);
+        assertEquals(10, ((Collection<?>) prop).size());
+
+        prop = config.getProperty("tables.table(0).fields.field.type");
+        assertTrue(prop instanceof Collection);
+        assertEquals(5, ((Collection<?>) prop).size());
+
+        prop = config.getProperty("tables.table(1).fields.field.type");
+        assertTrue(prop instanceof Collection);
+        assertEquals(5, ((Collection<?>) prop).size());
+    }
+
+    @Test
+    public void testGetProperty() throws Exception
+    {
+        config.setFileName(TEST_FILE);
+        config.load();
+
+        configTest(config);
+    }
+
+    @Test
+    public void testLoadURL() throws Exception
+    {
+        config.load(new File(TEST_FILE).getAbsoluteFile().toURI().toURL());
+        configTest(config);
+    }
+
+    @Test
+    public void testLoadBasePath1() throws Exception
+    {
+        config.setBasePath(TEST_DIR);
+        config.setFileName(TEST_FILENAME);
+        config.load();
+        configTest(config);
+    }
+
+    @Test
+    public void testLoadBasePath2() throws Exception
+    {
+        config.setBasePath(new File(TEST_FILE).getAbsoluteFile().toURI().toURL().toString());
+        config.setFileName(TEST_FILENAME);
+        config.load();
+        configTest(config);
+    }
+
+    /**
+     * Ensure various node types are correctly processed in config.
+     * @throws Exception
+     */
+    @Test
+    public void testXmlNodeTypes() throws Exception
+    {
+        // Number of keys expected from test configuration file
+        final int KEY_COUNT = 5;
+
+        // Load the configuration file
+        config.load(new File(TEST_FILE2).getAbsoluteFile().toURI().toURL());
+
+        // Validate comment in element ignored
+        assertEquals("Comment in element must not change element value.", "Case1Text", config
+                .getString("case1"));
+
+        // Validate sibling comment ignored
+        assertEquals("Comment as sibling must not change element value.", "Case2Text", config
+                .getString("case2.child"));
+
+        // Validate comment ignored, CDATA processed
+        assertEquals("Comment and use of CDATA must not change element value.", "Case3Text", config
+                .getString("case3"));
+
+        // Validate comment and processing instruction ignored
+        assertEquals("Comment and use of PI must not change element value.", "Case4Text", config
+                .getString("case4"));
+
+        // Validate comment ignored in parent attribute
+        assertEquals("Comment must not change attribute node value.", "Case5Text", config
+                .getString("case5[@attr]"));
+
+        // Validate non-text nodes haven't snuck in as keys
+        Iterator<String> iter = config.getKeys();
+        int count = 0;
+        while (iter.hasNext())
+        {
+            iter.next();
+            count++;
+        }
+        assertEquals("Config must contain only " + KEY_COUNT + " keys.", KEY_COUNT, count);
+    }
+
+    @Test
+    public void testSave() throws Exception
+    {
+        config.setFileName(TEST_FILE3);
+        config.load();
+        File saveFile = folder.newFile(TEST_SAVENAME);
+        config.save(saveFile);
+
+        config = new XMLConfiguration();
+        config.load(saveFile.toURI().toURL());
+        assertEquals("value", config.getProperty("element"));
+        assertEquals("I'm complex!", config.getProperty("element2.subelement.subsubelement"));
+        assertEquals(8, config.getInt("test.short"));
+        assertEquals("one", config.getString("list(0).item(0)[@name]"));
+        assertEquals("two", config.getString("list(0).item(1)"));
+        assertEquals("six", config.getString("list(1).sublist.item(1)"));
+    }
+
+    /**
+     * Tests to save a newly created configuration.
+     */
+    @Test
+    public void testSaveNew() throws Exception
+    {
+        config.addProperty("connection.url", "jdbc://mydb:1234");
+        config.addProperty("connection.user", "scott");
+        config.addProperty("connection.passwd", "tiger");
+        config.addProperty("connection[@type]", "system");
+        config.addProperty("tables.table.name", "tests");
+        config.addProperty("tables.table(0).fields.field.name", "test_id");
+        config.addProperty("tables.table(0).fields.field(-1).name", "test_name");
+        config.addProperty("tables.table(-1).name", "results");
+        config.addProperty("tables.table(1).fields.field.name", "res_id");
+        config.addProperty("tables.table(1).fields.field(0).type", "int");
+        config.addProperty("tables.table(1).fields.field(-1).name", "value");
+        config.addProperty("tables.table(1).fields.field(1).type", "string");
+        config.addProperty("tables.table(1).fields.field(1)[@null]", "true");
+
+        File saveFile = folder.newFile(TEST_SAVENAME);
+        config.setFile(saveFile);
+        config.setRootElementName("myconfig");
+        config.save();
+
+        config = new XMLConfiguration();
+        config.load(saveFile);
+        assertEquals(1, config.getMaxIndex("tables.table.name"));
+        assertEquals("tests", config.getString("tables.table(0).name"));
+        assertEquals("test_name", config.getString("tables.table(0).fields.field(1).name"));
+        assertEquals("int", config.getString("tables.table(1).fields.field(0).type"));
+        assertTrue(config.getBoolean("tables.table(1).fields.field(1)[@null]"));
+        assertEquals("tiger", config.getString("connection.passwd"));
+        assertEquals("system", config.getProperty("connection[@type]"));
+        assertEquals("myconfig", config.getRootElementName());
+    }
+
+    /**
+     * Tests to save a modified configuration.
+     */
+    @Test
+    public void testSaveModified() throws Exception
+    {
+        config.setFile(new File(TEST_FILE3));
+        config.load();
+
+        assertTrue(config.getString("mean").startsWith("This is\n A long story..."));
+        assertTrue(config.getString("mean").indexOf("And even longer") > 0);
+        config.clearProperty("test.entity[@name]");
+        config.setProperty("element", "new value");
+        config.setProperty("test(0)", "A <new> value");
+        config.addProperty("test(1).int", new Integer(9));
+        config.addProperty("list(1).sublist.item", "seven");
+        config.setProperty("clear", "yes");
+        config.setProperty("mean", "now it's simple");
+        config.addProperty("[@topattr]", "available");
+        config.addProperty("[@topattr]", "successfull");
+
+        File saveFile = folder.newFile(TEST_SAVENAME);
+        config.save(saveFile);
+        config = new XMLConfiguration();
+        config.load(saveFile.getAbsolutePath());
+        assertFalse(config.containsKey("test.entity[@name]"));
+        assertEquals("1<2", config.getProperty("test.entity"));
+        assertEquals("new value", config.getString("element"));
+        assertEquals("A <new> value", config.getProperty("test(0)"));
+        assertEquals((short) 8, config.getShort("test(1).short"));
+        assertEquals(9, config.getInt("test(1).int"));
+        assertEquals("six", config.getProperty("list(1).sublist.item(1)"));
+        assertEquals("seven", config.getProperty("list(1).sublist.item(2)"));
+        assertEquals("yes", config.getProperty("clear"));
+        assertEquals("now it's simple", config.getString("mean"));
+        assertEquals("available", config.getString("[@topattr](0)"));
+        assertEquals("successfull", config.getString("[@topattr](1)"));
+    }
+
+    /**
+     * Tests manipulation of the root element's name.
+     */
+    @Test
+    public void testRootElement() throws Exception
+    {
+        assertEquals("configuration", config.getRootElementName());
+        config.setRootElementName("newRootName");
+        assertEquals("newRootName", config.getRootElementName());
+    }
+
+    /**
+     * Tests that it is not allowed to change the root element name when the
+     * configuration was loaded from a file.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testSetRootElementNameWhenLoadedFromFile() throws Exception
+    {
+        config.setFile(new File(TEST_FILE3));
+        config.load();
+        assertEquals("testconfig", config.getRootElementName());
+        config.setRootElementName("anotherRootElement");
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestINIConfiguration.java b/src/test/java/org/apache/commons/configuration/TestINIConfiguration.java
new file mode 100644
index 0000000..cf71ea5
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestINIConfiguration.java
@@ -0,0 +1,256 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.Test;
+
+/**
+ * Test class for INIConfiguration.
+ *
+ * @author Trevor Miller
+ * @version $Id: TestINIConfiguration.java 1224770 2011-12-26 17:18:36Z oheger $
+ */
+ at SuppressWarnings("deprecation")
+public class TestINIConfiguration
+{
+    private static String LINE_SEPARATOR = System.getProperty("line.separator");
+
+    /** Constant for the content of an ini file. */
+    private static final String INI_DATA =
+            "[section1]" + LINE_SEPARATOR
+            + "var1 = foo" + LINE_SEPARATOR
+            + "var2 = 451" + LINE_SEPARATOR
+            + LINE_SEPARATOR
+            + "[section2]" + LINE_SEPARATOR
+            + "var1 = 123.45" + LINE_SEPARATOR
+            + "var2 = bar" + LINE_SEPARATOR
+            + LINE_SEPARATOR
+            + "[section3]" + LINE_SEPARATOR
+            + "var1 = true" + LINE_SEPARATOR
+            + "interpolated = ${section3.var1}" + LINE_SEPARATOR
+            + "multi = foo" + LINE_SEPARATOR
+            + "multi = bar" + LINE_SEPARATOR
+            + LINE_SEPARATOR;
+
+    private static final String INI_DATA2 =
+            "[section4]" + LINE_SEPARATOR
+            + "var1 = \"quoted value\"" + LINE_SEPARATOR
+            + "var2 = \"quoted value\\nwith \\\"quotes\\\"\"" + LINE_SEPARATOR
+            + "var3 = 123 ; comment" + LINE_SEPARATOR
+            + "var4 = \"1;2;3\" ; comment" + LINE_SEPARATOR
+            + "var5 = '\\'quoted\\' \"value\"' ; comment";
+
+    /**
+     * Test of save method, of class {@link INIConfiguration}.
+     */
+    @Test
+    public void testSave() throws Exception
+    {
+        Writer writer = new StringWriter();
+        INIConfiguration instance = new INIConfiguration();
+        instance.addProperty("section1.var1", "foo");
+        instance.addProperty("section1.var2", "451");
+        instance.addProperty("section2.var1", "123.45");
+        instance.addProperty("section2.var2", "bar");
+        instance.addProperty("section3.var1", "true");
+        instance.addProperty("section3.interpolated", "${section3.var1}");
+        instance.addProperty("section3.multi", "foo");
+        instance.addProperty("section3.multi", "bar");
+        instance.save(writer);
+
+        assertEquals("Wrong content of ini file", INI_DATA, writer.toString());
+    }
+
+    /**
+     * Test of load method, of class {@link INIConfiguration}.
+     */
+    @Test
+    public void testLoad() throws Exception
+    {
+        checkLoad(INI_DATA);
+    }
+
+    /**
+     * Tests the load() method when the alternative value separator is used (a
+     * ':' for '=').
+     */
+    @Test
+    public void testLoadAlternativeSeparator() throws Exception
+    {
+        checkLoad(INI_DATA.replace('=', ':'));
+    }
+
+    /**
+     * Helper method for testing the load operation. Loads the specified content
+     * into a configuration and then checks some properties.
+     *
+     * @param data the data to load
+     */
+    private void checkLoad(String data) throws ConfigurationException, IOException
+    {
+        Reader reader = new StringReader(data);
+        INIConfiguration instance = new INIConfiguration();
+        instance.load(reader);
+        reader.close();
+        assertTrue(instance.getString("section1.var1").equals("foo"));
+        assertTrue(instance.getInt("section1.var2") == 451);
+        assertTrue(instance.getDouble("section2.var1") == 123.45);
+        assertTrue(instance.getString("section2.var2").equals("bar"));
+        assertTrue(instance.getBoolean("section3.var1"));
+        assertTrue(instance.getSections().size() == 3);
+    }
+
+    /**
+     * Test of isCommentLine method, of class {@link INIConfiguration}.
+     */
+    @Test
+    public void testIsCommentLine()
+    {
+        INIConfiguration instance = new INIConfiguration();
+        assertTrue(instance.isCommentLine("#comment1"));
+        assertTrue(instance.isCommentLine(";comment1"));
+        assertFalse(instance.isCommentLine("nocomment=true"));
+        assertFalse(instance.isCommentLine(null));
+    }
+
+    /**
+     * Test of isSectionLine method, of class {@link INIConfiguration}.
+     */
+    @Test
+    public void testIsSectionLine()
+    {
+        INIConfiguration instance = new INIConfiguration();
+        assertTrue(instance.isSectionLine("[section]"));
+        assertFalse(instance.isSectionLine("nosection=true"));
+        assertFalse(instance.isSectionLine(null));
+    }
+
+    /**
+     * Test of getSections method, of class {@link INIConfiguration}.
+     */
+    @Test
+    public void testGetSections()
+    {
+        INIConfiguration instance = new INIConfiguration();
+        instance.addProperty("test1.foo", "bar");
+        instance.addProperty("test2.foo", "abc");
+        Set<String> expResult = new HashSet<String>();
+        expResult.add("test1");
+        expResult.add("test2");
+        Set<String> result = instance.getSections();
+        assertEquals(expResult, result);
+    }
+
+    @Test
+    public void testQuotedValue() throws Exception
+    {
+        INIConfiguration config = new INIConfiguration();
+        config.load(new StringReader(INI_DATA2));
+
+        assertEquals("value", "quoted value", config.getString("section4.var1"));
+    }
+
+    @Test
+    public void testQuotedValueWithQuotes() throws Exception
+    {
+        INIConfiguration config = new INIConfiguration();
+        config.load(new StringReader(INI_DATA2));
+
+        assertEquals("value", "quoted value\\nwith \"quotes\"", config.getString("section4.var2"));
+    }
+
+    @Test
+    public void testValueWithComment() throws Exception
+    {
+        INIConfiguration config = new INIConfiguration();
+        config.load(new StringReader(INI_DATA2));
+
+        assertEquals("value", "123", config.getString("section4.var3"));
+    }
+
+    @Test
+    public void testQuotedValueWithComment() throws Exception
+    {
+        INIConfiguration config = new INIConfiguration();
+        config.load(new StringReader(INI_DATA2));
+
+        assertEquals("value", "1;2;3", config.getString("section4.var4"));
+    }
+
+    @Test
+    public void testQuotedValueWithSingleQuotes() throws Exception
+    {
+        INIConfiguration config = new INIConfiguration();
+        config.load(new StringReader(INI_DATA2));
+
+        assertEquals("value", "'quoted' \"value\"", config.getString("section4.var5"));
+    }
+
+    @Test
+    public void testWriteValueWithCommentChar() throws Exception
+    {
+        INIConfiguration config = new INIConfiguration();
+        config.setProperty("section.key1", "1;2;3");
+
+        StringWriter writer = new StringWriter();
+        config.save(writer);
+
+        INIConfiguration config2 = new INIConfiguration();
+        config2.load(new StringReader(writer.toString()));
+
+        assertEquals("value", "1;2;3", config2.getString("section.key1"));
+    }
+
+    /**
+     * Tests whether whitespace is left unchanged for quoted values.
+     */
+    @Test
+    public void testQuotedValueWithWhitespace() throws Exception
+    {
+        final String content = "CmdPrompt = \" [test at cmd ~]$ \"";
+        INIConfiguration config = new INIConfiguration();
+        config.load(new StringReader(content));
+        assertEquals("Wrong propert value", " [test at cmd ~]$ ", config
+                .getString("CmdPrompt"));
+    }
+
+    /**
+     * Tests a quoted value with space and a comment.
+     */
+    @Test
+    public void testQuotedValueWithWhitespaceAndComment() throws Exception
+    {
+        final String content = "CmdPrompt = \" [test at cmd ~]$ \" ; a comment";
+        INIConfiguration config = new INIConfiguration();
+        config.load(new StringReader(content));
+        assertEquals("Wrong propert value", " [test at cmd ~]$ ", config
+                .getString("CmdPrompt"));
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestJNDIConfiguration.java b/src/test/java/org/apache/commons/configuration/TestJNDIConfiguration.java
new file mode 100644
index 0000000..3e28845
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestJNDIConfiguration.java
@@ -0,0 +1,375 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Hashtable;
+import java.util.Properties;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test to see if the JNDIConfiguration works properly.
+ *
+ * @version $Id: TestJNDIConfiguration.java 1301996 2012-03-17 20:30:39Z sebb $
+ */
+public class TestJNDIConfiguration {
+
+    public static final String CONTEXT_FACTORY = MockInitialContextFactory.class.getName();
+
+    private PotentialErrorJNDIConfiguration conf;
+    private NonStringTestHolder nonStringTestHolder;
+
+    /** A test error listener for counting internal errors.*/
+    private ConfigurationErrorListenerImpl listener;
+
+    @Before
+    public void setUp() throws Exception {
+
+        System.setProperty("java.naming.factory.initial", CONTEXT_FACTORY);
+
+        Properties props = new Properties();
+        props.put("java.naming.factory.initial", CONTEXT_FACTORY);
+        Context ctx = new InitialContext(props);
+        conf = new PotentialErrorJNDIConfiguration(ctx);
+
+        nonStringTestHolder = new NonStringTestHolder();
+        nonStringTestHolder.setConfiguration(conf);
+
+        listener = new ConfigurationErrorListenerImpl();
+        conf.addErrorListener(listener);
+    }
+
+    /**
+     * Clears the test environment. If an error listener is defined, checks
+     * whether no error event was received.
+     */
+    @After
+    public void tearDown() throws Exception
+    {
+        if (listener != null)
+        {
+            listener.verify();
+        }
+    }
+
+    @Test
+    public void testBoolean() throws Exception {
+        nonStringTestHolder.testBoolean();
+    }
+
+    @Test
+    public void testBooleanDefaultValue() throws Exception {
+        nonStringTestHolder.testBooleanDefaultValue();
+    }
+
+    @Test
+    public void testByte() throws Exception {
+        nonStringTestHolder.testByte();
+    }
+
+    @Test
+    public void testDouble() throws Exception {
+        nonStringTestHolder.testDouble();
+    }
+
+    @Test
+    public void testDoubleDefaultValue() throws Exception {
+        nonStringTestHolder.testDoubleDefaultValue();
+    }
+
+    @Test
+    public void testFloat() throws Exception {
+        nonStringTestHolder.testFloat();
+    }
+
+    @Test
+    public void testFloatDefaultValue() throws Exception {
+        nonStringTestHolder.testFloatDefaultValue();
+    }
+
+    @Test
+    public void testInteger() throws Exception {
+        nonStringTestHolder.testInteger();
+    }
+
+    @Test
+    public void testIntegerDefaultValue() throws Exception {
+        nonStringTestHolder.testIntegerDefaultValue();
+    }
+
+    @Test
+    public void testLong() throws Exception {
+        nonStringTestHolder.testLong();
+    }
+
+    @Test
+    public void testLongDefaultValue() throws Exception {
+        nonStringTestHolder.testLongDefaultValue();
+    }
+
+    @Test
+    public void testShort() throws Exception {
+        nonStringTestHolder.testShort();
+    }
+
+    @Test
+    public void testShortDefaultValue() throws Exception {
+        nonStringTestHolder.testShortDefaultValue();
+    }
+
+    @Test
+    public void testListMissing() throws Exception {
+        nonStringTestHolder.testListMissing();
+    }
+
+    @Test
+    public void testSubset() throws Exception {
+        nonStringTestHolder.testSubset();
+    }
+
+    @Test
+    public void testProperties() throws Exception {
+        Object o = conf.getProperty("test.boolean");
+        assertNotNull(o);
+        assertEquals("true", o.toString());
+    }
+
+    @Test
+    public void testContainsKey()
+    {
+        String key = "test.boolean";
+        assertTrue("'" + key + "' not found", conf.containsKey(key));
+
+        conf.clearProperty(key);
+        assertFalse("'" + key + "' still found", conf.containsKey(key));
+    }
+
+    @Test
+    public void testChangePrefix()
+    {
+        assertEquals("'test.boolean' property", "true", conf.getString("test.boolean"));
+        assertEquals("'boolean' property", null, conf.getString("boolean"));
+
+        // change the prefix
+        conf.setPrefix("test");
+        assertEquals("'test.boolean' property", null, conf.getString("test.boolean"));
+        assertEquals("'boolean' property", "true", conf.getString("boolean"));
+    }
+
+    @Test
+    public void testResetRemovedProperties() throws Exception
+    {
+        assertEquals("'test.boolean' property", "true", conf.getString("test.boolean"));
+
+        // remove the property
+        conf.clearProperty("test.boolean");
+        assertEquals("'test.boolean' property", null, conf.getString("test.boolean"));
+
+        // change the context
+        conf.setContext(new InitialContext());
+
+        // get the property
+        assertEquals("'test.boolean' property", "true", conf.getString("test.boolean"));
+    }
+
+    @Test
+    public void testConstructor() throws Exception
+    {
+        // test the constructor accepting a context
+        JNDIConfiguration c = new JNDIConfiguration(new InitialContext());
+
+        assertEquals("'test.boolean' property", "true", c.getString("test.boolean"));
+
+        // test the constructor accepting a context and a prefix
+        c = new JNDIConfiguration(new InitialContext(), "test");
+
+        assertEquals("'boolean' property", "true", c.getString("boolean"));
+    }
+
+    /**
+     * Configures the test config to throw an exception.
+     */
+    private PotentialErrorJNDIConfiguration setUpErrorConfig()
+    {
+        conf.installException();
+        conf.removeErrorListener(conf.getErrorListeners().iterator().next());
+        return conf;
+    }
+
+    /**
+     * Tests whether the expected error events have been received.
+     *
+     * @param type the expected event type
+     * @param propName the name of the property
+     * @param propValue the property value
+     */
+    private void checkErrorListener(int type, String propName, Object propValue)
+    {
+        listener.verify(type, propName, propValue);
+        assertTrue("Wrong exception class",
+                listener.getLastEvent().getCause() instanceof NamingException);
+        listener = null;
+    }
+
+    /**
+     * Tests whether a JNDI configuration registers an error log listener.
+     */
+    @Test
+    public void testLogListener() throws NamingException
+    {
+        JNDIConfiguration c = new JNDIConfiguration();
+        assertEquals("No error log listener registered", 1, c
+                .getErrorListeners().size());
+    }
+
+    /**
+     * Tests handling of errors in getKeys().
+     */
+    @Test
+    public void testGetKeysError()
+    {
+        assertFalse("Iteration not empty", setUpErrorConfig().getKeys()
+                .hasNext());
+        checkErrorListener(AbstractConfiguration.EVENT_READ_PROPERTY, null,
+                null);
+    }
+
+    /**
+     * Tests handling of errors in isEmpty().
+     */
+    @Test
+    public void testIsEmptyError() throws Exception
+    {
+        assertTrue("Error config not empty", setUpErrorConfig().isEmpty());
+        checkErrorListener(AbstractConfiguration.EVENT_READ_PROPERTY, null,
+                null);
+    }
+
+    /**
+     * Tests handling of errors in the containsKey() method.
+     */
+    @Test
+    public void testContainsKeyError()
+    {
+        assertFalse("Key contained after error", setUpErrorConfig()
+                .containsKey("key"));
+        checkErrorListener(AbstractConfiguration.EVENT_READ_PROPERTY, "key",
+                null);
+    }
+
+    /**
+     * Tests handling of errors in getProperty().
+     */
+    @Test
+    public void testGetPropertyError()
+    {
+        assertNull("Wrong property value after error", setUpErrorConfig()
+                .getProperty("key"));
+        checkErrorListener(AbstractConfiguration.EVENT_READ_PROPERTY, "key",
+                null);
+    }
+
+    /**
+     * Tests the getKeys() method when there are cycles in the tree.
+     */
+    @Test
+    public void testGetKeysWithCycles() throws NamingException
+    {
+        Hashtable<Object, Object> env = new Hashtable<Object, Object>();
+        env.put(MockInitialContextFactory.PROP_CYCLES, Boolean.TRUE);
+        InitialContext initCtx = new InitialContext(env);
+        JNDIConfiguration c = new JNDIConfiguration(initCtx);
+        c.getKeys("cycle");
+    }
+
+    /**
+     * Tests getKeys() if no data is found. This should not cause a problem and
+     * not notify the error listeners.
+     */
+    @Test
+    public void testGetKeysNoData()
+    {
+        conf.installException(new NameNotFoundException("Test exception"));
+        assertFalse("Got keys", conf.getKeys().hasNext());
+        listener.verify();
+    }
+
+    /**
+     * A special JNDI configuration implementation that can be configured to
+     * throw an exception when accessing the base context. Used for testing the
+     * exception handling.
+     */
+    public static class PotentialErrorJNDIConfiguration extends
+            JNDIConfiguration
+    {
+        /** An exception to be thrown by getBaseContext(). */
+        private NamingException exception;
+
+        public PotentialErrorJNDIConfiguration(Context ctx)
+                throws NamingException
+        {
+            super(ctx);
+        }
+
+        /**
+         * Prepares this object to throw an exception when the JNDI context is
+         * queried.
+         *
+         * @param nex the exception to be thrown
+         */
+        public void installException(NamingException nex)
+        {
+            exception = nex;
+        }
+
+        /**
+         * Prepares this object to throw a standard exception when the JNDI
+         * context is queried.
+         */
+        public void installException()
+        {
+            installException(new NamingException("Simulated JNDI exception!"));
+        }
+
+        /**
+         * Returns the JNDI context. Optionally throws an exception.
+         */
+        @Override
+        public Context getBaseContext() throws NamingException
+        {
+            if (exception != null)
+            {
+                throw exception;
+            }
+            return super.getBaseContext();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/commons/configuration/TestJNDIEnvironmentValues.java b/src/test/java/org/apache/commons/configuration/TestJNDIEnvironmentValues.java
new file mode 100644
index 0000000..56d7339
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestJNDIEnvironmentValues.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestJNDIEnvironmentValues
+{
+    private JNDIConfiguration conf = null;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        System.setProperty("java.naming.factory.initial", TestJNDIConfiguration.CONTEXT_FACTORY);
+
+        conf = new JNDIConfiguration();
+        conf.setThrowExceptionOnMissing(true);
+    }
+
+    @Test
+    public void testThrowExceptionOnMissing()
+    {
+        assertTrue("Throw Exception Property is not set!", conf.isThrowExceptionOnMissing());
+    }
+
+    @Test
+    public void testSimpleGet() throws Exception
+    {
+        String s = conf.getString("test.key");
+        assertEquals("jndivalue", s);
+    }
+
+    @Test
+    public void testMoreGets() throws Exception
+    {
+        String s = conf.getString("test.key");
+        assertEquals("jndivalue", s);
+        assertEquals("jndivalue2", conf.getString("test.key2"));
+        assertEquals(1, conf.getShort("test.short"));
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void testGetMissingKey() throws Exception
+    {
+        conf.getString("test.imaginarykey");
+    }
+
+    @Test
+    public void testGetMissingKeyWithDefault() throws Exception
+    {
+        String result = conf.getString("test.imaginarykey", "bob");
+        assertEquals("bob", result);
+    }
+
+    @Test
+    public void testContainsKey() throws Exception
+    {
+        assertTrue(conf.containsKey("test.key"));
+        assertTrue(!conf.containsKey("test.imaginarykey"));
+    }
+
+    @Test
+    public void testClearProperty()
+    {
+        assertNotNull("null short for the 'test.short' key", conf.getShort("test.short", null));
+        conf.clearProperty("test.short");
+        assertNull("'test.short' property not cleared", conf.getShort("test.short", null));
+    }
+
+    @Test
+    public void testIsEmpty()
+    {
+        assertFalse("the configuration shouldn't be empty", conf.isEmpty());
+    }
+
+    @Test
+    public void testGetKeys() throws Exception
+    {
+        boolean found = false;
+        Iterator<String> it = conf.getKeys();
+
+        assertTrue("no key found", it.hasNext());
+
+        while (it.hasNext() && !found)
+        {
+            found = "test.boolean".equals(it.next());
+        }
+
+        assertTrue("'test.boolean' key not found", found);
+    }
+
+    @Test
+    public void testGetKeysWithUnknownPrefix()
+    {
+        // test for a unknown prefix
+        Iterator<String> it = conf.getKeys("foo.bar");
+        assertFalse("no key should be found", it.hasNext());
+    }
+
+    @Test
+    public void testGetKeysWithExistingPrefix()
+    {
+        // test for an existing prefix
+        Iterator<String> it = conf.getKeys("test");
+        boolean found = false;
+        while (it.hasNext() && !found)
+        {
+            found = "test.boolean".equals(it.next());
+        }
+
+        assertTrue("'test.boolean' key not found", found);
+    }
+
+    @Test
+    public void testGetKeysWithKeyAsPrefix()
+    {
+        // test for a prefix matching exactly the key of a property
+        Iterator<String> it = conf.getKeys("test.boolean");
+        boolean found = false;
+        while (it.hasNext() && !found)
+        {
+            found = "test.boolean".equals(it.next());
+        }
+
+        assertTrue("'test.boolean' key not found", found);
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestMapConfiguration.java b/src/test/java/org/apache/commons/configuration/TestMapConfiguration.java
new file mode 100644
index 0000000..1e3def7
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestMapConfiguration.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.configuration.event.ConfigurationEvent;
+import org.apache.commons.configuration.event.ConfigurationListener;
+import org.junit.Test;
+
+/**
+ * Tests for MapConfiguration.
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: TestMapConfiguration.java 1222465 2011-12-22 21:32:56Z oheger $
+ */
+public class TestMapConfiguration extends TestAbstractConfiguration
+{
+    /** Constant for a test key.*/
+    private static final String KEY = "key1";
+
+    /** Constant for a test property value with whitespace.*/
+    private static final String SPACE_VALUE = "   Value with whitespace  ";
+
+    /** The trimmed test value.*/
+    private static final String TRIM_VALUE = SPACE_VALUE.trim();
+
+    @Override
+    protected AbstractConfiguration getConfiguration()
+    {
+        Map<String, Object> map = new HashMap<String, Object>();
+        map.put(KEY, "value1");
+        map.put("key2", "value2");
+        map.put("list", "value1, value2");
+        map.put("listesc", "value1\\,value2");
+
+        return new MapConfiguration(map);
+    }
+
+    @Override
+    protected AbstractConfiguration getEmptyConfiguration()
+    {
+        return new MapConfiguration(new HashMap<String, Object>());
+    }
+
+    @Test
+    public void testGetMap()
+    {
+        Map<String, Object> map = new HashMap<String, Object>();
+
+        MapConfiguration conf = new MapConfiguration(map);
+        assertEquals(map, conf.getMap());
+    }
+
+    @Test
+    public void testClone()
+    {
+        MapConfiguration config = (MapConfiguration) getConfiguration();
+        MapConfiguration copy = (MapConfiguration) config.clone();
+        StrictConfigurationComparator comp = new StrictConfigurationComparator();
+        assertTrue("Configurations are not equal", comp.compare(config, copy));
+    }
+
+    /**
+     * Tests if the cloned configuration is decoupled from the original.
+     */
+    @Test
+    public void testCloneModify()
+    {
+        MapConfiguration config = (MapConfiguration) getConfiguration();
+        config.addConfigurationListener(new ConfigurationListener()
+        {
+            public void configurationChanged(ConfigurationEvent event)
+            {
+                // Just a dummy
+            }
+        });
+        MapConfiguration copy = (MapConfiguration) config.clone();
+        assertTrue("Event listeners were copied", copy
+                .getConfigurationListeners().isEmpty());
+
+        config.addProperty("cloneTest", Boolean.TRUE);
+        assertFalse("Map not decoupled", copy.containsKey("cloneTest"));
+        copy.clearProperty("key1");
+        assertEquals("Map not decoupled (2)", "value1", config
+                .getString("key1"));
+    }
+
+    /**
+     * Tests adding another value to an existing property.
+     */
+    @Test
+    public void testAddProperty()
+    {
+        MapConfiguration config = (MapConfiguration) getConfiguration();
+        config.addProperty(KEY, TRIM_VALUE);
+        config.addProperty(KEY, "anotherValue");
+        List<Object> values = config.getList(KEY);
+        assertEquals("Wrong number of values", 3, values.size());
+    }
+
+    /**
+     * Tests querying a property when trimming is active.
+     */
+    @Test
+    public void testGetPropertyTrim()
+    {
+        MapConfiguration config = (MapConfiguration) getConfiguration();
+        config.getMap().put(KEY, SPACE_VALUE);
+        assertEquals("Wrong trimmed value", TRIM_VALUE, config.getProperty(KEY));
+    }
+
+    /**
+     * Tests querying a property when trimming is disabled.
+     */
+    @Test
+    public void testGetPropertyTrimDisabled()
+    {
+        MapConfiguration config = (MapConfiguration) getConfiguration();
+        config.getMap().put(KEY, SPACE_VALUE);
+        config.setTrimmingDisabled(true);
+        assertEquals("Wrong trimmed value", SPACE_VALUE, config.getProperty(KEY));
+    }
+
+    /**
+     * Tests querying a property when trimming is enabled, but list splitting is
+     * disabled. In this case no trimming is performed (trimming only works if
+     * list splitting is enabled).
+     */
+    @Test
+    public void testGetPropertyTrimNoSplit()
+    {
+        MapConfiguration config = (MapConfiguration) getConfiguration();
+        config.getMap().put(KEY, SPACE_VALUE);
+        config.setDelimiterParsingDisabled(true);
+        assertEquals("Wrong trimmed value", SPACE_VALUE, config.getProperty(KEY));
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestMapConfigurationRegression.java b/src/test/java/org/apache/commons/configuration/TestMapConfigurationRegression.java
new file mode 100644
index 0000000..124df61
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestMapConfigurationRegression.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestMapConfigurationRegression
+{
+    @Test
+    public void testMapConfigurationRegression()
+    {
+        final String key = UUID.randomUUID().toString();
+        final String value = UUID.randomUUID().toString();
+        final Map<String, String> map = Collections.singletonMap(key, value);
+        final MapConfiguration mc = new MapConfiguration(map);
+        Assert.assertEquals(1, mc.getMap().size());
+        Assert.assertEquals(value, mc.getString(key));
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestMultiFileHierarchicalConfiguration.java b/src/test/java/org/apache/commons/configuration/TestMultiFileHierarchicalConfiguration.java
new file mode 100644
index 0000000..3967d6e
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestMultiFileHierarchicalConfiguration.java
@@ -0,0 +1,316 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+
+import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
+import org.junit.Test;
+import org.xml.sax.SAXParseException;
+
+/**
+ * Unit test for simple MultiConfigurationTest.
+ *
+ * @version $Id: TestMultiFileHierarchicalConfiguration.java 1224811 2011-12-26 21:04:25Z oheger $
+ */
+public class TestMultiFileHierarchicalConfiguration
+{
+    private static String PATTERN1 = "target/test-classes/testMultiConfiguration_${sys:Id}.xml";
+
+    private static final File MULTI_TENENT_FILE = new File(
+            "conf/testMultiTenentConfigurationBuilder2.xml");
+
+    private static final File MULTI_TENENT_FILE2 = new File(
+            "target/test-classes/testMultiTenentConfigurationBuilder2.xml");
+
+    private static final File MULTI_RELOAD_FILE = new File(
+            "conf/testMultiTenentConfigurationBuilder3.xml");
+
+    /**
+     * Rigourous Test :-)
+     */
+    @Test
+    public void testMultiConfiguration()
+    {
+        //set up a reloading strategy
+        FileChangedReloadingStrategy strategy = new FileChangedReloadingStrategy();
+        strategy.setRefreshDelay(10000);
+
+        MultiFileHierarchicalConfiguration config = new MultiFileHierarchicalConfiguration(PATTERN1);
+        config.setReloadingStrategy(strategy);
+
+        System.setProperty("Id", "1001");
+        assertTrue(config.getInt("rowsPerPage") == 15);
+
+        System.setProperty("Id", "1002");
+        assertTrue(config.getInt("rowsPerPage") == 25);
+
+        System.setProperty("Id", "1003");
+        assertTrue(config.getInt("rowsPerPage") == 35);
+    }
+
+    @Test
+    public void testSchemaValidationError() throws Exception
+    {
+        System.getProperties().remove("Id");
+        DefaultConfigurationBuilder factory = new DefaultConfigurationBuilder();
+        factory.setFile(MULTI_TENENT_FILE);
+        CombinedConfiguration config = factory.getConfiguration(true);
+        try
+        {
+            System.setProperty("Id", "2001");
+            config.getInt("rowsPerPage");
+            fail("No exception thrown");
+        }
+        catch (Exception ex)
+        {
+            Throwable cause = ex.getCause();
+            while (cause != null && !(cause instanceof SAXParseException))
+            {
+                cause = cause.getCause();
+            }
+            assertTrue("SAXParseException was not thrown", cause instanceof SAXParseException);
+        }
+    }
+
+    @Test
+    public void testSchemaValidation() throws Exception
+    {
+        System.getProperties().remove("Id");
+        DefaultConfigurationBuilder factory = new DefaultConfigurationBuilder();
+        factory.setFile(MULTI_TENENT_FILE);
+        CombinedConfiguration config = factory.getConfiguration(true);
+        System.setProperty("Id", "2002");
+        int rows = config.getInt("rowsPerPage");
+        assertTrue("expected: " + rows + " actual: " + "25", 25 == rows);
+    }
+
+    @Test
+    public void testMissingFile() throws Exception
+    {
+        System.getProperties().remove("Id");
+        DefaultConfigurationBuilder factory = new DefaultConfigurationBuilder();
+        factory.setFile(MULTI_TENENT_FILE);
+        CombinedConfiguration config = factory.getConfiguration(true);
+        System.setProperty("Id", "3099");
+        int rows = config.getInt("rowsPerPage");
+        assertTrue("expected: " + rows + " actual: " + "50", 50 == rows);
+
+    }
+
+    @Test
+    public void testFileReload1() throws Exception
+    {
+        System.getProperties().remove("Id");
+        DefaultConfigurationBuilder factory = new DefaultConfigurationBuilder();
+        factory.setFile(MULTI_RELOAD_FILE);
+        CombinedConfiguration config = factory.getConfiguration(true);
+
+        // create a new configuration
+        File input = new File("target/test-classes/testMultiConfiguration_3001.xml");
+        File output = new File("target/test-classes/testwrite/testMultiConfiguration_3001.xml");
+        output.delete();
+        output.getParentFile().mkdir();
+        copyFile(input, output);
+
+        assertNotNull(config);
+        verify("3001", config, 15);
+        Thread.sleep(1100);
+        XMLConfiguration x = new XMLConfiguration();
+        x.setFile(output);
+        x.setAttributeSplittingDisabled(true);
+        x.setDelimiterParsingDisabled(true);
+        x.load();
+        x.setProperty("rowsPerPage", "35");
+        //Insure orginal timestamp and new timestamp aren't the same second.
+        Thread.sleep(1100);
+        x.save();
+        verify("3001", config, 35);
+        output.delete();
+    }
+
+    @Test
+    public void testFileReload2() throws Exception
+    {
+        // create a new configuration
+        File input = new File("target/test-classes/testMultiConfiguration_3002.xml");
+        File output = new File("target/test-classes/testwrite/testMultiConfiguration_3002.xml");
+        output.delete();
+
+        System.getProperties().remove("Id");
+        DefaultConfigurationBuilder factory = new DefaultConfigurationBuilder();
+        factory.setFile(MULTI_RELOAD_FILE);
+        CombinedConfiguration config = factory.getConfiguration(true);
+        assertNotNull(config);
+        // The file should not exist yet.
+        verify("3002", config, 50);
+
+        output.getParentFile().mkdir();
+        copyFile(input, output);
+        Thread.sleep(600);
+        verify("3002", config, 25);
+        output.delete();
+    }
+
+    @Test
+    public void testFileReload3() throws Exception
+    {
+        // create a new configuration
+        File input = new File("target/test-classes/testMultiConfiguration_3001.xml");
+        File output = new File("target/test-classes/testwrite/testMultiConfiguration_3001.xml");
+        output.delete();
+        output.getParentFile().mkdir();
+
+        System.getProperties().remove("Id");
+        DefaultConfigurationBuilder factory = new DefaultConfigurationBuilder();
+        factory.setFile(MULTI_RELOAD_FILE);
+        CombinedConfiguration config = factory.getConfiguration(true);
+        assertNotNull(config);
+        //The file does not exist yet.
+        verify("3001", config, 50);
+        copyFile(input, output);
+        //Sleep so refreshDelay elapses
+        Thread.sleep(600);
+        verify("3001", config, 15);
+        Thread.sleep(500);
+        XMLConfiguration x = new XMLConfiguration();
+        x.setFile(output);
+        x.setAttributeSplittingDisabled(true);
+        x.setDelimiterParsingDisabled(true);
+        x.load();
+        x.setProperty("rowsPerPage", "35");
+        // Insure original timestamp and new timestamp are not the same second.
+        Thread.sleep(1100);
+        x.save();
+        verify("3001", config, 35);
+        output.delete();
+    }
+
+    @Test
+    public void testReloadDefault() throws Exception
+    {
+        // create a new configuration
+        String defaultName = "target/test-classes/testMultiConfiguration_default.xml";
+        File input = new File(defaultName);
+
+        System.getProperties().remove("Id");
+        DefaultConfigurationBuilder factory = new DefaultConfigurationBuilder();
+        factory.setFile(MULTI_TENENT_FILE2);
+        CombinedConfiguration config = factory.getConfiguration(true);
+        assertNotNull(config);
+        verify("3001", config, 15);
+        verify("3002", config, 25);
+        System.setProperty("Id", "3002");
+        config.addProperty("/ TestProp", "Test");
+        assertTrue("Property not added", "Test".equals(config.getString("TestProp")));
+        System.getProperties().remove("Id");
+        //Sleep so refreshDelay elapses
+        Thread.sleep(600);
+        long time = System.currentTimeMillis();
+        long original = input.lastModified();
+        input.setLastModified(time);
+        File defaultFile = new File(defaultName);
+        long newTime = defaultFile.lastModified();
+        assertTrue("time mismatch", original != newTime);
+        Thread.sleep(600);
+        verify("3001", config, 15);
+        verify("3002", config, 25);
+        System.setProperty("Id", "3002");
+        String test = config.getString("TestProp");
+        assertNull("Property was not cleared by reload", test);
+    }
+
+    @Test
+    public void testFileReloadSchemaValidationError() throws Exception
+    {
+        System.getProperties().remove("Id");
+        DefaultConfigurationBuilder factory = new DefaultConfigurationBuilder();
+        factory.setFile(MULTI_RELOAD_FILE);
+        CombinedConfiguration config = factory.getConfiguration(true);
+
+        // create a new configuration
+        File input = new File("target/test-classes/testMultiConfiguration_3001.xml");
+        File output = new File("target/test-classes/testwrite/testMultiConfiguration_3001.xml");
+        output.delete();
+        output.getParentFile().mkdir();
+        copyFile(input, output);
+
+        assertNotNull(config);
+        verify("3001", config, 15);
+        Thread.sleep(1100);
+        XMLConfiguration x = new XMLConfiguration();
+        x.setFile(output);
+        x.setAttributeSplittingDisabled(true);
+        x.setDelimiterParsingDisabled(true);
+        x.load();
+        x.setProperty("rowsPerPage", "test");
+        //Insure orginal timestamp and new timestamp aren't the same second.
+        Thread.sleep(1100);
+        x.save();
+        System.setProperty("Id", "3001");
+        try
+        {
+            config.getInt("rowsPerPage");
+            fail("No exception was thrown");
+        }
+        catch (Exception ex)
+        {
+
+        }
+
+        output.delete();
+    }
+
+    private void copyFile(File input, File output) throws IOException
+    {
+        Reader reader = new FileReader(input);
+        Writer writer = new FileWriter(output);
+        char[] buffer = new char[4096];
+        int n = 0;
+        while (-1 != (n = reader.read(buffer)))
+        {
+            writer.write(buffer, 0, n);
+        }
+        reader.close();
+        writer.close();
+    }
+
+    private void verify(String key, CombinedConfiguration config, int rows)
+    {
+        if (key == null)
+        {
+            System.getProperties().remove("Id");
+        }
+        else
+        {
+            System.setProperty("Id", key);
+        }
+        int actual = config.getInt("rowsPerPage");
+        assertTrue("expected: " + rows + " actual: " + actual, actual == rows);
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestNonStringProperties.java b/src/test/java/org/apache/commons/configuration/TestNonStringProperties.java
new file mode 100644
index 0000000..1e4e166
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestNonStringProperties.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import org.junit.Before;
+
+/**
+ * Test if non-string properties are handled correctly.
+ *
+ * @version $Id: TestNonStringProperties.java 1222445 2011-12-22 20:53:47Z oheger $
+ */
+public class TestNonStringProperties extends BaseNonStringProperties
+{
+    /** The File that we test with */
+    private String testProperties = ConfigurationAssert.getTestFile("test.properties").getAbsolutePath();
+
+    @Before
+    public void setUp() throws Exception
+    {
+        conf = new PropertiesConfiguration(testProperties);
+        nonStringTestHolder.setConfiguration(conf);
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestNullCompositeConfiguration.java b/src/test/java/org/apache/commons/configuration/TestNullCompositeConfiguration.java
new file mode 100644
index 0000000..934abc5
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestNullCompositeConfiguration.java
@@ -0,0 +1,451 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test loading multiple configurations.
+ *
+ * @version $Id: TestNullCompositeConfiguration.java 1224814 2011-12-26 21:15:53Z oheger $
+ */
+public class TestNullCompositeConfiguration
+{
+    protected PropertiesConfiguration conf1;
+    protected PropertiesConfiguration conf2;
+    protected XMLConfiguration xmlConf;
+    protected CompositeConfiguration cc;
+
+    /** The File that we test with */
+    private String testProperties = ConfigurationAssert.getTestFile("test.properties").getAbsolutePath();
+    private String testProperties2 = ConfigurationAssert.getTestFile("test2.properties").getAbsolutePath();
+    private String testPropertiesXML = ConfigurationAssert.getTestFile("test.xml").getAbsolutePath();
+
+    @Before
+    public void setUp() throws Exception
+    {
+        cc = new CompositeConfiguration();
+        conf1 = new PropertiesConfiguration(testProperties);
+        conf2 = new PropertiesConfiguration(testProperties2);
+        xmlConf = new XMLConfiguration(new File(testPropertiesXML));
+
+        cc.setThrowExceptionOnMissing(false);
+    }
+
+    @Test
+    public void testThrowExceptionOnMissing()
+    {
+        assertFalse("Throw Exception Property is set!", cc.isThrowExceptionOnMissing());
+    }
+
+    @Test
+    public void testAddRemoveConfigurations() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        assertEquals(2, cc.getNumberOfConfigurations());
+        cc.addConfiguration(conf1);
+        assertEquals(2, cc.getNumberOfConfigurations());
+        cc.addConfiguration(conf2);
+        assertEquals(3, cc.getNumberOfConfigurations());
+        cc.removeConfiguration(conf1);
+        assertEquals(2, cc.getNumberOfConfigurations());
+        cc.clear();
+        assertEquals(1, cc.getNumberOfConfigurations());
+    }
+
+    @Test
+    public void testGetPropertyWIncludes() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(conf2);
+        List<Object> l = cc.getList("packages");
+        assertTrue(l.contains("packagea"));
+    }
+
+    @Test
+    public void testGetProperty() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(conf2);
+        assertEquals("Make sure we get the property from conf1 first", "test.properties", cc.getString("propertyInOrder"));
+        cc.clear();
+
+        cc.addConfiguration(conf2);
+        cc.addConfiguration(conf1);
+        assertEquals("Make sure we get the property from conf2 first", "test2.properties", cc.getString("propertyInOrder"));
+    }
+
+    @Test
+    public void testCantRemoveMemoryConfig() throws Exception
+    {
+        cc.clear();
+        assertEquals(1, cc.getNumberOfConfigurations());
+
+        Configuration internal = cc.getConfiguration(0);
+        cc.removeConfiguration(internal);
+
+        assertEquals(1, cc.getNumberOfConfigurations());
+    }
+
+    @Test
+    public void testGetPropertyMissing() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(conf2);
+
+        assertNull("Bogus property is not null!", cc.getString("bogus.property"));
+
+        assertTrue("Should be false", !cc.getBoolean("test.missing.boolean", false));
+        assertTrue("Should be true", cc.getBoolean("test.missing.boolean.true", true));
+    }
+
+    @Test
+    public void testMultipleTypesOfConfigs() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(xmlConf);
+        assertEquals("Make sure we get the property from conf1 first", 1, cc.getInt("test.short"));
+        cc.clear();
+
+        cc.addConfiguration(xmlConf);
+        cc.addConfiguration(conf1);
+        assertEquals("Make sure we get the property from xml", 8, cc.getInt("test.short"));
+    }
+
+    @Test
+    public void testPropertyExistsInOnlyOneConfig() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(xmlConf);
+        assertEquals("value", cc.getString("element"));
+    }
+
+    /**
+     * Tests getting a default when the key doesn't exist
+     */
+    @Test
+    public void testDefaultValueWhenKeyMissing() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(xmlConf);
+        assertEquals("default", cc.getString("bogus", "default"));
+        assertTrue(1.4 == cc.getDouble("bogus", 1.4));
+        assertTrue(1.4 == cc.getDouble("bogus", 1.4));
+    }
+
+    @Test
+    public void testGettingConfiguration() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(xmlConf);
+        assertEquals(PropertiesConfiguration.class, cc.getConfiguration(0).getClass());
+        assertEquals(XMLConfiguration.class, cc.getConfiguration(1).getClass());
+    }
+
+    /**
+     * Tests setting values.  These are set in memory mode only!
+     */
+    @Test
+    public void testClearingProperty() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(xmlConf);
+        cc.clearProperty("test.short");
+        assertTrue("Make sure test.short is gone!", !cc.containsKey("test.short"));
+    }
+
+    /**
+     * Tests adding values.  Make sure they _DON'T_ override any other properties but add to the
+     * existing properties  and keep sequence
+     */
+    @Test
+    public void testAddingProperty() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(xmlConf);
+
+        String[] values = cc.getStringArray("test.short");
+
+        assertEquals("Number of values before add is wrong!", 1, values.length);
+        assertEquals("First Value before add is wrong", "1", values[0]);
+
+        cc.addProperty("test.short", "88");
+
+        values = cc.getStringArray("test.short");
+
+        assertEquals("Number of values is wrong!", 2, values.length);
+        assertEquals("First Value is wrong", "1", values[0]);
+        assertEquals("Third Value is wrong", "88", values[1]);
+    }
+
+    /**
+     * Tests setting values.  These are set in memory mode only!
+     */
+    @Test
+    public void testSettingMissingProperty() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(xmlConf);
+        cc.setProperty("my.new.property", "supernew");
+        assertEquals("supernew", cc.getString("my.new.property"));
+    }
+
+    /**
+     * Tests retrieving subsets of configurations
+     */
+    @Test
+    public void testGettingSubset() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(xmlConf);
+
+        Configuration subset = null;
+        subset = cc.subset("test");
+        assertNotNull(subset);
+        assertFalse("Shouldn't be empty", subset.isEmpty());
+        assertEquals("Make sure the initial loaded configs subset overrides any later add configs subset", "1", subset.getString("short"));
+
+        cc.setProperty("test.short", "43");
+        subset = cc.subset("test");
+        assertEquals("Make sure the initial loaded configs subset overrides any later add configs subset", "43", subset.getString("short"));
+    }
+
+    /**
+     * Tests subsets and still can resolve elements
+     */
+    @Test
+    public void testSubsetCanResolve() throws Exception
+    {
+        cc = new CompositeConfiguration();
+        final BaseConfiguration config = new BaseConfiguration();
+        config.addProperty("subset.tempfile", "${java.io.tmpdir}/file.tmp");
+        cc.addConfiguration(config);
+        cc.addConfiguration(ConfigurationConverter.getConfiguration(System.getProperties()));
+
+        Configuration subset = cc.subset("subset");
+        assertEquals(System.getProperty("java.io.tmpdir") + "/file.tmp", subset.getString("tempfile"));
+    }
+
+    /**
+      * Tests {@code List} parsing.
+      */
+    @Test
+    public void testList() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(xmlConf);
+
+        List<Object> packages = cc.getList("packages");
+        // we should get 3 packages here
+        assertEquals(3, packages.size());
+
+        List<Object> defaultList = new ArrayList<Object>();
+        defaultList.add("1");
+        defaultList.add("2");
+
+        packages = cc.getList("packages.which.dont.exist", defaultList);
+        // we should get 2 packages here
+        assertEquals(2, packages.size());
+    }
+
+    /**
+     * Tests {@code String} array parsing.
+     */
+    @Test
+    public void testStringArray() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(xmlConf);
+
+        String[] packages = cc.getStringArray("packages");
+        // we should get 3 packages here
+        assertEquals(3, packages.length);
+
+        packages = cc.getStringArray("packages.which.dont.exist");
+        // we should get 0 packages here
+        assertEquals(0, packages.length);
+    }
+
+    @Test
+    public void testGetList()
+    {
+        Configuration conf1 = new BaseConfiguration();
+        conf1.addProperty("array", "value1");
+        conf1.addProperty("array", "value2");
+
+        Configuration conf2 = new BaseConfiguration();
+        conf2.addProperty("array", "value3");
+        conf2.addProperty("array", "value4");
+
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(conf2);
+
+        // check the composite 'array' property
+        List<Object> list = cc.getList("array");
+        assertNotNull("null list", list);
+        assertEquals("list size", 2, list.size());
+        assertTrue("'value1' not found in the list", list.contains("value1"));
+        assertTrue("'value2' not found in the list", list.contains("value2"));
+
+        // add an element to the list in the composite configuration
+        cc.addProperty("array", "value5");
+
+        // test the new list
+        list = cc.getList("array");
+        assertNotNull("null list", list);
+        assertEquals("list size", 3, list.size());
+        assertTrue("'value1' not found in the list", list.contains("value1"));
+        assertTrue("'value2' not found in the list", list.contains("value2"));
+        assertTrue("'value5' not found in the list", list.contains("value5"));
+    }
+
+    @Test
+    public void testGetVector()
+    {
+        Configuration conf1 = new BaseConfiguration();
+        conf1.addProperty("array", "value1");
+        conf1.addProperty("array", "value2");
+
+        Configuration conf2 = new BaseConfiguration();
+        conf2.addProperty("array", "value3");
+        conf2.addProperty("array", "value4");
+
+        cc.addConfiguration(conf1);
+        cc.addConfiguration(conf2);
+
+        // add an element to the vector in the composite configuration
+        cc.addProperty("array", "value5");
+
+        List<Object> list = cc.getList("array");
+        assertEquals("Wrong number of elements", 3, list.size());
+        assertEquals("Wrong element 1", "value1", list.get(0));
+        assertEquals("Wrong element 2", "value2", list.get(1));
+        assertEquals("Wrong element 3", "value5", list.get(2));
+    }
+
+    /**
+      * Tests {@code getKeys()} preserves the order
+      */
+    @Test
+    public void testGetKeysPreservesOrder() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        List<String> orderedList = new ArrayList<String>();
+        for (Iterator<String> keys = conf1.getKeys(); keys.hasNext();)
+        {
+            orderedList.add(keys.next());
+        }
+        List<String> iteratedList = new ArrayList<String>();
+        for (Iterator<String> keys = cc.getKeys(); keys.hasNext();)
+        {
+            iteratedList.add(keys.next());
+        }
+        assertEquals(orderedList.size(), iteratedList.size());
+        for (int i = 0; i < orderedList.size(); i++)
+        {
+            assertEquals(orderedList.get(i), iteratedList.get(i));
+        }
+    }
+
+    /**
+      * Tests {@code getKeys(String key)} preserves the order
+      */
+    @Test
+    public void testGetKeys2PreservesOrder() throws Exception
+    {
+        cc.addConfiguration(conf1);
+        List<String> orderedList = new ArrayList<String>();
+        for (Iterator<String> keys = conf1.getKeys("test"); keys.hasNext();)
+        {
+            orderedList.add(keys.next());
+        }
+        List<String> iteratedList = new ArrayList<String>();
+        for (Iterator<String> keys = cc.getKeys("test"); keys.hasNext();)
+        {
+            iteratedList.add(keys.next());
+        }
+        assertEquals(orderedList.size(), iteratedList.size());
+        for (int i = 0; i < orderedList.size(); i++)
+        {
+            assertEquals(orderedList.get(i), iteratedList.get(i));
+        }
+    }
+
+    @Test
+    public void testGetStringWithDefaults()
+    {
+        BaseConfiguration defaults = new BaseConfiguration();
+        defaults.addProperty("default", "default string");
+
+        Configuration c = new CompositeConfiguration(defaults);
+
+        c.addProperty("string", "test string");
+
+        assertEquals("test string", c.getString("string"));
+
+        assertNull("XXX should have been null!", c.getString("XXX"));
+
+        //test defaults
+        assertEquals(
+            "test string",
+            c.getString("string", "some default value"));
+        assertEquals("default string", c.getString("default"));
+        assertEquals(
+            "default string",
+            c.getString("default", "some default value"));
+        assertEquals(
+            "some default value",
+            c.getString("XXX", "some default value"));
+    }
+
+    @Test
+    public void testCheckingInMemoryConfiguration() throws Exception
+    {
+        String TEST_KEY = "testKey";
+        Configuration defaults = new PropertiesConfiguration();
+        defaults.setProperty(TEST_KEY, "testValue");
+        Configuration testConfiguration = new CompositeConfiguration(defaults);
+        assertTrue(testConfiguration.containsKey(TEST_KEY));
+        assertFalse(testConfiguration.isEmpty());
+        boolean foundTestKey = false;
+        Iterator<String> i = testConfiguration.getKeys();
+        for (; i.hasNext();)
+        {
+            String key = i.next();
+            if (key.equals(TEST_KEY))
+            {
+                foundTestKey = true;
+            }
+        }
+        assertTrue(foundTestKey);
+        testConfiguration.clearProperty(TEST_KEY);
+        assertFalse(testConfiguration.containsKey(TEST_KEY));
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestNullJNDIEnvironmentValues.java b/src/test/java/org/apache/commons/configuration/TestNullJNDIEnvironmentValues.java
new file mode 100644
index 0000000..5456897
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestNullJNDIEnvironmentValues.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Iterator;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestNullJNDIEnvironmentValues
+{
+    private JNDIConfiguration conf = null;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        System.setProperty("java.naming.factory.initial", TestJNDIConfiguration.CONTEXT_FACTORY);
+
+        conf = new JNDIConfiguration();
+        conf.setThrowExceptionOnMissing(false);
+    }
+
+    @Test
+    public void testThrowExceptionOnMissing()
+    {
+        assertFalse("Throw Exception Property is set!", conf.isThrowExceptionOnMissing());
+    }
+
+    @Test
+    public void testSimpleGet() throws Exception
+    {
+        String s = conf.getString("test.key");
+        assertEquals("jndivalue", s);
+    }
+
+    @Test
+    public void testMoreGets() throws Exception
+    {
+        String s = conf.getString("test.key");
+        assertEquals("jndivalue", s);
+        assertEquals("jndivalue2", conf.getString("test.key2"));
+        assertEquals(1, conf.getShort("test.short"));
+    }
+
+    @Test
+    public void testGetMissingKey() throws Exception
+    {
+        assertNull("Missing Key is not null!", conf.getString("test.imaginarykey"));
+    }
+
+    @Test
+    public void testGetMissingKeyWithDefault() throws Exception
+    {
+        String result = conf.getString("test.imaginarykey", "bob");
+        assertEquals("bob", result);
+    }
+
+    @Test
+    public void testContainsKey() throws Exception
+    {
+        assertTrue(conf.containsKey("test.key"));
+        assertTrue(!conf.containsKey("test.imaginarykey"));
+    }
+
+    @Test
+    public void testClearProperty()
+    {
+        assertNotNull("null short for the 'test.short' key", conf.getShort("test.short", null));
+        conf.clearProperty("test.short");
+        assertNull("'test.short' property not cleared", conf.getShort("test.short", null));
+    }
+
+    @Test
+    public void testIsEmpty()
+    {
+        assertFalse("the configuration shouldn't be empty", conf.isEmpty());
+    }
+
+    @Test
+    public void testGetKeys() throws Exception
+    {
+        boolean found = false;
+        Iterator<String> it = conf.getKeys();
+
+        assertTrue("no key found", it.hasNext());
+
+        while (it.hasNext() && !found)
+        {
+            found = "test.boolean".equals(it.next());
+        }
+
+        assertTrue("'test.boolean' key not found", found);
+    }
+
+    @Test
+    public void testGetKeysWithUnknownPrefix()
+    {
+        // test for a unknown prefix
+        Iterator<String> it = conf.getKeys("foo.bar");
+        assertFalse("no key should be found", it.hasNext());
+    }
+
+    @Test
+    public void testGetKeysWithExistingPrefix()
+    {
+        // test for an existing prefix
+        Iterator<String> it = conf.getKeys("test");
+        boolean found = false;
+        while (it.hasNext() && !found)
+        {
+            found = "test.boolean".equals(it.next());
+        }
+
+        assertTrue("'test.boolean' key not found", found);
+    }
+
+    @Test
+    public void testGetKeysWithKeyAsPrefix()
+    {
+        // test for a prefix matching exactly the key of a property
+        Iterator<String> it = conf.getKeys("test.boolean");
+        boolean found = false;
+        while (it.hasNext() && !found)
+        {
+            found = "test.boolean".equals(it.next());
+        }
+
+        assertTrue("'test.boolean' key not found", found);
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestPatternSubtreeConfiguration.java b/src/test/java/org/apache/commons/configuration/TestPatternSubtreeConfiguration.java
new file mode 100644
index 0000000..5c6c477
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestPatternSubtreeConfiguration.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+
+import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
+import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Unit test for simple MultiConfigurationTest.
+ *
+ * @version $Id: TestPatternSubtreeConfiguration.java 1224818 2011-12-26 21:20:17Z oheger $
+ */
+public class TestPatternSubtreeConfiguration
+{
+    private static String CONFIG_FILE = "target/test-classes/testPatternSubtreeConfig.xml";
+    private static String PATTERN = "BusinessClient[@name='${sys:Id}']";
+    private XMLConfiguration conf;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        conf = new XMLConfiguration();
+        conf.setFile(new File(CONFIG_FILE));
+        conf.load();
+    }
+
+    /**
+     * Rigourous Test :-)
+     */
+    @Test
+    public void testMultiConfiguration()
+    {
+        //set up a reloading strategy
+        FileChangedReloadingStrategy strategy = new FileChangedReloadingStrategy();
+        strategy.setRefreshDelay(10000);
+
+        PatternSubtreeConfigurationWrapper config = new PatternSubtreeConfigurationWrapper(this.conf, PATTERN);
+        config.setReloadingStrategy(strategy);
+        config.setExpressionEngine(new XPathExpressionEngine());
+
+        System.setProperty("Id", "1001");
+        assertTrue(config.getInt("rowsPerPage") == 15);
+
+        System.setProperty("Id", "1002");
+        assertTrue(config.getInt("rowsPerPage") == 25);
+
+        System.setProperty("Id", "1003");
+        assertTrue(config.getInt("rowsPerPage") == 35);
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/commons/configuration/TestPropertiesConfiguration.java b/src/test/java/org/apache/commons/configuration/TestPropertiesConfiguration.java
new file mode 100644
index 0000000..c60e8b9
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestPropertiesConfiguration.java
@@ -0,0 +1,1359 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
+import org.apache.commons.lang.SystemUtils;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test for loading and saving properties files.
+ *
+ * @version $Id: TestPropertiesConfiguration.java 1534402 2013-10-21 22:35:52Z henning $
+ */
+public class TestPropertiesConfiguration
+{
+    /** Constant for a test property name.*/
+    private static final String PROP_NAME = "testProperty";
+
+    /** Constant for a test property value.*/
+    private static final String PROP_VALUE = "value";
+
+    /** Constant for the line break character. */
+    private static final String CR = System.getProperty("line.separator");
+
+    /** The configuration to be tested.*/
+    private PropertiesConfiguration conf;
+
+    /** The File that we test with */
+    private static String testProperties = ConfigurationAssert.getTestFile("test.properties").getAbsolutePath();
+
+    private static String testBasePath = ConfigurationAssert.TEST_DIR.getAbsolutePath();
+    private static String testBasePath2 = ConfigurationAssert.TEST_DIR.getParentFile().getAbsolutePath();
+    private static File testSavePropertiesFile = ConfigurationAssert.getOutFile("testsave.properties");
+
+    @Before
+    public void setUp() throws Exception
+    {
+        conf = new PropertiesConfiguration(testProperties);
+
+        // remove the test save file if it exists
+        if (testSavePropertiesFile.exists())
+        {
+            assertTrue("Test output file could not be deleted",
+                    testSavePropertiesFile.delete());
+        }
+    }
+
+    @Test
+    public void testLoad() throws Exception
+    {
+        String loaded = conf.getString("configuration.loaded");
+        assertEquals("true", loaded);
+    }
+
+    /**
+     * Tests if properties can be appended by simply calling load() another
+     * time.
+     */
+    @Test
+    public void testAppend() throws Exception
+    {
+        File file2 = ConfigurationAssert.getTestFile("threesome.properties");
+        conf.load(file2);
+        assertEquals("aaa", conf.getString("test.threesome.one"));
+        assertEquals("true", conf.getString("configuration.loaded"));
+    }
+
+    /**
+     * Tests that empty properties are treated as the empty string
+     * (rather than as null).
+     */
+    @Test
+    public void testEmpty() throws Exception
+    {
+        String empty = conf.getString("test.empty");
+        assertNotNull(empty);
+        assertEquals("", empty);
+    }
+
+    /**
+     * Tests that references to other properties work
+     */
+    @Test
+    public void testReference() throws Exception
+    {
+        assertEquals("baseextra", conf.getString("base.reference"));
+    }
+
+    /**
+     * test if includes properties get loaded too
+     */
+    @Test
+    public void testLoadInclude() throws Exception
+    {
+        String loaded = conf.getString("include.loaded");
+        assertEquals("true", loaded);
+    }
+
+    /**
+     * test if includes properties from interpolated file
+     * name get loaded
+     */
+    @Test
+    public void testLoadIncludeInterpol() throws Exception
+    {
+        String loaded = conf.getString("include.interpol.loaded");
+        assertEquals("true", loaded);
+    }
+
+    /**
+     * Tests whether include files can be resolved if a configuration file is
+     * read from a reader.
+     */
+    @Test
+    public void testLoadIncludeFromReader() throws ConfigurationException,
+            IOException
+    {
+        StringReader in =
+                new StringReader(PropertiesConfiguration.getInclude() + " = "
+                        + ConfigurationAssert.getTestURL("include.properties"));
+        conf = new PropertiesConfiguration();
+        conf.load(in);
+        assertEquals("Include file not loaded", "true",
+                conf.getString("include.loaded"));
+    }
+
+    /**
+     * Tests whether include files can be disabled.
+     */
+    @Test
+    public void testDisableIncludes() throws ConfigurationException,
+            IOException
+    {
+        String content =
+                PropertiesConfiguration.getInclude()
+                        + " = nonExistingIncludeFile" + CR + PROP_NAME + " = "
+                        + PROP_VALUE + CR;
+        StringReader in = new StringReader(content);
+        conf = new PropertiesConfiguration();
+        conf.setIncludesAllowed(false);
+        conf.load(in);
+        assertEquals("Data not loaded", PROP_VALUE, conf.getString(PROP_NAME));
+    }
+
+    @Test
+    public void testSetInclude() throws Exception
+    {
+        // change the include key
+        PropertiesConfiguration.setInclude("import");
+
+        // load the configuration
+        PropertiesConfiguration conf = new PropertiesConfiguration();
+        conf.load(ConfigurationAssert.getTestFile("test.properties"));
+
+        // restore the previous value for the other tests
+        PropertiesConfiguration.setInclude("include");
+
+        assertNull(conf.getString("include.loaded"));
+    }
+
+    /**
+     * Tests {@code List} parsing.
+     */
+    @Test
+    public void testList() throws Exception
+    {
+        List<Object> packages = conf.getList("packages");
+        // we should get 3 packages here
+        assertEquals(3, packages.size());
+    }
+
+    @Test
+    public void testSave() throws Exception
+    {
+        // add an array of strings to the configuration
+        conf.addProperty("string", "value1");
+        List<Object> list = new ArrayList<Object>();
+        for (int i = 1; i < 5; i++)
+        {
+            list.add("value" + i);
+        }
+        conf.addProperty("array", list);
+
+        // save the configuration
+        String filename = testSavePropertiesFile.getAbsolutePath();
+        conf.save(filename);
+
+        assertTrue("The saved file doesn't exist", testSavePropertiesFile.exists());
+
+        // read the configuration and compare the properties
+        PropertiesConfiguration checkConfig = new PropertiesConfiguration(filename);
+        ConfigurationAssert.assertEquals(conf, checkConfig);
+
+        // Save it again, verifying a save with a filename works.
+        checkConfig.save();
+    }
+
+    @Test
+    public void testSaveToCustomURL() throws Exception
+    {
+        // save the configuration to a custom URL
+        URL url = new URL("foo", "", 0, "./target/testsave-custom-url.properties", new FileURLStreamHandler());
+        conf.save(url);
+
+        // reload the configuration
+        Configuration config2 = new PropertiesConfiguration(url);
+        assertEquals("true", config2.getString("configuration.loaded"));
+    }
+
+    @Test
+    public void testInMemoryCreatedSave() throws Exception
+    {
+        PropertiesConfiguration pc = new PropertiesConfiguration();
+        // add an array of strings to the configuration
+        pc.addProperty("string", "value1");
+        List<Object> list = new ArrayList<Object>();
+        for (int i = 1; i < 5; i++)
+        {
+            list.add("value" + i);
+        }
+        pc.addProperty("array", list);
+
+        // save the configuration
+        String filename = testSavePropertiesFile.getAbsolutePath();
+        pc.save(filename);
+
+        assertTrue("The saved file doesn't exist", testSavePropertiesFile.exists());
+
+        // read the configuration and compare the properties
+        PropertiesConfiguration checkConfig = new PropertiesConfiguration(filename);
+        ConfigurationAssert.assertEquals(pc, checkConfig);
+
+        // Save it again, verifying a save with a filename works.
+        checkConfig.save();
+    }
+
+    /**
+     * Tests saving a configuration when delimiter parsing is disabled.
+     */
+    @Test
+    public void testSaveWithDelimiterParsingDisabled() throws ConfigurationException
+    {
+        conf.clear();
+        conf.setDelimiterParsingDisabled(true);
+        conf.addProperty("test.list", "a,b,c");
+        conf.addProperty("test.dirs", "C:\\Temp\\,D:\\Data\\");
+        conf.save(testSavePropertiesFile);
+
+        PropertiesConfiguration checkConfig = new PropertiesConfiguration();
+        checkConfig.setDelimiterParsingDisabled(true);
+        checkConfig.setFile(testSavePropertiesFile);
+        checkConfig.load();
+        ConfigurationAssert.assertEquals(conf, checkConfig);
+    }
+
+    @Test(expected = ConfigurationException.class)
+    public void testSaveMissingFilename() throws ConfigurationException
+    {
+        PropertiesConfiguration pc = new PropertiesConfiguration();
+        pc.save();
+    }
+
+    /**
+     * Tests if the base path is taken into account by the save() method.
+     * @throws Exception if an error occurs
+     */
+    @Test
+    public void testSaveWithBasePath() throws Exception
+    {
+        conf.setProperty("test", "true");
+        conf.setBasePath(testSavePropertiesFile.getParentFile().toURI().toURL()
+                .toString());
+        conf.setFileName(testSavePropertiesFile.getName());
+        conf.save();
+        assertTrue(testSavePropertiesFile.exists());
+    }
+
+    /**
+     * Tests whether the escape character for list delimiters can be itself
+     * escaped and survives a save operation.
+     */
+    @Test
+    public void testSaveEscapedEscapingCharacter()
+            throws ConfigurationException
+    {
+        conf.addProperty("test.dirs", "C:\\Temp\\\\,D:\\Data\\\\,E:\\Test\\");
+        List<Object> dirs = conf.getList("test.dirs");
+        assertEquals("Wrong number of list elements", 3, dirs.size());
+        conf.save(testSavePropertiesFile);
+
+        PropertiesConfiguration checkConfig = new PropertiesConfiguration(
+                testSavePropertiesFile);
+        ConfigurationAssert.assertEquals(conf, checkConfig);
+    }
+
+    @Test
+    public void testLoadViaProperty() throws Exception
+    {
+        PropertiesConfiguration pc = new PropertiesConfiguration();
+        pc.setFileName(testProperties);
+        pc.load();
+
+        assertTrue("Make sure we have multiple keys", pc.getBoolean("test.boolean"));
+    }
+
+    @Test
+    public void testLoadViaPropertyWithBasePath() throws Exception
+    {
+        PropertiesConfiguration pc = new PropertiesConfiguration();
+        pc.setBasePath(testBasePath);
+        pc.setFileName("test.properties");
+        pc.load();
+
+        assertTrue("Make sure we have multiple keys", pc.getBoolean("test.boolean"));
+    }
+
+    @Test
+    public void testLoadViaPropertyWithBasePath2() throws Exception
+    {
+        PropertiesConfiguration pc = new PropertiesConfiguration();
+        pc.setBasePath(testBasePath2);
+        pc.setFileName("test.properties");
+        pc.load();
+
+        assertTrue("Make sure we have multiple keys", pc.getBoolean("test.boolean"));
+
+        pc = new PropertiesConfiguration();
+        pc.setBasePath(testBasePath2);
+        pc.setFileName("test.properties");
+        pc.load();
+
+        assertTrue("Make sure we have multiple keys", pc.getBoolean("test.boolean"));
+    }
+
+    @Test
+    public void testLoadFromFile() throws Exception
+    {
+        File file = ConfigurationAssert.getTestFile("test.properties");
+        conf = new PropertiesConfiguration(file);
+
+        assertEquals("true", conf.getString("configuration.loaded"));
+    }
+
+    @Test(expected = ConfigurationException.class)
+    public void testLoadUnexistingFile() throws ConfigurationException
+    {
+        conf = new PropertiesConfiguration("Unexisting file");
+    }
+
+    /**
+     * Tests to load a file with enabled auto save mode.
+     */
+    @Test
+    public void testLoadWithAutoSave() throws Exception
+    {
+        setUpSavedProperties();
+    }
+
+    /**
+     * Tests the auto save functionality when an existing property is modified.
+     */
+    @Test
+    public void testLoadWithAutoSaveAndSetExisting() throws Exception
+    {
+        setUpSavedProperties();
+        conf.setProperty("a", "moreThanOne");
+        checkSavedConfig();
+    }
+
+    /**
+     * Tests the auto save functionality when a new property is added using the
+     * setProperty() method.
+     */
+    @Test
+    public void testLoadWithAutoSaveAndSetNew() throws Exception
+    {
+        setUpSavedProperties();
+        conf.setProperty("d", "four");
+        checkSavedConfig();
+    }
+
+    /**
+     * Tests the auto save functionality when a new property is added using the
+     * addProperty() method.
+     */
+    @Test
+    public void testLoadWithAutoSaveAndAdd() throws Exception
+    {
+        setUpSavedProperties();
+        conf.addProperty("d", "four");
+        checkSavedConfig();
+    }
+
+    /**
+     * Tests the auto save functionality when a property is removed.
+     */
+    @Test
+    public void testLoadWithAutoSaveAndClear() throws Exception
+    {
+        setUpSavedProperties();
+        conf.clearProperty("c");
+        PropertiesConfiguration checkConfig = checkSavedConfig();
+        assertFalse("The saved configuration contain the key '" + "c" + "'", checkConfig.containsKey("c"));
+    }
+
+    /**
+     * Creates a properties file on disk. Used for testing load and save
+     * operations.
+     *
+     * @throws IOException if an I/O error occurs
+     */
+    private void setUpSavedProperties() throws IOException, ConfigurationException
+    {
+        PrintWriter out = null;
+
+        try
+        {
+            out = new PrintWriter(new FileWriter(testSavePropertiesFile));
+            out.println("a = one");
+            out.println("b = two");
+            out.println("c = three");
+            out.close();
+            out = null;
+
+            conf = new PropertiesConfiguration();
+            conf.setAutoSave(true);
+            conf.setFile(testSavePropertiesFile);
+            conf.load();
+            assertEquals("one", conf.getString("a"));
+            assertEquals("two", conf.getString("b"));
+            assertEquals("three", conf.getString("c"));
+        }
+        finally
+        {
+            if (out != null)
+            {
+                out.close();
+            }
+        }
+    }
+
+    /**
+     * Helper method for testing a saved configuration. Reads in the file using
+     * a new instance and compares this instance with the original one.
+     *
+     * @return the newly created configuration instance
+     * @throws ConfigurationException if an error occurs
+     */
+    private PropertiesConfiguration checkSavedConfig()
+            throws ConfigurationException
+    {
+        PropertiesConfiguration checkConfig = new PropertiesConfiguration(testSavePropertiesFile);
+        ConfigurationAssert.assertEquals(conf, checkConfig);
+        return checkConfig;
+    }
+
+    @Test
+    public void testGetStringWithEscapedChars()
+    {
+        String property = conf.getString("test.unescape");
+        assertEquals("String with escaped characters", "This \n string \t contains \" escaped \\ characters", property);
+    }
+
+    @Test
+    public void testGetStringWithEscapedComma()
+    {
+        String property = conf.getString("test.unescape.list-separator");
+        assertEquals("String with an escaped list separator", "This string contains , an escaped list separator", property);
+    }
+
+    @Test
+    public void testUnescapeJava()
+    {
+        assertEquals("test\\,test", PropertiesConfiguration.unescapeJava("test\\,test", ','));
+    }
+
+    @Test
+    public void testEscapedKey() throws Exception
+    {
+        PropertiesConfiguration conf = new PropertiesConfiguration();
+        conf.load(new StringReader("\\u0066\\u006f\\u006f=bar"));
+
+        assertEquals("value of the 'foo' property", "bar", conf.getString("foo"));
+    }
+
+    @Test
+    public void testMixedArray()
+    {
+        String[] array = conf.getStringArray("test.mixed.array");
+
+        assertEquals("array length", 4, array.length);
+        assertEquals("1st element", "a", array[0]);
+        assertEquals("2nd element", "b", array[1]);
+        assertEquals("3rd element", "c", array[2]);
+        assertEquals("4th element", "d", array[3]);
+    }
+
+    @Test
+    public void testMultilines()
+    {
+        String property = "This is a value spread out across several adjacent "
+                + "natural lines by escaping the line terminator with "
+                + "a backslash character.";
+
+        assertEquals("'test.multilines' property", property, conf.getString("test.multilines"));
+    }
+
+    @Test
+    public void testChangingDefaultListDelimiter() throws Exception
+    {
+        PropertiesConfiguration pc = new PropertiesConfiguration(testProperties);
+        assertEquals(4, pc.getList("test.mixed.array").size());
+
+        char delimiter = PropertiesConfiguration.getDefaultListDelimiter();
+        PropertiesConfiguration.setDefaultListDelimiter('^');
+        pc = new PropertiesConfiguration(testProperties);
+        assertEquals(2, pc.getList("test.mixed.array").size());
+        PropertiesConfiguration.setDefaultListDelimiter(delimiter);
+    }
+
+    @Test
+    public void testChangingListDelimiter() throws Exception
+    {
+        PropertiesConfiguration pc1 = new PropertiesConfiguration(testProperties);
+        assertEquals(4, pc1.getList("test.mixed.array").size());
+
+        PropertiesConfiguration pc2 = new PropertiesConfiguration();
+        pc2.setListDelimiter('^');
+        pc2.setFileName(testProperties);
+        pc2.load();
+        assertEquals("Should obtain the first value", "a", pc2.getString("test.mixed.array"));
+        assertEquals(2, pc2.getList("test.mixed.array").size());
+    }
+
+    @Test
+    public void testDisableListDelimiter() throws Exception
+    {
+        PropertiesConfiguration pc1 = new PropertiesConfiguration(testProperties);
+        assertEquals(4, pc1.getList("test.mixed.array").size());
+
+        PropertiesConfiguration pc2 = new PropertiesConfiguration();
+        pc2.setDelimiterParsingDisabled(true);
+        pc2.setFileName(testProperties);
+        pc2.load();
+        assertEquals(2, pc2.getList("test.mixed.array").size());
+    }
+
+    /**
+     * Tests escaping of an end of line with a backslash.
+     */
+    @Test
+    public void testNewLineEscaping()
+    {
+        List<Object> list = conf.getList("test.path");
+        assertEquals(3, list.size());
+        assertEquals("C:\\path1\\", list.get(0));
+        assertEquals("C:\\path2\\", list.get(1));
+        assertEquals("C:\\path3\\complex\\test\\", list.get(2));
+    }
+
+    /**
+     * Tests if included files are loaded when the source lies in the class path.
+     */
+    @Test
+    public void testLoadIncludeFromClassPath() throws ConfigurationException
+    {
+        conf = new PropertiesConfiguration("test.properties");
+        assertEquals("true", conf.getString("include.loaded"));
+    }
+
+    /**
+     * Test if the lines starting with # or ! are properly ignored.
+     */
+    @Test
+    public void testComment() {
+        assertFalse("comment line starting with '#' parsed as a property", conf.containsKey("#comment"));
+        assertFalse("comment line starting with '!' parsed as a property", conf.containsKey("!comment"));
+    }
+
+    /**
+     * Check that key/value separators can be part of a key.
+     */
+    @Test
+    public void testEscapedKeyValueSeparator()
+    {
+        assertEquals("Escaped separator '=' not supported in keys", "foo", conf.getProperty("test.separator=in.key"));
+        assertEquals("Escaped separator ':' not supported in keys", "bar", conf.getProperty("test.separator:in.key"));
+        assertEquals("Escaped separator '\\t' not supported in keys", "foo", conf.getProperty("test.separator\tin.key"));
+        assertEquals("Escaped separator '\\f' not supported in keys", "bar", conf.getProperty("test.separator\fin.key"));
+        assertEquals("Escaped separator ' ' not supported in keys"  , "foo", conf.getProperty("test.separator in.key"));
+    }
+
+    /**
+     * Test all acceptable key/value separators ('=', ':' or white spaces).
+     */
+    @Test
+    public void testKeyValueSeparators() {
+        assertEquals("equal separator not properly parsed",      "foo", conf.getProperty("test.separator.equal"));
+        assertEquals("colon separator not properly parsed",      "foo", conf.getProperty("test.separator.colon"));
+        assertEquals("tab separator not properly parsed",        "foo", conf.getProperty("test.separator.tab"));
+        assertEquals("formfeed separator not properly parsed",   "foo", conf.getProperty("test.separator.formfeed"));
+        assertEquals("whitespace separator not properly parsed", "foo", conf.getProperty("test.separator.whitespace"));
+    }
+
+    /**
+     * Tests including properties when they are loaded from a nested directory
+     * structure.
+     */
+    @SuppressWarnings("deprecation")
+    @Test
+    public void testIncludeInSubDir() throws ConfigurationException
+    {
+        ConfigurationFactory factory = new ConfigurationFactory("conf/testFactoryPropertiesInclude.xml");
+        Configuration config = factory.getConfiguration();
+        assertTrue(config.getBoolean("deeptest"));
+        assertTrue(config.getBoolean("deepinclude"));
+        assertFalse(config.containsKey("deeptestinvalid"));
+    }
+
+    /**
+     * Tests whether the correct line separator is used.
+     */
+    @Test
+    public void testLineSeparator() throws ConfigurationException
+    {
+        final String EOL = System.getProperty("line.separator");
+        conf = new PropertiesConfiguration();
+        conf.setHeader("My header");
+        conf.setProperty("prop", "value");
+
+        StringWriter out = new StringWriter();
+        conf.save(out);
+        String content = out.toString();
+        assertTrue("Header could not be found", content.indexOf("# My header"
+                + EOL + EOL) == 0);
+        assertTrue("Property could not be found", content.indexOf("prop = value" + EOL) > 0);
+    }
+
+    /**
+     * Tests what happens if a reloading strategy's <code>reloadingRequired()</code>
+     * implementation accesses methods of the configuration that in turn cause a reload.
+     */
+    @Test
+    public void testReentrantReload()
+    {
+        conf.setProperty("shouldReload", Boolean.FALSE);
+        conf.setReloadingStrategy(new FileChangedReloadingStrategy()
+        {
+            @Override
+            public boolean reloadingRequired()
+            {
+                return configuration.getBoolean("shouldReload");
+            }
+        });
+        assertFalse("Property has wrong value", conf.getBoolean("shouldReload"));
+    }
+
+    /**
+     * Tests accessing the layout object.
+     */
+    @Test
+    public void testGetLayout()
+    {
+        PropertiesConfigurationLayout layout = conf.getLayout();
+        assertNotNull("Layout is null", layout);
+        assertSame("Different object returned", layout, conf.getLayout());
+        conf.setLayout(null);
+        PropertiesConfigurationLayout layout2 = conf.getLayout();
+        assertNotNull("Layout 2 is null", layout2);
+        assertNotSame("Same object returned", layout, layout2);
+    }
+
+    /**
+     * Tests the propertyLoaded() method for a simple property.
+     */
+    @Test
+    public void testPropertyLoaded() throws ConfigurationException
+    {
+        DummyLayout layout = new DummyLayout(conf);
+        conf.setLayout(layout);
+        conf.propertyLoaded("layoutLoadedProperty", "yes");
+        assertEquals("Layout's load() was called", 0, layout.loadCalls);
+        assertEquals("Property not added", "yes", conf.getString("layoutLoadedProperty"));
+    }
+
+    /**
+     * Tests the propertyLoaded() method for an include property.
+     */
+    @Test
+    public void testPropertyLoadedInclude() throws ConfigurationException
+    {
+        DummyLayout layout = new DummyLayout(conf);
+        conf.setLayout(layout);
+        conf.propertyLoaded(PropertiesConfiguration.getInclude(), "testClasspath.properties,testEqual.properties");
+        assertEquals("Layout's load() was not correctly called", 2, layout.loadCalls);
+        assertFalse("Property was added", conf.containsKey(PropertiesConfiguration.getInclude()));
+    }
+
+    /**
+     * Tests propertyLoaded() for an include property, when includes are
+     * disabled.
+     */
+    @Test
+    public void testPropertyLoadedIncludeNotAllowed() throws ConfigurationException
+    {
+        DummyLayout layout = new DummyLayout(conf);
+        conf.setLayout(layout);
+        conf.setIncludesAllowed(false);
+        conf.propertyLoaded(PropertiesConfiguration.getInclude(), "testClassPath.properties,testEqual.properties");
+        assertEquals("Layout's load() was called", 0, layout.loadCalls);
+        assertFalse("Property was added", conf.containsKey(PropertiesConfiguration.getInclude()));
+    }
+
+    /**
+     * Tests whether comment lines are correctly detected.
+     */
+    @Test
+    public void testIsCommentLine()
+    {
+        assertTrue("Comment not detected", PropertiesConfiguration.isCommentLine("# a comment"));
+        assertTrue("Alternative comment not detected", PropertiesConfiguration.isCommentLine("! a comment"));
+        assertTrue("Comment with no space not detected", PropertiesConfiguration.isCommentLine("#a comment"));
+        assertTrue("Comment with leading space not detected", PropertiesConfiguration.isCommentLine("    ! a comment"));
+        assertFalse("Wrong comment", PropertiesConfiguration.isCommentLine("   a#comment"));
+    }
+
+    /**
+     * Tests whether a properties configuration can be successfully cloned. It
+     * is especially checked whether the layout object is taken into account.
+     */
+    @Test
+    public void testClone() throws ConfigurationException
+    {
+        PropertiesConfiguration copy = (PropertiesConfiguration) conf.clone();
+        assertNotSame("Copy has same layout object", conf.getLayout(), copy.getLayout());
+        assertEquals("Wrong number of event listeners for original", 1, conf.getConfigurationListeners().size());
+        assertEquals("Wrong number of event listeners for clone", 1, copy.getConfigurationListeners().size());
+        assertSame("Wrong event listener for original", conf.getLayout(), conf.getConfigurationListeners().iterator().next());
+        assertSame("Wrong event listener for clone", copy.getLayout(), copy.getConfigurationListeners().iterator().next());
+        StringWriter outConf = new StringWriter();
+        conf.save(outConf);
+        StringWriter outCopy = new StringWriter();
+        copy.save(outCopy);
+        assertEquals("Output from copy is different", outConf.toString(), outCopy.toString());
+    }
+
+    /**
+     * Tests the clone() method when no layout object exists yet.
+     */
+    @Test
+    public void testCloneNullLayout()
+    {
+        conf = new PropertiesConfiguration();
+        PropertiesConfiguration copy = (PropertiesConfiguration) conf.clone();
+        assertNotSame("Layout objects are the same", conf.getLayout(), copy.getLayout());
+    }
+
+    /**
+     * Tests saving a file-based configuration to a HTTP server.
+     */
+    @Test
+    public void testSaveToHTTPServerSuccess() throws Exception
+    {
+        MockHttpURLStreamHandler handler = new MockHttpURLStreamHandler(
+                HttpURLConnection.HTTP_OK, testSavePropertiesFile);
+        URL url = new URL(null, "http://jakarta.apache.org", handler);
+        conf.save(url);
+        MockHttpURLConnection con = handler.getMockConnection();
+        assertTrue("Wrong output flag", con.getDoOutput());
+        assertEquals("Wrong method", "PUT", con.getRequestMethod());
+
+        PropertiesConfiguration checkConfig = new PropertiesConfiguration(
+                testSavePropertiesFile);
+        ConfigurationAssert.assertEquals(conf, checkConfig);
+    }
+
+    /**
+     * Tests saving a file-based configuration to a HTTP server when the server
+     * reports a failure. This should cause an exception.
+     */
+    @Test
+    public void testSaveToHTTPServerFail() throws Exception
+    {
+        MockHttpURLStreamHandler handler = new MockHttpURLStreamHandler(
+                HttpURLConnection.HTTP_BAD_REQUEST, testSavePropertiesFile);
+        URL url = new URL(null, "http://jakarta.apache.org", handler);
+        try
+        {
+            conf.save(url);
+            fail("Response code was not checked!");
+        }
+        catch (ConfigurationException cex)
+        {
+            assertTrue("Wrong root cause: " + cex,
+                    cex.getCause() instanceof IOException);
+        }
+    }
+
+    /**
+     * Test the creation of a file containing a '#' in its name. This test is
+     * skipped on Java 1.3 as it always fails.
+     */
+    @Test
+    public void testFileWithSharpSymbol() throws Exception
+    {
+        if (SystemUtils.isJavaVersionAtLeast(1.4f))
+        {
+            File file = new File("target/sharp#1.properties");
+            file.createNewFile();
+
+            PropertiesConfiguration conf = new PropertiesConfiguration(file);
+            conf.save();
+
+            assertTrue("Missing file " + file, file.exists());
+        }
+    }
+
+    /**
+     * Tests initializing a properties configuration from a non existing file.
+     * There was a bug, which caused properties getting lost when later save()
+     * is called.
+     */
+    @Test
+    public void testInitFromNonExistingFile() throws ConfigurationException
+    {
+        final String testProperty = "test.successfull";
+        conf = new PropertiesConfiguration(testSavePropertiesFile);
+        conf.addProperty(testProperty, Boolean.TRUE);
+        conf.save();
+        PropertiesConfiguration checkConfig = new PropertiesConfiguration(
+                testSavePropertiesFile);
+        assertTrue("Test property not found", checkConfig
+                .getBoolean(testProperty));
+    }
+
+    /**
+     * Tests copying another configuration into the test configuration. This
+     * test ensures that the layout object is informed about the newly added
+     * properties.
+     */
+    @Test
+    public void testCopyAndSave() throws ConfigurationException
+    {
+        Configuration copyConf = setUpCopyConfig();
+        conf.copy(copyConf);
+        checkCopiedConfig(copyConf);
+    }
+
+    /**
+     * Tests appending a configuration to the test configuration. Again it has
+     * to be ensured that the layout object is correctly updated.
+     */
+    @Test
+    public void testAppendAndSave() throws ConfigurationException
+    {
+        Configuration copyConf = setUpCopyConfig();
+        conf.append(copyConf);
+        checkCopiedConfig(copyConf);
+    }
+
+    /**
+     * Tests adding properties through a DataConfiguration. This is related to
+     * CONFIGURATION-332.
+     */
+    @Test
+    public void testSaveWithDataConfig() throws ConfigurationException
+    {
+        conf = new PropertiesConfiguration(testSavePropertiesFile);
+        DataConfiguration dataConfig = new DataConfiguration(conf);
+        dataConfig.setProperty("foo", "bar");
+        assertEquals("Property not set", "bar", conf.getString("foo"));
+
+        conf.save();
+        PropertiesConfiguration config2 = new PropertiesConfiguration(
+                testSavePropertiesFile);
+        assertEquals("Property not saved", "bar", config2.getString("foo"));
+    }
+
+    /**
+     * Tests whether the correct default encoding is used when loading a
+     * properties file. This test is related to CONFIGURATION-345.
+     */
+    @Test
+    public void testLoadWithDefaultEncoding() throws ConfigurationException
+    {
+        class PropertiesConfigurationTestImpl extends PropertiesConfiguration
+        {
+            String loadEncoding;
+
+            public PropertiesConfigurationTestImpl(String fileName)
+                    throws ConfigurationException
+            {
+                super(fileName);
+            }
+
+            @Override
+            public void load(InputStream in, String encoding)
+                    throws ConfigurationException
+            {
+                loadEncoding = encoding;
+                super.load(in, encoding);
+            }
+        }
+
+        PropertiesConfigurationTestImpl testConf = new PropertiesConfigurationTestImpl(
+                testProperties);
+        assertEquals("Default encoding not used", "ISO-8859-1",
+                testConf.loadEncoding);
+    }
+
+    /**
+     * Tests whether a default IOFactory is set.
+     */
+    @Test
+    public void testGetIOFactoryDefault()
+    {
+        assertNotNull("No default IO factory", conf.getIOFactory());
+    }
+
+    /**
+     * Tests setting the IOFactory to null. This should cause an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetIOFactoryNull()
+    {
+        conf.setIOFactory(null);
+    }
+
+    /**
+     * Tests setting an IOFactory that uses a specialized reader.
+     */
+    @Test
+    public void testSetIOFactoryReader() throws ConfigurationException
+    {
+        final int propertyCount = 10;
+        conf.clear();
+        conf.setIOFactory(new PropertiesConfiguration.IOFactory()
+        {
+            public PropertiesConfiguration.PropertiesReader createPropertiesReader(
+                    Reader in, char delimiter)
+            {
+                return new PropertiesReaderTestImpl(in, delimiter,
+                        propertyCount);
+            }
+
+            public PropertiesConfiguration.PropertiesWriter createPropertiesWriter(
+                    Writer out, char delimiter)
+            {
+                throw new UnsupportedOperationException("Unexpected call!");
+            }
+        });
+        conf.load();
+        for (int i = 1; i <= propertyCount; i++)
+        {
+            assertEquals("Wrong property value at " + i, PROP_VALUE + i, conf
+                    .getString(PROP_NAME + i));
+        }
+    }
+
+    /**
+     * Tests setting an IOFactory that uses a specialized writer.
+     */
+    @Test
+    public void testSetIOFactoryWriter() throws ConfigurationException, IOException
+    {
+        final PropertiesWriterTestImpl testWriter = new PropertiesWriterTestImpl(',');
+        conf.setIOFactory(new PropertiesConfiguration.IOFactory()
+        {
+            public PropertiesConfiguration.PropertiesReader createPropertiesReader(
+                    Reader in, char delimiter)
+            {
+                throw new UnsupportedOperationException("Unexpected call!");
+            }
+
+            public PropertiesConfiguration.PropertiesWriter createPropertiesWriter(
+                    Writer out, char delimiter)
+            {
+                return testWriter;
+            }
+        });
+        conf.save(new StringWriter());
+        testWriter.close();
+        checkSavedConfig();
+    }
+
+    /**
+     * Tests that the property separators are retained when saving the
+     * configuration.
+     */
+    @Test
+    public void testKeepSeparators() throws ConfigurationException, IOException
+    {
+        conf.save(testSavePropertiesFile);
+        final String[] separatorTests = {
+                "test.separator.equal = foo", "test.separator.colon : foo",
+                "test.separator.tab\tfoo", "test.separator.whitespace foo",
+                "test.separator.no.space=foo"
+        };
+        Set<String> foundLines = new HashSet<String>();
+        BufferedReader in = new BufferedReader(new FileReader(
+                testSavePropertiesFile));
+        try
+        {
+            String s;
+            while ((s = in.readLine()) != null)
+            {
+                for (int i = 0; i < separatorTests.length; i++)
+                {
+                    if (separatorTests[i].equals(s))
+                    {
+                        foundLines.add(s);
+                    }
+                }
+            }
+        }
+        finally
+        {
+            in.close();
+        }
+        assertEquals("No all separators were found: " + foundLines,
+                separatorTests.length, foundLines.size());
+    }
+
+    /**
+     * Tests whether properties with slashes in their values can be saved. This
+     * test is related to CONFIGURATION-408.
+     */
+    @Test
+    public void testSlashEscaping() throws ConfigurationException
+    {
+        conf.setProperty(PROP_NAME, "http://www.apache.org");
+        StringWriter writer = new StringWriter();
+        conf.save(writer);
+        String s = writer.toString();
+        assertTrue("Value not found: " + s, s.indexOf(PROP_NAME
+                + " = http://www.apache.org") >= 0);
+    }
+
+    /**
+     * Tests whether backslashes are correctly handled if lists are parsed. This
+     * test is related to CONFIGURATION-418.
+     */
+    @Test
+    public void testBackslashEscapingInLists() throws Exception
+    {
+        checkBackslashList("share2");
+        checkBackslashList("share1");
+    }
+
+    /**
+     * Tests whether a list property is handled correctly if delimiter parsing
+     * is disabled. This test is related to CONFIGURATION-495.
+     */
+    @Test
+    public void testSetPropertyListWithDelimiterParsingDisabled()
+            throws ConfigurationException
+    {
+        String prop = "delimiterListProp";
+        conf.setDelimiterParsingDisabled(true);
+        List<String> list = Arrays.asList("val", "val2", "val3");
+        conf.setProperty(prop, list);
+        conf.setFile(testSavePropertiesFile);
+        conf.save();
+        conf.clear();
+        conf.load();
+        assertEquals("Wrong list property", list, conf.getProperty(prop));
+    }
+
+    /**
+     * Tests whether a footer comment is correctly read.
+     */
+    @Test
+    public void testReadFooterComment()
+    {
+        assertEquals("Wrong footer comment", "\n# This is a foot comment\n",
+                conf.getFooter());
+        assertEquals("Wrong footer comment from layout",
+                "\nThis is a foot comment\n", conf.getLayout()
+                        .getCanonicalFooterCooment(false));
+    }
+
+    /**
+     * Tests whether a footer comment is correctly written out.
+     */
+    @Test
+    public void testWriteFooterComment() throws ConfigurationException,
+            IOException
+    {
+        final String footer = "my footer";
+        conf.clear();
+        conf.setProperty(PROP_NAME, PROP_VALUE);
+        conf.setFooter(footer);
+        StringWriter out = new StringWriter();
+        conf.save(out);
+        assertEquals("Wrong result", PROP_NAME + " = " + PROP_VALUE + CR + "# "
+                + footer + CR, out.toString());
+    }
+
+    /**
+     * Helper method for testing the content of a list with elements that
+     * contain backslashes.
+     *
+     * @param key the key
+     */
+    private void checkBackslashList(String key)
+    {
+        Object prop = conf.getProperty("test." + key);
+        assertTrue("Not a list", prop instanceof List);
+        List<?> list = (List<?>) prop;
+        assertEquals("Wrong number of list elements", 2, list.size());
+        final String prefix = "\\\\" + key;
+        assertEquals("Wrong element 1", prefix + "a", list.get(0));
+        assertEquals("Wrong element 2", prefix + "b", list.get(1));
+    }
+
+    /**
+     * Creates a configuration that can be used for testing copy operations.
+     *
+     * @return the configuration to be copied
+     */
+    private Configuration setUpCopyConfig()
+    {
+        final int count = 25;
+        Configuration result = new BaseConfiguration();
+        for (int i = 1; i <= count; i++)
+        {
+            result.addProperty("copyKey" + i, "copyValue" + i);
+        }
+        return result;
+    }
+
+    /**
+     * Tests whether the data of a configuration that was copied into the test
+     * configuration is correctly saved.
+     *
+     * @param copyConf the copied configuration
+     * @throws ConfigurationException if an error occurs
+     */
+    private void checkCopiedConfig(Configuration copyConf)
+            throws ConfigurationException
+    {
+        conf.save(testSavePropertiesFile);
+        PropertiesConfiguration checkConf = new PropertiesConfiguration(
+                testSavePropertiesFile);
+        for (Iterator<String> it = copyConf.getKeys(); it.hasNext();)
+        {
+            String key = it.next();
+            assertEquals("Wrong value for property " + key, checkConf
+                    .getProperty(key), copyConf.getProperty(key));
+        }
+    }
+
+    /**
+     * A dummy layout implementation for checking whether certain methods are
+     * correctly called by the configuration.
+     */
+    static class DummyLayout extends PropertiesConfigurationLayout
+    {
+        /** Stores the number how often load() was called. */
+        public int loadCalls;
+
+        public DummyLayout(PropertiesConfiguration config)
+        {
+            super(config);
+        }
+
+        @Override
+        public void load(Reader in) throws ConfigurationException
+        {
+            loadCalls++;
+        }
+    }
+
+    /**
+     * A mock implementation of a HttpURLConnection used for testing saving to
+     * a HTTP server.
+     */
+    static class MockHttpURLConnection extends HttpURLConnection
+    {
+        /** The response code to return.*/
+        private final int returnCode;
+
+        /** The output file. The output stream will point to this file.*/
+        private final File outputFile;
+
+        protected MockHttpURLConnection(URL u, int respCode, File outFile)
+        {
+            super(u);
+            returnCode = respCode;
+            outputFile = outFile;
+        }
+
+        @Override
+        public void disconnect()
+        {
+        }
+
+        @Override
+        public boolean usingProxy()
+        {
+            return false;
+        }
+
+        @Override
+        public void connect() throws IOException
+        {
+        }
+
+        @Override
+        public int getResponseCode() throws IOException
+        {
+            return returnCode;
+        }
+
+        @Override
+        public OutputStream getOutputStream() throws IOException
+        {
+            return new FileOutputStream(outputFile);
+        }
+    }
+
+    /**
+     * A mock stream handler for working with the mock HttpURLConnection.
+     */
+    static class MockHttpURLStreamHandler extends URLStreamHandler
+    {
+        /** Stores the response code.*/
+        private int responseCode;
+
+        /** Stores the output file.*/
+        private File outputFile;
+
+        /** Stores the connection.*/
+        private MockHttpURLConnection connection;
+
+        public MockHttpURLStreamHandler(int respCode, File outFile)
+        {
+            responseCode = respCode;
+            outputFile = outFile;
+        }
+
+        public MockHttpURLConnection getMockConnection()
+        {
+            return connection;
+        }
+
+        @Override
+        protected URLConnection openConnection(URL u) throws IOException
+        {
+            connection = new MockHttpURLConnection(u, responseCode, outputFile);
+            return connection;
+        }
+    }
+
+    /**
+     * A test PropertiesReader for testing whether a custom reader can be
+     * injected. This implementation creates a configurable number of synthetic
+     * test properties.
+     */
+    private static class PropertiesReaderTestImpl extends
+            PropertiesConfiguration.PropertiesReader
+    {
+        /** The number of test properties to be created. */
+        private final int maxProperties;
+
+        /** The current number of properties. */
+        private int propertyCount;
+
+        public PropertiesReaderTestImpl(Reader reader, char listDelimiter,
+                int maxProps)
+        {
+            super(reader, listDelimiter);
+            assertEquals("Wrong list delimiter", ',', listDelimiter);
+            maxProperties = maxProps;
+        }
+
+        @Override
+        public String getPropertyName()
+        {
+            return PROP_NAME + propertyCount;
+        }
+
+        @Override
+        public String getPropertyValue()
+        {
+            return PROP_VALUE + propertyCount;
+        }
+
+        @Override
+        public boolean nextProperty() throws IOException
+        {
+            propertyCount++;
+            return propertyCount <= maxProperties;
+        }
+    }
+
+    /**
+     * A test PropertiesWriter for testing whether a custom writer can be
+     * injected. This implementation simply redirects all output into a test
+     * file.
+     */
+    private static class PropertiesWriterTestImpl extends
+            PropertiesConfiguration.PropertiesWriter
+    {
+        public PropertiesWriterTestImpl(char delimiter) throws IOException
+        {
+            super(new FileWriter(testSavePropertiesFile), delimiter);
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestPropertiesConfigurationLayout.java b/src/test/java/org/apache/commons/configuration/TestPropertiesConfigurationLayout.java
new file mode 100644
index 0000000..b85e9b0
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestPropertiesConfigurationLayout.java
@@ -0,0 +1,845 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Iterator;
+
+import org.apache.commons.configuration.event.ConfigurationEvent;
+import org.apache.commons.configuration.event.ConfigurationListener;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for PropertiesConfigurationLayout.
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: TestPropertiesConfigurationLayout.java 1534402 2013-10-21 22:35:52Z henning $
+ */
+public class TestPropertiesConfigurationLayout
+{
+    /** Constant for the line break character. */
+    private static final String CR = System.getProperty("line.separator");
+
+    /** Constant for the normalized line break character. */
+    private static final String CRNORM = "\n";
+
+    /** Constant for a test property key. */
+    static final String TEST_KEY = "myProperty";
+
+    /** Constant for a test comment. */
+    static final String TEST_COMMENT = "A comment for my test property";
+
+    /** Constant for a test property value. */
+    static final String TEST_VALUE = "myPropertyValue";
+
+    /** The layout object under test. */
+    PropertiesConfigurationLayout layout;
+
+    /** The associated configuration object. */
+    LayoutTestConfiguration config;
+
+    /** A properties builder that can be used for testing. */
+    PropertiesBuilder builder;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        config = new LayoutTestConfiguration();
+        layout = new PropertiesConfigurationLayout(config);
+        config.setLayout(layout);
+        builder = new PropertiesBuilder();
+    }
+
+    /**
+     * Tests a newly created instance.
+     */
+    @Test
+    public void testInit()
+    {
+        assertTrue("Object contains keys", layout.getKeys().isEmpty());
+        assertNull("Header comment not null", layout.getHeaderComment());
+        Iterator<ConfigurationListener> it = config.getConfigurationListeners().iterator();
+        assertTrue("No event listener registered", it.hasNext());
+        assertSame("Layout not registered as event listener", layout, it.next());
+        assertFalse("Multiple event listeners registered", it.hasNext());
+        assertSame("Configuration not stored", config, layout
+                .getConfiguration());
+        assertFalse("Force single line flag set", layout.isForceSingleLine());
+        assertNull("Got a global separator", layout.getGlobalSeparator());
+    }
+
+    /**
+     * Tests creating a layout object with a null configuration. This should
+     * cause an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInitNull()
+    {
+        new PropertiesConfigurationLayout(null);
+    }
+
+    /**
+     * Tests reading a simple properties file.
+     */
+    @Test
+    public void testReadSimple() throws ConfigurationException
+    {
+        builder.addComment(TEST_COMMENT);
+        builder.addProperty(TEST_KEY, TEST_VALUE);
+        layout.load(builder.getReader());
+        assertNull("A header comment was found", layout.getHeaderComment());
+        assertEquals("Wrong number of properties", 1, layout.getKeys().size());
+        assertTrue("Property not found", layout.getKeys().contains(TEST_KEY));
+        assertEquals("Comment not found", TEST_COMMENT, layout
+                .getCanonicalComment(TEST_KEY, false));
+        assertEquals("Wrong number of blanc lines", 0, layout
+                .getBlancLinesBefore(TEST_KEY));
+        assertTrue("Wrong single line flag", layout.isSingleLine(TEST_KEY));
+        assertEquals("Property not stored in config", TEST_VALUE, config
+                .getString(TEST_KEY));
+    }
+
+    /**
+     * Tests whether blanc lines before a property are correctly detected.
+     */
+    @Test
+    public void testBlancLines() throws ConfigurationException
+    {
+        builder.addProperty("prop", "value");
+        builder.addComment(null);
+        builder.addComment(null);
+        builder.addComment(TEST_COMMENT);
+        builder.addComment(null);
+        builder.addProperty(TEST_KEY, TEST_VALUE);
+        layout.load(builder.getReader());
+        assertEquals("Wrong number of blanc lines", 2, layout
+                .getBlancLinesBefore(TEST_KEY));
+        assertEquals("Wrong comment", TEST_COMMENT + CRNORM, layout
+                .getCanonicalComment(TEST_KEY, false));
+        assertEquals("Wrong property value", TEST_VALUE, config
+                .getString(TEST_KEY));
+    }
+
+    /**
+     * Tests the single line flag for a simple property definition.
+     */
+    @Test
+    public void testIsSingleLine() throws ConfigurationException
+    {
+        builder.addProperty(TEST_KEY, TEST_VALUE + "," + TEST_VALUE + "2");
+        layout.load(builder.getReader());
+        assertTrue("Wrong single line flag", layout.isSingleLine(TEST_KEY));
+        assertEquals("Wrong number of values", 2, config.getList(TEST_KEY)
+                .size());
+    }
+
+    /**
+     * Tests the single line flag if there are multiple property definitions.
+     */
+    @Test
+    public void testIsSingleLineMulti() throws ConfigurationException
+    {
+        builder.addProperty(TEST_KEY, TEST_VALUE);
+        builder.addProperty("anotherProp", "a value");
+        builder.addProperty(TEST_KEY, TEST_VALUE + "2");
+        layout.load(builder.getReader());
+        assertFalse("Wrong single line flag", layout.isSingleLine(TEST_KEY));
+        assertEquals("Wrong number of values", 2, config.getList(TEST_KEY)
+                .size());
+    }
+
+    /**
+     * Tests whether comments are combined for multiple occurrences.
+     */
+    @Test
+    public void testCombineComments() throws ConfigurationException
+    {
+        builder.addComment(TEST_COMMENT);
+        builder.addProperty(TEST_KEY, TEST_VALUE);
+        builder.addComment(null);
+        builder.addComment(TEST_COMMENT);
+        builder.addProperty(TEST_KEY, TEST_VALUE + "2");
+        layout.load(builder.getReader());
+        assertEquals("Wrong combined comment",
+                TEST_COMMENT + CRNORM + TEST_COMMENT, layout.getCanonicalComment(
+                        TEST_KEY, false));
+        assertEquals("Wrong combined blanc numbers", 0, layout
+                .getBlancLinesBefore(TEST_KEY));
+    }
+
+    /**
+     * Tests if a header comment is detected.
+     */
+    @Test
+    public void testHeaderComment() throws ConfigurationException
+    {
+        builder.addComment(TEST_COMMENT);
+        builder.addComment(null);
+        builder.addProperty(TEST_KEY, TEST_VALUE);
+        layout.load(builder.getReader());
+        assertEquals("Wrong header comment", TEST_COMMENT, layout
+                .getCanonicalHeaderComment(false));
+        assertNull("Wrong comment for property", layout.getCanonicalComment(
+                TEST_KEY, false));
+    }
+
+    /**
+     * Tests if a header comment containing blanc lines is correctly detected.
+     */
+    @Test
+    public void testHeaderCommentWithBlancs() throws ConfigurationException
+    {
+        builder.addComment(TEST_COMMENT);
+        builder.addComment(null);
+        builder.addComment(TEST_COMMENT);
+        builder.addComment(null);
+        builder.addProperty(TEST_KEY, TEST_VALUE);
+        layout.load(builder.getReader());
+        assertEquals("Wrong header comment", TEST_COMMENT + CRNORM + CRNORM
+                + TEST_COMMENT, layout.getCanonicalHeaderComment(false));
+        assertNull("Wrong comment for property", layout.getComment(TEST_KEY));
+    }
+
+    /**
+     * Tests if a header comment is correctly detected when it contains blanc
+     * lines and the first property has a comment, too.
+     */
+    @Test
+    public void testHeaderCommentWithBlancsAndPropComment()
+            throws ConfigurationException
+    {
+        builder.addComment(TEST_COMMENT);
+        builder.addComment(null);
+        builder.addComment(TEST_COMMENT);
+        builder.addComment(null);
+        builder.addComment(TEST_COMMENT);
+        builder.addProperty(TEST_KEY, TEST_VALUE);
+        layout.load(builder.getReader());
+        assertEquals("Wrong header comment", TEST_COMMENT + CRNORM + CRNORM
+                + TEST_COMMENT, layout.getCanonicalHeaderComment(false));
+        assertEquals("Wrong comment for property", TEST_COMMENT, layout
+                .getCanonicalComment(TEST_KEY, false));
+    }
+
+    /**
+     * Tests fetching a canonical header comment when no comment is set.
+     */
+    @Test
+    public void testHeaderCommentNull()
+    {
+        assertNull("No null comment with comment chars", layout
+                .getCanonicalHeaderComment(true));
+        assertNull("No null comment without comment chars", layout
+                .getCanonicalHeaderComment(false));
+    }
+
+    /**
+     * Tests if a property add event is correctly processed.
+     */
+    @Test
+    public void testEventAdd()
+    {
+        ConfigurationEvent event = new ConfigurationEvent(this,
+                AbstractConfiguration.EVENT_ADD_PROPERTY, TEST_KEY, TEST_VALUE,
+                false);
+        layout.configurationChanged(event);
+        assertTrue("Property not stored", layout.getKeys().contains(TEST_KEY));
+        assertEquals("Blanc lines before new property", 0, layout
+                .getBlancLinesBefore(TEST_KEY));
+        assertTrue("No single line property", layout.isSingleLine(TEST_KEY));
+        assertEquals("Wrong separator", " = ", layout.getSeparator(TEST_KEY));
+    }
+
+    /**
+     * Tests adding a property multiple time through an event. The property
+     * should then be a multi-line property.
+     */
+    @Test
+    public void testEventAddMultiple()
+    {
+        ConfigurationEvent event = new ConfigurationEvent(this,
+                AbstractConfiguration.EVENT_ADD_PROPERTY, TEST_KEY, TEST_VALUE,
+                false);
+        layout.configurationChanged(event);
+        layout.configurationChanged(event);
+        assertFalse("No multi-line property", layout.isSingleLine(TEST_KEY));
+    }
+
+    /**
+     * Tests if an add event is correctly processed if the affected property is
+     * already stored in the layout object.
+     */
+    @Test
+    public void testEventAddExisting() throws ConfigurationException
+    {
+        builder.addComment(TEST_COMMENT);
+        builder.addProperty(TEST_KEY, TEST_VALUE);
+        layout.load(builder.getReader());
+        ConfigurationEvent event = new ConfigurationEvent(this,
+                AbstractConfiguration.EVENT_ADD_PROPERTY, TEST_KEY, TEST_VALUE,
+                false);
+        layout.configurationChanged(event);
+        assertFalse("No multi-line property", layout.isSingleLine(TEST_KEY));
+        assertEquals("Comment was modified", TEST_COMMENT, layout
+                .getCanonicalComment(TEST_KEY, false));
+    }
+
+    /**
+     * Tests if a set property event for a non existing property is correctly
+     * handled.
+     */
+    @Test
+    public void testEventSetNonExisting()
+    {
+        ConfigurationEvent event = new ConfigurationEvent(this,
+                AbstractConfiguration.EVENT_SET_PROPERTY, TEST_KEY, TEST_VALUE,
+                false);
+        layout.configurationChanged(event);
+        assertTrue("New property was not found", layout.getKeys().contains(
+                TEST_KEY));
+    }
+
+    /**
+     * Tests if a property delete event is correctly processed.
+     */
+    @Test
+    public void testEventDelete()
+    {
+        ConfigurationEvent event = new ConfigurationEvent(this,
+                AbstractConfiguration.EVENT_ADD_PROPERTY, TEST_KEY, TEST_VALUE,
+                false);
+        layout.configurationChanged(event);
+        event = new ConfigurationEvent(this,
+                AbstractConfiguration.EVENT_CLEAR_PROPERTY, TEST_KEY, null,
+                false);
+        layout.configurationChanged(event);
+        assertFalse("Property still existing", layout.getKeys().contains(
+                TEST_KEY));
+    }
+
+    /**
+     * Tests if a clear event is correctly processed.
+     */
+    @Test
+    public void testEventClearConfig() throws Exception
+    {
+        fillLayout();
+        ConfigurationEvent event = new ConfigurationEvent(this,
+                AbstractConfiguration.EVENT_CLEAR, null, null, false);
+        layout.configurationChanged(event);
+        assertTrue("Keys not empty", layout.getKeys().isEmpty());
+        assertNull("Header comment was not reset", layout.getHeaderComment());
+    }
+
+    /**
+     * Tests if a before update event is correctly ignored.
+     */
+    @Test
+    public void testEventAddBefore()
+    {
+        ConfigurationEvent event = new ConfigurationEvent(this,
+                AbstractConfiguration.EVENT_ADD_PROPERTY, TEST_KEY, TEST_VALUE,
+                true);
+        layout.configurationChanged(event);
+        assertFalse("Property already stored", layout.getKeys().contains(
+                TEST_KEY));
+    }
+
+    /**
+     * Tests if a reload update is correctly processed.
+     */
+    @Test
+    public void testEventReload()
+    {
+        fillLayout();
+        ConfigurationEvent event = new ConfigurationEvent(this,
+                AbstractFileConfiguration.EVENT_RELOAD, null, null, true);
+        layout.configurationChanged(event);
+        assertTrue("Keys not empty", layout.getKeys().isEmpty());
+        assertNull("Header comment was not reset", layout.getHeaderComment());
+    }
+
+    /**
+     * Tests the event after a reload has been performed. This should be
+     * ignored.
+     */
+    @Test
+    public void testEventReloadAfter()
+    {
+        fillLayout();
+        ConfigurationEvent event = new ConfigurationEvent(this,
+                AbstractFileConfiguration.EVENT_RELOAD, null, null, false);
+        layout.configurationChanged(event);
+        assertFalse("Keys are empty", layout.getKeys().isEmpty());
+        assertNotNull("Header comment was reset", layout.getHeaderComment());
+    }
+
+    /**
+     * Tests a recursive load call.
+     */
+    @Test
+    public void testRecursiveLoadCall() throws ConfigurationException
+    {
+        PropertiesBuilder b = new PropertiesBuilder();
+        b.addComment("A nested header comment.");
+        b.addComment("With multiple lines");
+        b.addComment(null);
+        b.addComment("Second comment");
+        b.addProperty(TEST_KEY, TEST_VALUE);
+        b.addProperty(TEST_KEY + "2", TEST_VALUE + "2");
+        config.builder = b;
+
+        builder.addComment("Header comment");
+        builder.addComment(null);
+        builder.addComment(TEST_COMMENT);
+        builder.addProperty(TEST_KEY, TEST_VALUE);
+        builder.addComment("Include file");
+        builder.addProperty(PropertiesConfiguration.getInclude(), "test");
+
+        layout.load(builder.getReader());
+
+        assertEquals("Wrong header comment", "Header comment", layout
+                .getCanonicalHeaderComment(false));
+        assertFalse("Include property was stored", layout.getKeys().contains(
+                PropertiesConfiguration.getInclude()));
+        assertEquals("Wrong comment for property", TEST_COMMENT + CRNORM
+                + "A nested header comment." + CRNORM + "With multiple lines" + CRNORM
+                + CRNORM + "Second comment", layout.getCanonicalComment(TEST_KEY,
+                false));
+    }
+
+    /**
+     * Tests whether the output of the layout object is identical to the source
+     * file (at least for simple properties files).
+     */
+    @Test
+    public void testReadAndWrite() throws ConfigurationException
+    {
+        builder.addComment("This is my test properties file,");
+        builder.addComment("which contains a header comment.");
+        builder.addComment(null);
+        builder.addComment(TEST_COMMENT);
+        builder.addProperty(TEST_KEY, TEST_COMMENT);
+        builder.addComment(null);
+        builder.addComment(null);
+        builder.addComment("Another comment");
+        builder.addProperty("property", "and a value");
+        layout.load(builder.getReader());
+        checkLayoutString(builder.toString());
+    }
+
+    /**
+     * Tests if the content of the layout object is correctly written.
+     */
+    @Test
+    public void testSave() throws ConfigurationException
+    {
+        config.addProperty(TEST_KEY, TEST_VALUE);
+        layout.setComment(TEST_KEY, TEST_COMMENT);
+        config.addProperty(TEST_KEY, TEST_VALUE + "2");
+        config.addProperty("AnotherProperty", "AnotherValue");
+        config.addProperty("AnotherProperty", "3rdValue");
+        layout.setComment("AnotherProperty", "AnotherComment");
+        layout.setBlancLinesBefore("AnotherProperty", 2);
+        layout.setSingleLine("AnotherProperty", true);
+        layout.setHeaderComment("A header comment" + CRNORM + "for my properties");
+        checkLayoutString("# A header comment" + CR + "# for my properties"
+                + CR + CR + "# " + TEST_COMMENT + CR + TEST_KEY + " = "
+                + TEST_VALUE + CR + TEST_KEY + " = " + TEST_VALUE + "2" + CR
+                + CR + CR + "# AnotherComment" + CR
+                + "AnotherProperty = AnotherValue,3rdValue" + CR);
+    }
+
+    /**
+     * Tests the force single line flag.
+     */
+    @Test
+    public void testSaveForceSingleLine() throws ConfigurationException
+    {
+        config.setListDelimiter(';');
+        config.addProperty(TEST_KEY, TEST_VALUE);
+        config.addProperty(TEST_KEY, TEST_VALUE + "2");
+        config.addProperty("AnotherProperty", "value1;value2;value3");
+        layout.setComment(TEST_KEY, TEST_COMMENT);
+        layout.setForceSingleLine(true);
+        checkLayoutString("# " + TEST_COMMENT + CR + TEST_KEY + " = "
+                + TEST_VALUE + ';' + TEST_VALUE + "2" + CR
+                + "AnotherProperty = value1;value2;value3" + CR);
+    }
+
+    /**
+     * Tests the trimComment method.
+     */
+    @Test
+    public void testTrimComment()
+    {
+        assertEquals("Wrong trimmed comment", "This is a comment" + CR
+                + "that spans multiple" + CR + "lines in a" + CR
+                + " complex way.", PropertiesConfigurationLayout.trimComment(
+                "   # This is a comment" + CR + "that spans multiple" + CR
+                        + "!lines in a" + CR + " complex way.", false));
+    }
+
+    /**
+     * Tests trimming a comment with trailing CRs.
+     */
+    @Test
+    public void testTrimCommentTrainlingCR()
+    {
+        assertEquals("Wrong trimmed comment", "Comment with" + CR
+                + "trailing CR" + CR, PropertiesConfigurationLayout
+                .trimComment("Comment with" + CR + "! trailing CR" + CR, false));
+    }
+
+    /**
+     * Tests enforcing comment characters in a comment.
+     */
+    @Test
+    public void testTrimCommentFalse()
+    {
+        assertEquals("Wrong trimmed comment", "# Comment with" + CR
+                + " ! some mixed " + CR + "#comment" + CR + "# lines",
+                PropertiesConfigurationLayout.trimComment("Comment with" + CR
+                        + " ! some mixed " + CR + "#comment" + CR + "lines",
+                        true));
+    }
+
+    /**
+     * Tests accessing data for a property, which is not stored.
+     */
+    @Test
+    public void testGetNonExistingLayouData()
+    {
+        assertNull("A comment was found", layout.getComment("unknown"));
+        assertTrue("A multi-line property", layout.isSingleLine("unknown"));
+        assertEquals("Leading blanc lines", 0, layout
+                .getBlancLinesBefore("unknown"));
+    }
+
+    /**
+     * Tests accessing a property with a null key. This should throw an
+     * exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetNullLayouttData()
+    {
+        layout.setComment(null, TEST_COMMENT);
+    }
+
+    /**
+     * Tests resetting a comment.
+     */
+    @Test
+    public void testSetNullComment()
+    {
+        fillLayout();
+        layout.setComment(TEST_KEY, null);
+        assertNull("Comment was not reset", layout.getComment(TEST_KEY));
+    }
+
+    /**
+     * Tests saving when a comment for a non existing property is contained in
+     * the layout object. This comment should be ignored.
+     */
+    @Test
+    public void testSaveCommentForUnexistingProperty()
+            throws ConfigurationException
+    {
+        fillLayout();
+        layout.setComment("NonExistingKey", "NonExistingComment");
+        String output = getLayoutString();
+        assertTrue("Non existing key was found", output
+                .indexOf("NonExistingKey") < 0);
+        assertTrue("Non existing comment was found", output
+                .indexOf("NonExistingComment") < 0);
+    }
+
+    /**
+     * Tests saving an empty layout object.
+     */
+    @Test
+    public void testSaveEmptyLayout() throws ConfigurationException
+    {
+        checkLayoutString("");
+    }
+
+    /**
+     * Tests the copy constructor.
+     */
+    @Test
+    public void testInitCopy()
+    {
+        fillLayout();
+        PropertiesConfigurationLayout l2 = new PropertiesConfigurationLayout(
+                config, layout);
+        assertEquals("Wrong number of keys", layout.getKeys().size(), l2
+                .getKeys().size());
+        for (String key : layout.getKeys())
+        {
+            assertTrue("Key was not found: " + key, l2.getKeys().contains(key));
+        }
+        assertEquals("Wrong header comment", layout.getHeaderComment(),
+                l2.getHeaderComment());
+        assertEquals("Wrong footer comment", layout.getFooterComment(),
+                l2.getFooterComment());
+    }
+
+    /**
+     * Tests if the copy and the original are independent from each other.
+     */
+    @Test
+    public void testInitCopyModify()
+    {
+        fillLayout();
+        PropertiesConfigurationLayout l2 = new PropertiesConfigurationLayout(
+                config, layout);
+        assertEquals("Comments are not equal", layout.getComment(TEST_KEY), l2
+                .getComment(TEST_KEY));
+        layout.setComment(TEST_KEY, "A new comment");
+        assertEquals("Comment was changed", TEST_COMMENT, l2
+                .getCanonicalComment(TEST_KEY, false));
+        l2.setBlancLinesBefore(TEST_KEY, l2.getBlancLinesBefore(TEST_KEY) + 1);
+        assertFalse("Blanc lines do not differ", layout
+                .getBlancLinesBefore(TEST_KEY) == l2
+                .getBlancLinesBefore(TEST_KEY));
+    }
+
+    /**
+     * Tests changing the separator for a property.
+     */
+    @Test
+    public void testSetSeparator() throws ConfigurationException
+    {
+        config.addProperty(TEST_KEY, TEST_VALUE);
+        layout.setSeparator(TEST_KEY, ":");
+        checkLayoutString(TEST_KEY + ":" + TEST_VALUE + CR);
+    }
+
+    /**
+     * Tests setting the global separator. This separator should override the
+     * separators for all properties.
+     */
+    @Test
+    public void testSetGlobalSeparator() throws ConfigurationException
+    {
+        final String sep = "=";
+        config.addProperty(TEST_KEY, TEST_VALUE);
+        config.addProperty("key2", "value2");
+        layout.setSeparator(TEST_KEY, " : ");
+        layout.setGlobalSeparator(sep);
+        checkLayoutString(TEST_KEY + sep + TEST_VALUE + CR + "key2" + sep
+                + "value2" + CR);
+    }
+
+    /**
+     * Tests setting the line separator.
+     */
+    @Test
+    public void testSetLineSeparator() throws ConfigurationException
+    {
+        final String lf = CR + CR;
+        config.addProperty(TEST_KEY, TEST_VALUE);
+        layout.setBlancLinesBefore(TEST_KEY, 2);
+        layout.setComment(TEST_KEY, TEST_COMMENT);
+        layout.setHeaderComment("Header comment");
+        layout.setLineSeparator(lf);
+        checkLayoutString("# Header comment" + lf + lf + lf + lf + "# "
+                + TEST_COMMENT + lf + TEST_KEY + " = " + TEST_VALUE + lf);
+    }
+
+    /**
+     * Tests whether the line separator is also taken into account within
+     * comments.
+     */
+    @Test
+    public void testSetLineSeparatorInComments() throws ConfigurationException
+    {
+        final String lf = "<-\n";
+        config.addProperty(TEST_KEY, TEST_VALUE);
+        layout.setComment(TEST_KEY, TEST_COMMENT + "\nMore comment");
+        layout.setHeaderComment("Header\ncomment");
+        layout.setLineSeparator(lf);
+        checkLayoutString("# Header" + lf + "# comment" + lf + lf + "# "
+                + TEST_COMMENT + lf + "# More comment" + lf + TEST_KEY + " = "
+                + TEST_VALUE + lf);
+    }
+
+    /**
+     * Helper method for filling the layout object with some properties.
+     */
+    private void fillLayout()
+    {
+        builder.addComment("A header comment");
+        builder.addComment(null);
+        builder.addProperty("prop", "value");
+        builder.addComment(TEST_COMMENT);
+        builder.addProperty(TEST_KEY, TEST_VALUE);
+        builder.addProperty("anotherProp", "anotherValue");
+        builder.addComment("A footer comment");
+        try
+        {
+            layout.load(builder.getReader());
+        }
+        catch (ConfigurationException cex)
+        {
+            // should not happen
+            fail("Exception was thrown: " + cex);
+        }
+    }
+
+    /**
+     * Writes the layout's data into a string.
+     *
+     * @return the layout file's content as string
+     * @throws ConfigurationException if an error occurs
+     */
+    private String getLayoutString() throws ConfigurationException
+    {
+        StringWriter out = new StringWriter();
+        layout.save(out);
+        return out.toString();
+    }
+
+    /**
+     * Checks if the layout's output is correct.
+     *
+     * @param expected the expected result
+     * @throws ConfigurationException if an error occurs
+     */
+    private void checkLayoutString(String expected)
+            throws ConfigurationException
+    {
+        assertEquals("Wrong layout file content", expected, getLayoutString());
+    }
+
+    /**
+     * A helper class used for constructing test properties files.
+     */
+    static class PropertiesBuilder
+    {
+        /** A buffer for storing the data. */
+        private StringBuilder buf = new StringBuilder();
+
+        /** A counter for varying the comment character. */
+        private int commentCounter;
+
+        /**
+         * Adds a property to the simulated file.
+         *
+         * @param key the property key
+         * @param value the value
+         */
+        public void addProperty(String key, String value)
+        {
+            buf.append(key).append(" = ").append(value).append(CR);
+        }
+
+        /**
+         * Adds a comment line.
+         *
+         * @param s the comment (can be <b>null</b>, then a blanc line is
+         * added)
+         */
+        public void addComment(String s)
+        {
+            if (s != null)
+            {
+                if (commentCounter % 2 == 0)
+                {
+                    buf.append("# ");
+                }
+                else
+                {
+                    buf.append("! ");
+                }
+                buf.append(s);
+                commentCounter++;
+            }
+            buf.append(CR);
+        }
+
+        /**
+         * Returns a reader for the simulated properties.
+         *
+         * @return a reader
+         */
+        public Reader getReader()
+        {
+            return new StringReader(buf.toString());
+        }
+
+        /**
+         * Returns a string representation of the buffer's content.
+         *
+         * @return the buffer as string
+         */
+        @Override
+        public String toString()
+        {
+            return buf.toString();
+        }
+    }
+
+    /**
+     * A mock properties configuration implementation that is used to check
+     * whether some expected methods are called.
+     */
+    static class LayoutTestConfiguration extends PropertiesConfiguration
+    {
+        /** Stores a builder object. */
+        public PropertiesBuilder builder;
+
+        /**
+         * Simulates the propertyLoaded() callback. If a builder was set, a
+         * load() call on the layout is invoked.
+         */
+        @Override
+        boolean propertyLoaded(String key, String value)
+                throws ConfigurationException
+        {
+            if (builder == null)
+            {
+                return super.propertyLoaded(key, value);
+            }
+            else
+            {
+                if (PropertiesConfiguration.getInclude().equals(key))
+                {
+                    getLayout().load(builder.getReader());
+                    return false;
+                }
+                else
+                {
+                    return true;
+                }
+            }
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestPropertiesSequence.java b/src/test/java/org/apache/commons/configuration/TestPropertiesSequence.java
new file mode 100644
index 0000000..daa9fe4
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestPropertiesSequence.java
@@ -0,0 +1,146 @@
+package org.apache.commons.configuration;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.collections.IteratorUtils;
+import org.apache.commons.lang.StringUtils;
+import org.junit.Test;
+
+/**
+ * Test that the configuration factory returns keys in the same
+ * sequence as the properties configurator
+ *
+ * @version $Id: TestPropertiesSequence.java 1225011 2011-12-27 20:46:13Z oheger $
+ */
+ at SuppressWarnings("deprecation")
+public class TestPropertiesSequence
+{
+    @Test
+    public void testConfigurationValuesInSameOrderFromFile() throws Exception
+    {
+        String simpleConfigurationFile = ConfigurationAssert.getTestFile("testSequence.properties").getAbsolutePath();
+        String compositeConfigurationFile = ConfigurationAssert.getTestFile("testSequenceDigester.xml").getAbsolutePath();
+
+        Configuration simpleConfiguration = new PropertiesConfiguration(simpleConfigurationFile);
+
+        ConfigurationFactory configurationFactory = new ConfigurationFactory();
+        configurationFactory.setConfigurationFileName(compositeConfigurationFile);
+        Configuration compositeConfiguration = configurationFactory.getConfiguration();
+
+        Configuration a = simpleConfiguration.subset("prefix");
+        Configuration b = compositeConfiguration.subset("prefix");
+
+        List<?> keysSimpleConfiguration = IteratorUtils.toList(a.getKeys());
+        List<?> keysCompositeConfiguration = IteratorUtils.toList(b.getKeys());
+
+        assertTrue("Size:" + keysSimpleConfiguration.size(), keysSimpleConfiguration.size() > 0);
+        assertEquals(keysSimpleConfiguration.size(), keysCompositeConfiguration.size());
+
+        for (int i = 0; i < keysSimpleConfiguration.size(); i++)
+        {
+            assertEquals(keysSimpleConfiguration.get(i), keysCompositeConfiguration.get(i));
+        }
+    }
+
+    @Test
+    public void testConfigurationValuesInSameOrderWithManualAdd() throws Exception
+    {
+        String simpleConfigurationFile = ConfigurationAssert.getTestFile("testSequence.properties").getAbsolutePath();
+        String compositeConfigurationFile = ConfigurationAssert.getTestFile("testSequenceDigester.xml").getAbsolutePath();
+
+        Configuration simpleConfiguration = new PropertiesConfiguration(simpleConfigurationFile);
+
+        ConfigurationFactory configurationFactory = new ConfigurationFactory();
+        configurationFactory.setConfigurationFileName(compositeConfigurationFile);
+        Configuration compositeConfiguration = configurationFactory.getConfiguration();
+
+        simpleConfiguration.setProperty("prefix.Co.test", Boolean.TRUE);
+        simpleConfiguration.setProperty("prefix.Av.test", Boolean.TRUE);
+
+        compositeConfiguration.setProperty("prefix.Co.test", Boolean.TRUE);
+        compositeConfiguration.setProperty("prefix.Av.test", Boolean.TRUE);
+
+        Configuration a = simpleConfiguration.subset("prefix");
+        Configuration b = compositeConfiguration.subset("prefix");
+
+        List<?> keysSimpleConfiguration = IteratorUtils.toList(a.getKeys());
+        List<?> keysCompositeConfiguration = IteratorUtils.toList(b.getKeys());
+
+        assertTrue("Size:" + keysSimpleConfiguration.size(), keysSimpleConfiguration.size() > 0);
+        assertEquals(keysSimpleConfiguration.size(), keysCompositeConfiguration.size());
+
+        for (int i = 0; i < keysSimpleConfiguration.size(); i++)
+        {
+            assertEquals(keysSimpleConfiguration.get(i), keysCompositeConfiguration.get(i));
+        }
+    }
+
+    @Test
+    public void testMappingInSameOrder() throws Exception
+    {
+        String simpleConfigurationFile = ConfigurationAssert.getTestFile("testSequence.properties").getAbsolutePath();
+        String compositeConfigurationFile = ConfigurationAssert.getTestFile("testSequenceDigester.xml").getAbsolutePath();
+
+        Configuration simpleConfiguration = new PropertiesConfiguration(simpleConfigurationFile);
+
+        ConfigurationFactory configurationFactory = new ConfigurationFactory();
+        configurationFactory.setConfigurationFileName(compositeConfigurationFile);
+        Configuration compositeConfiguration = configurationFactory.getConfiguration();
+
+        Configuration mapping = new BaseConfiguration();
+        Configuration mapping2 = new BaseConfiguration();
+
+        for (Iterator<String> keys = simpleConfiguration.getKeys(); keys.hasNext();)
+        {
+            String key = keys.next();
+            String[] keyParts = StringUtils.split(key, ".");
+
+            if ((keyParts.length == 3) && keyParts[0].equals("prefix") && keyParts[2].equals("postfix"))
+            {
+                String serviceKey = keyParts[1];
+
+                if (!mapping.containsKey(serviceKey))
+                {
+                    mapping.setProperty(serviceKey, simpleConfiguration.getString(key));
+                }
+            }
+        }
+
+        for (Iterator<String> keys = compositeConfiguration.getKeys(); keys.hasNext();)
+        {
+            String key = keys.next();
+            String[] keyParts = StringUtils.split(key, ".");
+
+            if ((keyParts.length == 3) && keyParts[0].equals("prefix") && keyParts[2].equals("postfix"))
+            {
+                String serviceKey = keyParts[1];
+
+                if (!mapping2.containsKey(serviceKey))
+                {
+                    mapping2.setProperty(serviceKey, compositeConfiguration.getString(key));
+                }
+            }
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestPropertyConverter.java b/src/test/java/org/apache/commons/configuration/TestPropertyConverter.java
new file mode 100644
index 0000000..59c73f2
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestPropertyConverter.java
@@ -0,0 +1,390 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.annotation.ElementType;
+import java.math.BigDecimal;
+import java.util.Iterator;
+import java.util.List;
+
+import org.junit.Test;
+
+/**
+ * Test class for PropertyConverter.
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: TestPropertyConverter.java 1534376 2013-10-21 21:14:18Z henning $
+ */
+public class TestPropertyConverter
+{
+    /** Constant for an enumeration class used by some tests. */
+    private static final Class<ElementType> ENUM_CLASS = ElementType.class;
+
+    @Test
+    public void testSplit()
+    {
+        String s = "abc, xyz , 123";
+        List<String> list = PropertyConverter.split(s, ',');
+
+        assertEquals("size", 3, list.size());
+        assertEquals("1st token for '" + s + "'", "abc", list.get(0));
+        assertEquals("2nd token for '" + s + "'", "xyz", list.get(1));
+        assertEquals("3rd token for '" + s + "'", "123", list.get(2));
+    }
+
+    @Test
+    public void testSplitNoTrim()
+    {
+        String s = "abc, xyz , 123";
+        List<String> list = PropertyConverter.split(s, ',', false);
+
+        assertEquals("size", 3, list.size());
+        assertEquals("1st token for '" + s + "'", "abc", list.get(0));
+        assertEquals("2nd token for '" + s + "'", " xyz ", list.get(1));
+        assertEquals("3rd token for '" + s + "'", " 123", list.get(2));
+    }
+
+    @Test
+    public void testSplitWithEscapedSeparator()
+    {
+        String s = "abc\\,xyz, 123";
+        List<String> list = PropertyConverter.split(s, ',');
+
+        assertEquals("size", 2, list.size());
+        assertEquals("1st token for '" + s + "'", "abc,xyz", list.get(0));
+        assertEquals("2nd token for '" + s + "'", "123", list.get(1));
+    }
+
+    @Test
+    public void testSplitEmptyValues()
+    {
+        String s = ",,";
+        List<String> list = PropertyConverter.split(s, ',');
+
+        assertEquals("size", 3, list.size());
+        assertEquals("1st token for '" + s + "'", "", list.get(0));
+        assertEquals("2nd token for '" + s + "'", "", list.get(1));
+        assertEquals("3rd token for '" + s + "'", "", list.get(2));
+    }
+
+    @Test
+    public void testSplitWithEndingSlash()
+    {
+        String s = "abc, xyz\\";
+        List<String> list = PropertyConverter.split(s, ',');
+
+        assertEquals("size", 2, list.size());
+        assertEquals("1st token for '" + s + "'", "abc", list.get(0));
+        assertEquals("2nd token for '" + s + "'", "xyz\\", list.get(1));
+    }
+
+    @Test
+    public void testSplitNull()
+    {
+        List<String> list = PropertyConverter.split(null, ',');
+        assertNotNull(list);
+        assertTrue(list.isEmpty());
+    }
+
+    /**
+     * Tests whether an escape character can be itself escaped.
+     */
+    @Test
+    public void testSplitEscapeEscapeChar()
+    {
+        List<String> list = PropertyConverter.split("C:\\Temp\\\\,xyz", ',');
+        assertEquals("Wrong list size", 2, list.size());
+        assertEquals("Wrong element 1", "C:\\Temp\\", list.get(0));
+        assertEquals("Wrong element 2", "xyz", list.get(1));
+    }
+
+    /**
+     * Tests whether delimiters are correctly escaped.
+     */
+    @Test
+    public void testEscapeDelimiters()
+    {
+        assertEquals("Wrong escaped delimiters",
+                "C:\\\\Temp\\\\\\,D:\\\\Data\\\\", PropertyConverter
+                        .escapeDelimiters("C:\\Temp\\,D:\\Data\\", ','));
+    }
+
+    /**
+     * Tests whether only the list delimiter can be escaped.
+     */
+    @Test
+    public void testEscapeListDelimiter()
+    {
+        assertEquals("Wrong escaped list delimiter", "C:\\Temp\\\\,D:\\Data\\",
+                PropertyConverter.escapeListDelimiter("C:\\Temp\\,D:\\Data\\",
+                        ','));
+    }
+
+    @Test
+    public void testToIterator()
+    {
+        int[] array = new int[]{1, 2, 3};
+
+        Iterator<?> it = PropertyConverter.toIterator(array, ',');
+
+        assertEquals("1st element", new Integer(1), it.next());
+        assertEquals("2nd element", new Integer(2), it.next());
+        assertEquals("3rd element", new Integer(3), it.next());
+    }
+
+    /**
+     * Tests the interpolation features.
+     */
+    @Test
+    public void testInterpolateString()
+    {
+        PropertiesConfiguration config = new PropertiesConfiguration();
+        config.addProperty("animal", "quick brown fox");
+        config.addProperty("target", "lazy dog");
+        assertEquals("Wrong interpolation",
+                "The quick brown fox jumps over the lazy dog.",
+                PropertyConverter.interpolate("The ${animal} jumps over the ${target}.", config));
+    }
+
+    /**
+     * Tests interpolation of an object. Here nothing should be substituted.
+     */
+    @Test
+    public void testInterpolateObject()
+    {
+        assertEquals("Object was not correctly interpolated", new Integer(42),
+                PropertyConverter.interpolate(new Integer(42), new PropertiesConfiguration()));
+    }
+
+    /**
+     * Tests complex interpolation where the variables' values contain in turn
+     * other variables.
+     */
+    @Test
+    public void testInterpolateRecursive()
+    {
+        PropertiesConfiguration config = new PropertiesConfiguration();
+        config.addProperty("animal", "${animal_attr} fox");
+        config.addProperty("target", "${target_attr} dog");
+        config.addProperty("animal_attr", "quick brown");
+        config.addProperty("target_attr", "lazy");
+        assertEquals("Wrong complex interpolation",
+                "The quick brown fox jumps over the lazy dog.",
+                PropertyConverter.interpolate("The ${animal} jumps over the ${target}.", config));
+    }
+
+    /**
+     * Tests an interpolation that leads to a cycle. This should throw an
+     * exception.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testCyclicInterpolation()
+    {
+        PropertiesConfiguration config = new PropertiesConfiguration();
+        config.addProperty("animal", "${animal_attr} ${species}");
+        config.addProperty("animal_attr", "quick brown");
+        config.addProperty("species", "${animal}");
+        PropertyConverter.interpolate("This is a ${animal}", config);
+    }
+
+    /**
+     * Tests interpolation if a variable is unknown. Then the variable won't be
+     * substituted.
+     */
+    @Test
+    public void testInterpolationUnknownVariable()
+    {
+        PropertiesConfiguration config = new PropertiesConfiguration();
+        config.addProperty("animal", "quick brown fox");
+        assertEquals("Wrong interpolation",
+                "The quick brown fox jumps over ${target}.",
+                PropertyConverter.interpolate("The ${animal} jumps over ${target}.", config));
+    }
+
+    /**
+     * Tests conversion to numbers when the passed in objects are already
+     * numbers.
+     */
+    @Test
+    public void testToNumberDirect()
+    {
+        Integer i = new Integer(42);
+        assertSame("Wrong integer", i, PropertyConverter.toNumber(i, Integer.class));
+        BigDecimal d = new BigDecimal("3.1415");
+        assertSame("Wrong BigDecimal", d, PropertyConverter.toNumber(d, Integer.class));
+    }
+
+    /**
+     * Tests conversion to numbers when the passed in objects have a compatible
+     * string representation.
+     */
+    @Test
+    public void testToNumberFromString()
+    {
+        assertEquals("Incorrect Integer value", new Integer(42), PropertyConverter.toNumber("42", Integer.class));
+        assertEquals("Incorrect Short value", new Short((short) 10), PropertyConverter.toNumber(new StringBuffer("10"), Short.class));
+    }
+
+    /**
+     * Tests conversion to numbers when the passed in objects are strings with
+     * prefixes for special radices.
+     */
+    @Test
+    public void testToNumberFromHexString()
+    {
+        Number n = PropertyConverter.toNumber("0x10", Integer.class);
+        assertEquals("Incorrect Integer value", 16, n.intValue());
+    }
+
+    /**
+     * Tests conversion to numbers when an invalid Hex value is passed in.
+     * This should cause an exception.
+     */
+    @Test(expected = ConversionException.class)
+    public void testToNumberFromInvalidHexString()
+    {
+        PropertyConverter.toNumber("0xNotAHexValue", Integer.class);
+    }
+
+    /**
+     * Tests conversion to numbers when the passed in objects are strings with
+     * prefixes for special radices.
+     */
+    @Test
+    public void testToNumberFromBinaryString()
+    {
+        Number n = PropertyConverter.toNumber("0b1111", Integer.class);
+        assertEquals("Incorrect Integer value", 15, n.intValue());
+    }
+
+    /**
+     * Tests conversion to numbers when an invalid binary value is passed in.
+     * This should cause an exception.
+     */
+    @Test(expected = ConversionException.class)
+    public void testToNumberFromInvalidBinaryString()
+    {
+        PropertyConverter.toNumber("0bNotABinValue", Integer.class);
+    }
+
+    /**
+     * Tests conversion to numbers when the passed in objects have no numeric
+     * String representation. This should cause an exception.
+     */
+    @Test(expected = ConversionException.class)
+    public void testToNumberFromInvalidString()
+    {
+        PropertyConverter.toNumber("Not a number", Byte.class);
+    }
+
+    /**
+     * Tests conversion to numbers when the passed in target class is invalid.
+     * This should cause an exception.
+     */
+    @Test(expected = ConversionException.class)
+    public void testToNumberWithInvalidClass()
+    {
+        PropertyConverter.toNumber("42", Object.class);
+    }
+
+    @Test
+    public void testToEnumFromEnum()
+    {
+        assertEquals(ElementType.METHOD, PropertyConverter.toEnum(ElementType.METHOD, ENUM_CLASS));
+    }
+
+    @Test
+    public void testToEnumFromString()
+    {
+        assertEquals(ElementType.METHOD, PropertyConverter.toEnum("METHOD", ENUM_CLASS));
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testToEnumFromInvalidString()
+    {
+        PropertyConverter.toEnum("FOO", ENUM_CLASS);
+    }
+
+    @Test
+    public void testToEnumFromNumber()
+    {
+        assertEquals(ElementType.METHOD, PropertyConverter.toEnum(
+                Integer.valueOf(ElementType.METHOD.ordinal()),
+                ENUM_CLASS));
+    }
+
+    @Test(expected = ConversionException.class)
+    public void testToEnumFromInvalidNumber()
+    {
+        PropertyConverter.toEnum(Integer.valueOf(-1), ENUM_CLASS);
+    }
+
+    /**
+     * Tests a trivial conversion: the value has already the desired type.
+     */
+    @Test
+    public void testToNoConversionNeeded()
+    {
+        String value = "testValue";
+        assertEquals("Wrong conversion result", value,
+                PropertyConverter.to(String.class, value, null));
+    }
+
+    /**
+     * Tests whether a conversion to character is possible.
+     */
+    @Test
+    public void testToCharSuccess()
+    {
+        assertEquals("Wrong conversion result", Character.valueOf('t'),
+                PropertyConverter.to(Character.class, "t", null));
+    }
+
+    /**
+     * Tests whether other objects implementing a toString() method can be
+     * converted to character.
+     */
+    @Test
+    public void testToCharViaToString()
+    {
+        Object value = new Object()
+        {
+            @Override
+            public String toString()
+            {
+                return "X";
+            }
+        };
+        assertEquals("Wrong conversion result", Character.valueOf('X'),
+                PropertyConverter.to(Character.TYPE, value, null));
+    }
+
+    /**
+     * Tests a failed conversion to character.
+     */
+    @Test(expected = ConversionException.class)
+    public void testToCharFailed()
+    {
+        PropertyConverter.to(Character.TYPE, "FF", null);
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestStrictConfigurationComparator.java b/src/test/java/org/apache/commons/configuration/TestStrictConfigurationComparator.java
new file mode 100644
index 0000000..c341b95
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestStrictConfigurationComparator.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+/**
+ * Tests the StrintConfigurationComparator class
+ *
+ * @version $Id: TestStrictConfigurationComparator.java 1225015 2011-12-27 21:01:59Z oheger $
+ */
+public class TestStrictConfigurationComparator
+{
+    /**
+     * The comparator.
+     */
+    protected ConfigurationComparator comparator = new StrictConfigurationComparator();
+
+    /**
+     * The first configuration.
+     */
+    protected Configuration configuration = new BaseConfiguration();
+
+    /**
+     * Tests the comparator.
+     */
+    @Test
+    public void testCompare()
+    {
+        // Identity comparison for empty configuration
+        assertTrue(
+            "Compare an empty configuration with itself",
+            comparator.compare(configuration, configuration));
+
+        configuration.setProperty("one", "1");
+        configuration.setProperty("two", "2");
+        configuration.setProperty("three", "3");
+
+        // Identify comparison for non-empty configuration
+        assertTrue(
+            "Compare a configuration with itself",
+            comparator.compare(configuration, configuration));
+
+        // Create the second configuration
+        Configuration other = new BaseConfiguration();
+        assertFalse(
+            "Compare a configuration with an empty one",
+            comparator.compare(configuration, other));
+
+        other.setProperty("one", "1");
+        other.setProperty("two", "2");
+        other.setProperty("three", "3");
+
+        // Two identical, non-empty configurations
+        assertTrue(
+            "Compare a configuration with an identical one",
+            comparator.compare(configuration, other));
+
+        other.setProperty("four", "4");
+        assertFalse(
+            "Compare our configuration with another that has an additional key mapping",
+            comparator.compare(configuration, other));
+
+        configuration.setProperty("four", "4");
+        assertTrue(
+            "Compare our configuration with another that is identical",
+            comparator.compare(configuration, other));
+    }
+
+    @Test
+    public void testCompareNull()
+    {
+        assertTrue(comparator.compare(null, null));
+        assertFalse(comparator.compare(configuration, null));
+        assertFalse(comparator.compare(null, configuration));
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestSubnodeConfiguration.java b/src/test/java/org/apache/commons/configuration/TestSubnodeConfiguration.java
new file mode 100644
index 0000000..1a3c17a
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestSubnodeConfiguration.java
@@ -0,0 +1,625 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.configuration.event.ConfigurationEvent;
+import org.apache.commons.configuration.event.ConfigurationListener;
+import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
+import org.apache.commons.configuration.reloading.FileAlwaysReloadingStrategy;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
+import org.apache.commons.lang.text.StrLookup;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Test case for SubnodeConfiguration.
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: TestSubnodeConfiguration.java 1225018 2011-12-27 21:14:59Z oheger $
+ */
+public class TestSubnodeConfiguration
+{
+    /** An array with names of tables (test data). */
+    private static final String[] TABLE_NAMES =
+    { "documents", "users" };
+
+    /** An array with the fields of the test tables (test data). */
+    private static final String[][] TABLE_FIELDS =
+    {
+    { "docid", "docname", "author", "dateOfCreation", "version", "size" },
+    { "userid", "uname", "firstName", "lastName" } };
+
+    /** Constant for an updated table name.*/
+    private static final String NEW_TABLE_NAME = "newTable";
+
+    /** A helper object for creating temporary files. */
+    @Rule
+    public TemporaryFolder folder = new TemporaryFolder();
+
+    /** The parent configuration. */
+    HierarchicalConfiguration parent;
+
+    /** The subnode configuration to be tested. */
+    SubnodeConfiguration config;
+
+    /** Stores a counter for the created nodes. */
+    int nodeCounter;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        parent = setUpParentConfig();
+        nodeCounter = 0;
+    }
+
+    /**
+     * Tests creation of a subnode config.
+     */
+    @Test
+    public void testInitSubNodeConfig()
+    {
+        setUpSubnodeConfig();
+        assertSame("Wrong root node in subnode", getSubnodeRoot(parent), config
+                .getRoot());
+        assertSame("Wrong parent config", parent, config.getParent());
+    }
+
+    /**
+     * Tests constructing a subnode configuration with a null parent. This
+     * should cause an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInitSubNodeConfigWithNullParent()
+    {
+        config = new SubnodeConfiguration(null, getSubnodeRoot(parent));
+    }
+
+    /**
+     * Tests constructing a subnode configuration with a null root node. This
+     * should cause an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInitSubNodeConfigWithNullNode()
+    {
+        config = new SubnodeConfiguration(parent, null);
+    }
+
+    /**
+     * Tests if properties of the sub node can be accessed.
+     */
+    @Test
+    public void testGetProperties()
+    {
+        setUpSubnodeConfig();
+        assertEquals("Wrong table name", TABLE_NAMES[0], config
+                .getString("name"));
+        List<Object> fields = config.getList("fields.field.name");
+        assertEquals("Wrong number of fields", TABLE_FIELDS[0].length, fields
+                .size());
+        for (int i = 0; i < TABLE_FIELDS[0].length; i++)
+        {
+            assertEquals("Wrong field at position " + i, TABLE_FIELDS[0][i],
+                    fields.get(i));
+        }
+    }
+
+    /**
+     * Tests setting of properties in both the parent and the subnode
+     * configuration and whether the changes are visible to each other.
+     */
+    @Test
+    public void testSetProperty()
+    {
+        setUpSubnodeConfig();
+        config.setProperty(null, "testTable");
+        config.setProperty("name", TABLE_NAMES[0] + "_tested");
+        assertEquals("Root value was not set", "testTable", parent
+                .getString("tables.table(0)"));
+        assertEquals("Table name was not changed", TABLE_NAMES[0] + "_tested",
+                parent.getString("tables.table(0).name"));
+
+        parent.setProperty("tables.table(0).fields.field(1).name", "testField");
+        assertEquals("Field name was not changed", "testField", config
+                .getString("fields.field(1).name"));
+    }
+
+    /**
+     * Tests adding of properties.
+     */
+    @Test
+    public void testAddProperty()
+    {
+        setUpSubnodeConfig();
+        config.addProperty("[@table-type]", "test");
+        assertEquals("parent.createNode() was not called", 1, nodeCounter);
+        assertEquals("Attribute not set", "test", parent
+                .getString("tables.table(0)[@table-type]"));
+
+        parent.addProperty("tables.table(0).fields.field(-1).name", "newField");
+        List<Object> fields = config.getList("fields.field.name");
+        assertEquals("New field was not added", TABLE_FIELDS[0].length + 1,
+                fields.size());
+        assertEquals("Wrong last field", "newField", fields
+                .get(fields.size() - 1));
+    }
+
+    /**
+     * Tests listing the defined keys.
+     */
+    @Test
+    public void testGetKeys()
+    {
+        setUpSubnodeConfig();
+        Set<String> keys = new HashSet<String>();
+        CollectionUtils.addAll(keys, config.getKeys());
+        assertEquals("Incorrect number of keys", 2, keys.size());
+        assertTrue("Key 1 not contained", keys.contains("name"));
+        assertTrue("Key 2 not contained", keys.contains("fields.field.name"));
+    }
+
+    /**
+     * Tests setting the exception on missing flag. The subnode config obtains
+     * this flag from its parent.
+     */
+    @Test(expected = NoSuchElementException.class)
+    public void testSetThrowExceptionOnMissing()
+    {
+        parent.setThrowExceptionOnMissing(true);
+        setUpSubnodeConfig();
+        assertTrue("Exception flag not fetchted from parent", config
+                .isThrowExceptionOnMissing());
+        config.getString("non existing key");
+    }
+
+    /**
+     * Tests whether the exception flag can be set independently from the parent.
+     */
+    @Test
+    public void testSetThrowExceptionOnMissingAffectsParent()
+    {
+        parent.setThrowExceptionOnMissing(true);
+        setUpSubnodeConfig();
+        config.setThrowExceptionOnMissing(false);
+        assertTrue("Exception flag reset on parent", parent
+                .isThrowExceptionOnMissing());
+    }
+
+    /**
+     * Tests handling of the delimiter parsing disabled flag. This is shared
+     * with the parent, too.
+     */
+    @Test
+    public void testSetDelimiterParsingDisabled()
+    {
+        parent.setDelimiterParsingDisabled(true);
+        setUpSubnodeConfig();
+        parent.setDelimiterParsingDisabled(false);
+        assertTrue("Delimiter parsing flag was not received from parent",
+                config.isDelimiterParsingDisabled());
+        config.addProperty("newProp", "test1,test2,test3");
+        assertEquals("New property was splitted", "test1,test2,test3", parent
+                .getString("tables.table(0).newProp"));
+        parent.setDelimiterParsingDisabled(true);
+        config.setDelimiterParsingDisabled(false);
+        assertTrue("Delimiter parsing flag was reset on parent", parent
+                .isDelimiterParsingDisabled());
+    }
+
+    /**
+     * Tests manipulating the list delimiter. This piece of data is derived from
+     * the parent.
+     */
+    @Test
+    public void testSetListDelimiter()
+    {
+        parent.setListDelimiter('/');
+        setUpSubnodeConfig();
+        parent.setListDelimiter(';');
+        assertEquals("List delimiter not obtained from parent", '/', config
+                .getListDelimiter());
+        config.addProperty("newProp", "test1,test2/test3");
+        assertEquals("List was incorrectly splitted", "test1,test2", parent
+                .getString("tables.table(0).newProp"));
+        config.setListDelimiter(',');
+        assertEquals("List delimiter changed on parent", ';', parent
+                .getListDelimiter());
+    }
+
+    /**
+     * Tests changing the expression engine.
+     */
+    @Test
+    public void testSetExpressionEngine()
+    {
+        parent.setExpressionEngine(new XPathExpressionEngine());
+        setUpSubnodeConfig();
+        assertEquals("Wrong field name", TABLE_FIELDS[0][1], config
+                .getString("fields/field[2]/name"));
+        Set<String> keys = new HashSet<String>();
+        CollectionUtils.addAll(keys, config.getKeys());
+        assertEquals("Wrong number of keys", 2, keys.size());
+        assertTrue("Key 1 not contained", keys.contains("name"));
+        assertTrue("Key 2 not contained", keys.contains("fields/field/name"));
+        config.setExpressionEngine(null);
+        assertTrue("Expression engine reset on parent", parent
+                .getExpressionEngine() instanceof XPathExpressionEngine);
+    }
+
+    /**
+     * Tests the configurationAt() method.
+     */
+    @Test
+    public void testConfiguarationAt()
+    {
+        setUpSubnodeConfig();
+        SubnodeConfiguration sub2 = config
+                .configurationAt("fields.field(1)");
+        assertEquals("Wrong value of property", TABLE_FIELDS[0][1], sub2
+                .getString("name"));
+        assertEquals("Wrong parent", config.getParent(), sub2.getParent());
+    }
+
+    /**
+     * Tests interpolation features. The subnode config should use its parent
+     * for interpolation.
+     */
+    @Test
+    public void testInterpolation()
+    {
+        parent.addProperty("tablespaces.tablespace.name", "default");
+        parent.addProperty("tablespaces.tablespace(-1).name", "test");
+        parent.addProperty("tables.table(0).tablespace",
+                "${tablespaces.tablespace(0).name}");
+        assertEquals("Wrong interpolated tablespace", "default", parent
+                .getString("tables.table(0).tablespace"));
+
+        setUpSubnodeConfig();
+        assertEquals("Wrong interpolated tablespace in subnode", "default",
+                config.getString("tablespace"));
+    }
+
+    /**
+     * An additional test for interpolation when the configurationAt() method is
+     * involved.
+     */
+    @Test
+    public void testInterpolationFromConfigurationAt()
+    {
+        parent.addProperty("base.dir", "/home/foo");
+        parent.addProperty("test.absolute.dir.dir1", "${base.dir}/path1");
+        parent.addProperty("test.absolute.dir.dir2", "${base.dir}/path2");
+        parent.addProperty("test.absolute.dir.dir3", "${base.dir}/path3");
+
+        Configuration sub = parent.configurationAt("test.absolute.dir");
+        for (int i = 1; i < 4; i++)
+        {
+            assertEquals("Wrong interpolation in parent", "/home/foo/path" + i,
+                    parent.getString("test.absolute.dir.dir" + i));
+            assertEquals("Wrong interpolation in subnode",
+                    "/home/foo/path" + i, sub.getString("dir" + i));
+        }
+    }
+
+    /**
+     * An additional test for interpolation when the configurationAt() method is
+     * involved for a local interpolation.
+     */
+    @Test
+    public void testLocalInterpolationFromConfigurationAt()
+    {
+        parent.addProperty("base.dir", "/home/foo");
+        parent.addProperty("test.absolute.dir.dir1", "${base.dir}/path1");
+        parent.addProperty("test.absolute.dir.dir2", "${dir1}");
+
+        Configuration sub = parent.configurationAt("test.absolute.dir");
+        assertEquals("Wrong interpolation in subnode",
+            "/home/foo/path1", sub.getString("dir1"));
+        assertEquals("Wrong local interpolation in subnode",
+            "/home/foo/path1", sub.getString("dir2"));
+    }
+
+    /**
+     * Tests manipulating the interpolator.
+     */
+    @Test
+    public void testInterpolator()
+    {
+        parent.addProperty("tablespaces.tablespace.name", "default");
+        parent.addProperty("tablespaces.tablespace(-1).name", "test");
+
+        setUpSubnodeConfig();
+        InterpolationTestHelper.testGetInterpolator(config);
+    }
+
+    @Test
+    public void testLocalLookupsInInterpolatorAreInherited() {
+        parent.addProperty("tablespaces.tablespace.name", "default");
+        parent.addProperty("tablespaces.tablespace(-1).name", "test");
+        parent.addProperty("tables.table(0).var", "${brackets:x}");
+
+        ConfigurationInterpolator interpolator = parent.getInterpolator();
+        interpolator.registerLookup("brackets", new StrLookup(){
+
+            @Override
+            public String lookup(String key) {
+                return "(" + key +")";
+            }
+
+        });
+        setUpSubnodeConfig();
+        assertEquals("Local lookup was not inherited", "(x)", config.getString("var", ""));
+    }
+
+    /**
+     * Tests a reload operation for the parent configuration when the subnode
+     * configuration does not support reloads. Then the new value should not be
+     * detected.
+     */
+    @Test
+    public void testParentReloadNotSupported() throws ConfigurationException
+    {
+        Configuration c = setUpReloadTest(false);
+        assertEquals("Name changed in sub config", TABLE_NAMES[1], config
+                .getString("name"));
+        assertEquals("Name not changed in parent", NEW_TABLE_NAME, c
+                .getString("tables.table(1).name"));
+    }
+
+    /**
+     * Tests a reload operation for the parent configuration when the subnode
+     * configuration does support reloads. The new value should be returned.
+     */
+    @Test
+    public void testParentReloadSupported() throws ConfigurationException
+    {
+        Configuration c = setUpReloadTest(true);
+        assertEquals("Name not changed in sub config", NEW_TABLE_NAME, config
+                .getString("name"));
+        assertEquals("Name not changed in parent", NEW_TABLE_NAME, c
+                .getString("tables.table(1).name"));
+    }
+
+    /**
+     * Tests whether events are fired if a change of the parent is detected.
+     */
+    @Test
+    public void testParentReloadEvents() throws ConfigurationException
+    {
+        setUpReloadTest(true);
+        ConfigurationListenerTestImpl l = new ConfigurationListenerTestImpl();
+        config.addConfigurationListener(l);
+        config.getString("name");
+        assertEquals("Wrong number of events", 2, l.events.size());
+        boolean before = true;
+        for (ConfigurationEvent e : l.events)
+        {
+            assertEquals("Wrong configuration", config, e.getSource());
+            assertEquals("Wrong event type",
+                    HierarchicalConfiguration.EVENT_SUBNODE_CHANGED, e
+                            .getType());
+            assertNull("Got a property name", e.getPropertyName());
+            assertNull("Got a property value", e.getPropertyValue());
+            assertEquals("Wrong before flag", before, e.isBeforeUpdate());
+            before = !before;
+        }
+    }
+
+    /**
+     * Tests a reload operation for the parent configuration when the subnode
+     * configuration is aware of reloads, and the parent configuration is
+     * accessed first. The new value should be returned.
+     */
+    @Test
+    public void testParentReloadSupportAccessParent()
+            throws ConfigurationException
+    {
+        Configuration c = setUpReloadTest(true);
+        assertEquals("Name not changed in parent", NEW_TABLE_NAME, c
+                .getString("tables.table(1).name"));
+        assertEquals("Name not changed in sub config", NEW_TABLE_NAME, config
+                .getString("name"));
+    }
+
+    /**
+     * Tests whether reloads work with sub subnode configurations.
+     */
+    @Test
+    public void testParentReloadSubSubnode() throws ConfigurationException
+    {
+        setUpReloadTest(true);
+        SubnodeConfiguration sub = config.configurationAt("fields", true);
+        assertEquals("Wrong subnode key", "tables.table(1).fields", sub
+                .getSubnodeKey());
+        assertEquals("Changed field not detected", "newField", sub
+                .getString("field(0).name"));
+    }
+
+    /**
+     * Tests creating a sub sub config when the sub config is not aware of
+     * changes. Then the sub sub config shouldn't be either.
+     */
+    @Test
+    public void testParentReloadSubSubnodeNoChangeSupport()
+            throws ConfigurationException
+    {
+        setUpReloadTest(false);
+        SubnodeConfiguration sub = config.configurationAt("fields", true);
+        assertNull("Sub sub config is attached to parent", sub.getSubnodeKey());
+        assertEquals("Changed field name returned", TABLE_FIELDS[1][0], sub
+                .getString("field(0).name"));
+    }
+
+    /**
+     * Prepares a test for a reload operation.
+     *
+     * @param supportReload a flag whether the subnode configuration should
+     * support reload operations
+     * @return the parent configuration that can be used for testing
+     * @throws ConfigurationException if an error occurs
+     */
+    private XMLConfiguration setUpReloadTest(boolean supportReload)
+            throws ConfigurationException
+    {
+        try
+        {
+            File testFile = folder.newFile();
+            XMLConfiguration xmlConf = new XMLConfiguration(parent);
+            xmlConf.setFile(testFile);
+            xmlConf.save();
+            config = xmlConf.configurationAt("tables.table(1)", supportReload);
+            assertEquals("Wrong table name", TABLE_NAMES[1],
+                    config.getString("name"));
+            xmlConf.setReloadingStrategy(new FileAlwaysReloadingStrategy());
+            // Now change the configuration file
+            XMLConfiguration confUpdate = new XMLConfiguration(testFile);
+            confUpdate.setProperty("tables.table(1).name", NEW_TABLE_NAME);
+            confUpdate.setProperty("tables.table(1).fields.field(0).name",
+                    "newField");
+            confUpdate.save();
+            return xmlConf;
+        }
+        catch (IOException ioex)
+        {
+            throw new ConfigurationException(ioex);
+        }
+    }
+
+    /**
+     * Tests a manipulation of the parent configuration that causes the subnode
+     * configuration to become invalid. In this case the sub config should be
+     * detached and keep its old values.
+     */
+    @Test
+    public void testParentChangeDetach()
+    {
+        final String key = "tables.table(1)";
+        config = parent.configurationAt(key, true);
+        assertEquals("Wrong subnode key", key, config.getSubnodeKey());
+        assertEquals("Wrong table name", TABLE_NAMES[1], config
+                .getString("name"));
+        parent.clearTree(key);
+        assertEquals("Wrong table name after change", TABLE_NAMES[1], config
+                .getString("name"));
+        assertNull("Sub config was not detached", config.getSubnodeKey());
+    }
+
+    /**
+     * Tests detaching a subnode configuration when an exception is thrown
+     * during reconstruction. This can happen e.g. if the expression engine is
+     * changed for the parent.
+     */
+    @Test
+    public void testParentChangeDetatchException()
+    {
+        config = parent.configurationAt("tables.table(1)", true);
+        parent.setExpressionEngine(new XPathExpressionEngine());
+        assertEquals("Wrong name of table", TABLE_NAMES[1], config
+                .getString("name"));
+        assertNull("Sub config was not detached", config.getSubnodeKey());
+    }
+
+    /**
+     * Initializes the parent configuration. This method creates the typical
+     * structure of tables and fields nodes.
+     *
+     * @return the parent configuration
+     */
+    protected HierarchicalConfiguration setUpParentConfig()
+    {
+        HierarchicalConfiguration conf = new HierarchicalConfiguration()
+        {
+            /**
+             * Serial version UID.
+             */
+            private static final long serialVersionUID = 1L;
+
+            // Provide a special implementation of createNode() to check
+            // if it is called by the subnode config
+            @Override
+            protected Node createNode(String name)
+            {
+                nodeCounter++;
+                return super.createNode(name);
+            }
+        };
+        for (int i = 0; i < TABLE_NAMES.length; i++)
+        {
+            conf.addProperty("tables.table(-1).name", TABLE_NAMES[i]);
+            for (int j = 0; j < TABLE_FIELDS[i].length; j++)
+            {
+                conf.addProperty("tables.table.fields.field(-1).name",
+                        TABLE_FIELDS[i][j]);
+            }
+        }
+        return conf;
+    }
+
+    /**
+     * Returns the root node for the subnode config. This method returns the
+     * first table node.
+     *
+     * @param conf the parent config
+     * @return the root node for the subnode config
+     */
+    protected ConfigurationNode getSubnodeRoot(HierarchicalConfiguration conf)
+    {
+        ConfigurationNode root = conf.getRoot();
+        return root.getChild(0).getChild(0);
+    }
+
+    /**
+     * Performs a standard initialization of the subnode config to test.
+     */
+    protected void setUpSubnodeConfig()
+    {
+        config = new SubnodeConfiguration(parent, getSubnodeRoot(parent));
+    }
+
+    /**
+     * A specialized configuration listener for testing whether the expected
+     * events are fired.
+     */
+    private static class ConfigurationListenerTestImpl implements ConfigurationListener
+    {
+        /** Stores the events received.*/
+        final List<ConfigurationEvent> events = new ArrayList<ConfigurationEvent>();
+
+        public void configurationChanged(ConfigurationEvent event)
+        {
+            events.add(event);
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestSubsetConfiguration.java b/src/test/java/org/apache/commons/configuration/TestSubsetConfiguration.java
new file mode 100644
index 0000000..9365366
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestSubsetConfiguration.java
@@ -0,0 +1,352 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
+import org.apache.commons.lang.text.StrLookup;
+import org.junit.Test;
+
+/**
+ * Test case for the {@link SubsetConfiguration} class.
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: TestSubsetConfiguration.java 1225019 2011-12-27 21:18:54Z oheger $
+ */
+public class TestSubsetConfiguration
+{
+    static final String TEST_DIR = ConfigurationAssert.TEST_DIR_NAME;
+    static final String TEST_FILE = "testDigesterConfiguration2.xml";
+
+    @Test
+    public void testGetProperty()
+    {
+        Configuration conf = new BaseConfiguration();
+        conf.setProperty("test.key1", "value1");
+        conf.setProperty("testing.key2", "value1");
+
+        Configuration subset = new SubsetConfiguration(conf, "test", ".");
+        assertFalse("the subset is empty", subset.isEmpty());
+        assertTrue("'key1' not found in the subset", subset.containsKey("key1"));
+        assertFalse("'ng.key2' found in the subset", subset.containsKey("ng.key2"));
+    }
+
+    @Test
+    public void testSetProperty()
+    {
+        Configuration conf = new BaseConfiguration();
+        Configuration subset = new SubsetConfiguration(conf, "test", ".");
+
+        // set a property in the subset and check the parent
+        subset.setProperty("key1", "value1");
+        assertEquals("key1 in the subset configuration", "value1", subset.getProperty("key1"));
+        assertEquals("test.key1 in the parent configuration", "value1", conf.getProperty("test.key1"));
+
+        // set a property in the parent and check in the subset
+        conf.setProperty("test.key2", "value2");
+        assertEquals("test.key2 in the parent configuration", "value2", conf.getProperty("test.key2"));
+        assertEquals("key2 in the subset configuration", "value2", subset.getProperty("key2"));
+    }
+
+    @Test
+    public void testGetParentKey()
+    {
+        // subset with delimiter
+        SubsetConfiguration subset = new SubsetConfiguration(null, "prefix", ".");
+        assertEquals("parent key for \"key\"", "prefix.key", subset.getParentKey("key"));
+        assertEquals("parent key for \"\"", "prefix", subset.getParentKey(""));
+
+        // subset without delimiter
+        subset = new SubsetConfiguration(null, "prefix", null);
+        assertEquals("parent key for \"key\"", "prefixkey", subset.getParentKey("key"));
+        assertEquals("parent key for \"\"", "prefix", subset.getParentKey(""));
+    }
+
+    @Test
+    public void testGetChildKey()
+    {
+        // subset with delimiter
+        SubsetConfiguration subset = new SubsetConfiguration(null, "prefix", ".");
+        assertEquals("parent key for \"prefixkey\"", "key", subset.getChildKey("prefix.key"));
+        assertEquals("parent key for \"prefix\"", "", subset.getChildKey("prefix"));
+
+        // subset without delimiter
+        subset = new SubsetConfiguration(null, "prefix", null);
+        assertEquals("parent key for \"prefixkey\"", "key", subset.getChildKey("prefixkey"));
+        assertEquals("parent key for \"prefix\"", "", subset.getChildKey("prefix"));
+    }
+
+    @Test
+    public void testGetKeys()
+    {
+        Configuration conf = new BaseConfiguration();
+        conf.setProperty("test", "value0");
+        conf.setProperty("test.key1", "value1");
+        conf.setProperty("testing.key2", "value1");
+
+        Configuration subset = new SubsetConfiguration(conf, "test", ".");
+
+        Iterator<String> it = subset.getKeys();
+        assertEquals("1st key", "", it.next());
+        assertEquals("2nd key", "key1", it.next());
+        assertFalse("too many elements", it.hasNext());
+    }
+
+    @Test
+    public void testGetKeysWithPrefix()
+    {
+        Configuration conf = new BaseConfiguration();
+        conf.setProperty("test.abc", "value0");
+        conf.setProperty("test.abc.key1", "value1");
+        conf.setProperty("test.abcdef.key2", "value1");
+
+        Configuration subset = new SubsetConfiguration(conf, "test", ".");
+
+        Iterator<String> it = subset.getKeys("abc");
+        assertEquals("1st key", "abc", it.next());
+        assertEquals("2nd key", "abc.key1", it.next());
+        assertFalse("too many elements", it.hasNext());
+    }
+
+    @Test
+    public void testGetList()
+    {
+        Configuration conf = new BaseConfiguration();
+        conf.setProperty("test.abc", "value0,value1");
+        conf.addProperty("test.abc", "value3");
+
+        Configuration subset = new SubsetConfiguration(conf, "test", ".");
+        List<Object> list = subset.getList("abc", new ArrayList<Object>());
+        assertEquals(3, list.size());
+    }
+
+    @Test
+    public void testGetParent()
+    {
+        Configuration conf = new BaseConfiguration();
+        SubsetConfiguration subset = new SubsetConfiguration(conf, "prefix", ".");
+
+        assertEquals("parent", conf, subset.getParent());
+    }
+
+    @Test
+    public void testGetPrefix()
+    {
+        Configuration conf = new BaseConfiguration();
+        SubsetConfiguration subset = new SubsetConfiguration(conf, "prefix", ".");
+
+        assertEquals("prefix", "prefix", subset.getPrefix());
+    }
+
+    @Test
+    public void testSetPrefix()
+    {
+        Configuration conf = new BaseConfiguration();
+        SubsetConfiguration subset = new SubsetConfiguration(conf, null, ".");
+        subset.setPrefix("prefix");
+
+        assertEquals("prefix", "prefix", subset.getPrefix());
+    }
+
+    @Test
+    public void testThrowExceptionOnMissing()
+    {
+        BaseConfiguration config = new BaseConfiguration();
+        config.setThrowExceptionOnMissing(true);
+
+        SubsetConfiguration subset = new SubsetConfiguration(config, "prefix");
+
+        try
+        {
+            subset.getString("foo");
+            fail("NoSuchElementException expected");
+        }
+        catch (NoSuchElementException e)
+        {
+            // expected
+        }
+
+        config.setThrowExceptionOnMissing(false);
+        assertNull(subset.getString("foo"));
+
+
+        subset.setThrowExceptionOnMissing(true);
+        try
+        {
+            config.getString("foo");
+            fail("NoSuchElementException expected");
+        }
+        catch (NoSuchElementException e)
+        {
+            // expected
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void testNested() throws Exception
+    {
+        ConfigurationFactory factory = new ConfigurationFactory();
+        File src = new File(new File(TEST_DIR), TEST_FILE);
+        factory.setConfigurationURL(src.toURL());
+        Configuration config = factory.getConfiguration();
+        Configuration subConf = config.subset("tables.table(0)");
+        assertTrue(subConf.getKeys().hasNext());
+        Configuration subSubConf = subConf.subset("fields.field(1)");
+        Iterator<String> itKeys = subSubConf.getKeys();
+        Set<String> keys = new HashSet<String>();
+        keys.add("name");
+        keys.add("type");
+        while(itKeys.hasNext())
+        {
+            String k = itKeys.next();
+            assertTrue(keys.contains(k));
+            keys.remove(k);
+        }
+        assertTrue(keys.isEmpty());
+    }
+
+    @Test
+    public void testClear()
+    {
+        Configuration config = new BaseConfiguration();
+        config.setProperty("test.key1", "value1");
+        config.setProperty("testing.key2", "value1");
+
+        Configuration subset = config.subset("test");
+        subset.clear();
+
+        assertTrue("the subset is not empty", subset.isEmpty());
+        assertFalse("the parent configuration is empty", config.isEmpty());
+    }
+
+    @Test
+    public void testSetListDelimiter()
+    {
+        BaseConfiguration config = new BaseConfiguration();
+        Configuration subset = config.subset("prefix");
+        config.setListDelimiter('/');
+        subset.addProperty("list", "a/b/c");
+        assertEquals("Wrong size of list", 3, config.getList("prefix.list")
+                .size());
+
+        ((AbstractConfiguration) subset).setListDelimiter(';');
+        subset.addProperty("list2", "a;b;c");
+        assertEquals("Wrong size of list2", 3, config.getList("prefix.list2")
+                .size());
+    }
+
+    @Test
+    public void testGetListDelimiter()
+    {
+        BaseConfiguration config = new BaseConfiguration();
+        AbstractConfiguration subset = (AbstractConfiguration) config
+                .subset("prefix");
+        config.setListDelimiter('/');
+        assertEquals("Wrong list delimiter in subset", '/', subset
+                .getListDelimiter());
+        subset.setListDelimiter(';');
+        assertEquals("Wrong list delimiter in parent", ';', config
+                .getListDelimiter());
+    }
+
+    @Test
+    public void testSetDelimiterParsingDisabled()
+    {
+        BaseConfiguration config = new BaseConfiguration();
+        Configuration subset = config.subset("prefix");
+        config.setDelimiterParsingDisabled(true);
+        subset.addProperty("list", "a,b,c");
+        assertEquals("Wrong value of property", "a,b,c", config
+                .getString("prefix.list"));
+
+        ((AbstractConfiguration) subset).setDelimiterParsingDisabled(false);
+        subset.addProperty("list2", "a,b,c");
+        assertEquals("Wrong size of list2", 3, config.getList("prefix.list2")
+                .size());
+    }
+
+    @Test
+    public void testIsDelimiterParsingDisabled()
+    {
+        BaseConfiguration config = new BaseConfiguration();
+        AbstractConfiguration subset = (AbstractConfiguration) config
+                .subset("prefix");
+        config.setDelimiterParsingDisabled(true);
+        assertTrue("Wrong value of list parsing flag in subset", subset
+                .isDelimiterParsingDisabled());
+        subset.setDelimiterParsingDisabled(false);
+        assertFalse("Wrong value of list parsing flag in parent", config
+                .isDelimiterParsingDisabled());
+    }
+
+    /**
+     * Tests manipulating the interpolator.
+     */
+    @Test
+    public void testInterpolator()
+    {
+        BaseConfiguration config = new BaseConfiguration();
+        AbstractConfiguration subset = (AbstractConfiguration) config
+                .subset("prefix");
+        InterpolationTestHelper.testGetInterpolator(subset);
+    }
+
+    @Test
+    public void testLocalLookupsInInterpolatorAreInherited() {
+        BaseConfiguration config = new BaseConfiguration();
+        ConfigurationInterpolator interpolator = config.getInterpolator();
+        interpolator.registerLookup("brackets", new StrLookup(){
+
+            @Override
+            public String lookup(String key) {
+                return "(" + key +")";
+            }
+
+        });
+        config.setProperty("prefix.var", "${brackets:x}");
+        AbstractConfiguration subset = (AbstractConfiguration) config
+                .subset("prefix");
+        assertEquals("Local lookup was not inherited", "(x)", subset
+                .getString("var", ""));
+    }
+
+    @Test
+    public void testInterpolationForKeysOfTheParent() {
+        BaseConfiguration config = new BaseConfiguration();
+        config.setProperty("test", "junit");
+        config.setProperty("prefix.key", "${test}");
+        AbstractConfiguration subset = (AbstractConfiguration) config
+                .subset("prefix");
+        assertEquals("Interpolation does not resolve parent keys", "junit",
+                subset.getString("key", ""));
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestSystemConfiguration.java b/src/test/java/org/apache/commons/configuration/TestSystemConfiguration.java
new file mode 100644
index 0000000..245dc50
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestSystemConfiguration.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Properties;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@code SystemConfiguration}.
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: TestSystemConfiguration.java 1225020 2011-12-27 21:20:45Z oheger $
+ */
+public class TestSystemConfiguration
+{
+    @Test
+    public void testSystemConfiguration()
+    {
+        Properties props = System.getProperties();
+        props.put("test.number", "123");
+
+        Configuration conf = new SystemConfiguration();
+        assertEquals("number", 123, conf.getInt("test.number"));
+    }
+
+    @Test
+    public void testSetSystemProperties()
+    {
+        PropertiesConfiguration props = new PropertiesConfiguration();
+        props.addProperty("test.name", "Apache");
+        SystemConfiguration.setSystemProperties(props);
+        assertEquals("System Properties", "Apache", System.getProperty("test.name"));
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestSystemConfigurationRegression.java b/src/test/java/org/apache/commons/configuration/TestSystemConfigurationRegression.java
new file mode 100644
index 0000000..5d59b86
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestSystemConfigurationRegression.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import org.junit.Test;
+import org.junit.Assert;
+
+import java.util.UUID;
+
+public class TestSystemConfigurationRegression
+{
+    @Test
+    public void testSystemPropertiesRegression()
+    {
+        SystemConfiguration sc = new SystemConfiguration();
+        int oldSize = sc.getMap().size();
+        System.setProperty(UUID.randomUUID().toString(), "x");
+        Assert.assertEquals(oldSize + 1, sc.getMap().size());
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestThreesomeConfiguration.java b/src/test/java/org/apache/commons/configuration/TestThreesomeConfiguration.java
new file mode 100644
index 0000000..5f5311d
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestThreesomeConfiguration.java
@@ -0,0 +1,67 @@
+package org.apache.commons.configuration;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * A base class for testing {@link
+ * org.apache.commons.configuration.BasePropertiesConfiguration}
+ * extensions.
+ *
+ * @version $Id: TestThreesomeConfiguration.java 1225021 2011-12-27 21:23:22Z oheger $
+ */
+public class TestThreesomeConfiguration
+{
+    protected Configuration conf;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        conf = new PropertiesConfiguration("threesome.properties");
+    }
+
+    @Test
+    public void testList1() throws Exception
+    {
+        List<Object> packages = conf.getList("test.threesome.one");
+        // we should get 3 packages here
+        assertEquals(3, packages.size());
+    }
+
+    @Test
+    public void testList2() throws Exception
+    {
+        List<Object> packages = conf.getList("test.threesome.two");
+        // we should get 3 packages here
+        assertEquals(3, packages.size());
+    }
+
+    @Test
+    public void testList3() throws Exception
+    {
+        List<Object> packages = conf.getList("test.threesome.three");
+        // we should get 3 packages here
+        assertEquals(3, packages.size());
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestVFSConfigurationBuilder.java b/src/test/java/org/apache/commons/configuration/TestVFSConfigurationBuilder.java
new file mode 100644
index 0000000..768e2e8
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestVFSConfigurationBuilder.java
@@ -0,0 +1,1200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.configuration.beanutils.BeanHelper;
+import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
+import org.apache.commons.configuration.tree.DefaultConfigurationNode;
+import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for VFSConfigurationBuilder.
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: TestVFSConfigurationBuilder.java 1308148 2012-04-01 16:27:41Z rgoers $
+ */
+public class TestVFSConfigurationBuilder
+{
+    /** Test configuration definition file. */
+    private static final File TEST_FILE = ConfigurationAssert
+            .getTestFile("testDigesterConfiguration.xml");
+
+    private static final File ADDITIONAL_FILE = ConfigurationAssert
+            .getTestFile("testDigesterConfiguration2.xml");
+
+    private static final File OPTIONAL_FILE = ConfigurationAssert
+            .getTestFile("testDigesterOptionalConfiguration.xml");
+
+    private static final File OPTIONALEX_FILE = ConfigurationAssert
+            .getTestFile("testDigesterOptionalConfigurationEx.xml");
+
+    private static final File MULTI_FILE = ConfigurationAssert
+            .getTestFile("testDigesterConfiguration3.xml");
+
+    private static final File INIT_FILE = ConfigurationAssert
+            .getTestFile("testComplexInitialization.xml");
+
+    private static final File CLASS_FILE = ConfigurationAssert
+            .getTestFile("testExtendedClass.xml");
+
+    private static final File PROVIDER_FILE = ConfigurationAssert
+            .getTestFile("testConfigurationProvider.xml");
+
+    private static final File EXTENDED_PROVIDER_FILE = ConfigurationAssert
+            .getTestFile("testExtendedXMLConfigurationProvider.xml");
+
+    private static final File GLOBAL_LOOKUP_FILE = ConfigurationAssert
+            .getTestFile("testGlobalLookup.xml");
+
+    private static final File SYSTEM_PROPS_FILE = ConfigurationAssert
+            .getTestFile("testSystemProperties.xml");
+
+    private static final File VALIDATION_FILE = ConfigurationAssert
+            .getTestFile("testValidation.xml");
+
+    private static final File VALIDATION2_FILE = ConfigurationAssert
+            .getTestFile("testValidation2.xml");
+
+    private static final File MULTI_TENENT_FILE = ConfigurationAssert
+            .getTestFile("testMultiTenentConfigurationBuilder.xml");
+
+    private static final File FILESYSTEM_FILE = ConfigurationAssert
+            .getTestFile("testFileSystem.xml");
+
+    private static final File FILERELOAD_FILE = ConfigurationAssert
+            .getTestFile("testFileReloadConfigurationBuilder.xml");
+
+    private static final File MULTI_RELOAD_FILE1 = ConfigurationAssert
+            .getTestFile("testVFSMultiTenentConfigurationBuilder1.xml");
+
+    private static final File MULTI_RELOAD_FILE2 = ConfigurationAssert
+            .getTestFile("testVFSMultiTenentConfigurationBuilder2.xml");
+
+    /** Constant for the name of an optional configuration.*/
+    private static final String OPTIONAL_NAME = "optionalConfig";
+
+    /** Stores the object to be tested. */
+    DefaultConfigurationBuilder factory;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        System
+                .setProperty("java.naming.factory.initial",
+                        "org.apache.commons.configuration.MockInitialContextFactory");
+        System.setProperty("test_file_xml", "test.xml");
+        System.setProperty("test_file_combine", "testcombine1.xml");
+        System.setProperty("basePath", "file://" + System.getProperty("user.dir") + "/target/test-classes");
+        FileSystem.setDefaultFileSystem(new VFSFileSystem());
+        factory = new DefaultConfigurationBuilder();
+        factory.clearErrorListeners();  // avoid exception messages
+    }
+
+    @After
+    public void tearDown() throws Exception
+    {
+        FileSystem.resetDefaultFileSystem();
+    }
+
+    /**
+     * Tests the isReservedNode() method of ConfigurationDeclaration.
+     */
+    @Test
+    public void testConfigurationDeclarationIsReserved()
+    {
+        DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
+                factory, factory);
+        DefaultConfigurationNode parent = new DefaultConfigurationNode();
+        DefaultConfigurationNode nd = new DefaultConfigurationNode("at");
+        parent.addAttribute(nd);
+        assertTrue("Attribute at not recognized", decl.isReservedNode(nd));
+        nd = new DefaultConfigurationNode("optional");
+        parent.addAttribute(nd);
+        assertTrue("Attribute optional not recognized", decl.isReservedNode(nd));
+        nd = new DefaultConfigurationNode("config-class");
+        parent.addAttribute(nd);
+        assertTrue("Inherited attribute not recognized", decl
+                .isReservedNode(nd));
+        nd = new DefaultConfigurationNode("different");
+        parent.addAttribute(nd);
+        assertFalse("Wrong reserved attribute", decl.isReservedNode(nd));
+        nd = new DefaultConfigurationNode("at");
+        parent.addChild(nd);
+        assertFalse("Node type not evaluated", decl.isReservedNode(nd));
+    }
+
+    /**
+     * Tests if the at attribute is correctly detected as reserved attribute.
+     */
+    @Test
+    public void testConfigurationDeclarationIsReservedAt()
+    {
+        checkOldReservedAttribute("at");
+    }
+
+    /**
+     * Tests if the optional attribute is correctly detected as reserved
+     * attribute.
+     */
+    @Test
+    public void testConfigurationDeclarationIsReservedOptional()
+    {
+        checkOldReservedAttribute("optional");
+    }
+
+    /**
+     * Tests if special reserved attributes are recognized by the
+     * isReservedNode() method. For compatibility reasons the attributes "at"
+     * and "optional" are also treated as reserved attributes, but only if there
+     * are no corresponding attributes with the "config-" prefix.
+     *
+     * @param name the attribute name
+     */
+    private void checkOldReservedAttribute(String name)
+    {
+        DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
+                factory, factory);
+        DefaultConfigurationNode parent = new DefaultConfigurationNode();
+        DefaultConfigurationNode nd = new DefaultConfigurationNode("config-"
+                + name);
+        parent.addAttribute(nd);
+        assertTrue("config-" + name + " attribute not recognized", decl
+                .isReservedNode(nd));
+        DefaultConfigurationNode nd2 = new DefaultConfigurationNode(name);
+        parent.addAttribute(nd2);
+        assertFalse(name + " is reserved though config- exists", decl
+                .isReservedNode(nd2));
+        assertTrue("config- attribute not recognized when " + name + " exists",
+                decl.isReservedNode(nd));
+    }
+
+    /**
+     * Tests access to certain reserved attributes of a
+     * ConfigurationDeclaration.
+     */
+    @Test
+    public void testConfigurationDeclarationGetAttributes()
+    {
+        factory.addProperty("xml.fileName", "test.xml");
+        DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
+                factory, factory.configurationAt("xml"));
+        assertNull("Found an at attribute", decl.getAt());
+        assertFalse("Found an optional attribute", decl.isOptional());
+        factory.addProperty("xml[@config-at]", "test1");
+        assertEquals("Wrong value of at attribute", "test1", decl.getAt());
+        factory.addProperty("xml[@at]", "test2");
+        assertEquals("Wrong value of config-at attribute", "test1", decl.getAt());
+        factory.clearProperty("xml[@config-at]");
+        assertEquals("Old at attribute not detected", "test2", decl.getAt());
+        factory.addProperty("xml[@config-optional]", "true");
+        assertTrue("Wrong value of optional attribute", decl.isOptional());
+        factory.addProperty("xml[@optional]", "false");
+        assertTrue("Wrong value of config-optional attribute", decl.isOptional());
+        factory.clearProperty("xml[@config-optional]");
+        factory.setProperty("xml[@optional]", Boolean.TRUE);
+        assertTrue("Old optional attribute not detected", decl.isOptional());
+    }
+
+    /**
+     * Tests whether an invalid value of an optional attribute is detected.
+     */
+    @Test(expected = ConfigurationRuntimeException.class)
+    public void testConfigurationDeclarationOptionalAttributeInvalid()
+    {
+        factory.addProperty("xml.fileName", "test.xml");
+        DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
+                factory, factory.configurationAt("xml"));
+        factory.setProperty("xml[@optional]", "invalid value");
+        decl.isOptional();
+    }
+
+    /**
+     * Tests adding a new configuration provider.
+     */
+    @Test
+    public void testAddConfigurationProvider()
+    {
+        DefaultConfigurationBuilder.ConfigurationProvider provider = new DefaultConfigurationBuilder.ConfigurationProvider();
+        assertNull("Provider already registered", factory
+                .providerForTag("test"));
+        factory.addConfigurationProvider("test", provider);
+        assertSame("Provider not registered", provider, factory
+                .providerForTag("test"));
+    }
+
+    /**
+     * Tries to register a null configuration provider. This should cause an
+     * exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testAddConfigurationProviderNull()
+    {
+        factory.addConfigurationProvider("test", null);
+    }
+
+    /**
+     * Tries to register a configuration provider for a null tag. This should
+     * cause an exception to be thrown.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testAddConfigurationProviderNullTag()
+    {
+        factory.addConfigurationProvider(null,
+               new DefaultConfigurationBuilder.ConfigurationProvider());
+    }
+
+    /**
+     * Tests removing configuration providers.
+     */
+    @Test
+    public void testRemoveConfigurationProvider()
+    {
+        assertNull("Removing unknown provider", factory
+                .removeConfigurationProvider("test"));
+        assertNull("Removing provider for null tag", factory
+                .removeConfigurationProvider(null));
+        DefaultConfigurationBuilder.ConfigurationProvider provider = new DefaultConfigurationBuilder.ConfigurationProvider();
+        factory.addConfigurationProvider("test", provider);
+        assertSame("Failed to remove provider", provider, factory
+                .removeConfigurationProvider("test"));
+        assertNull("Provider still registered", factory.providerForTag("test"));
+    }
+
+    /**
+     * Tests creating a configuration object from a configuration declaration.
+     */
+    @Test
+    public void testConfigurationBeanFactoryCreateBean()
+    {
+        factory.addConfigurationProvider("test",
+                new DefaultConfigurationBuilder.ConfigurationProvider(
+                        PropertiesConfiguration.class));
+        factory.addProperty("test[@throwExceptionOnMissing]", "true");
+        DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
+                factory, factory.configurationAt("test"));
+        PropertiesConfiguration conf = (PropertiesConfiguration) BeanHelper
+                .createBean(decl);
+        assertTrue("Property was not initialized", conf
+                .isThrowExceptionOnMissing());
+    }
+
+    /**
+     * Tests creating a configuration object from an unknown tag. This should
+     * cause an exception.
+     */
+    @Test(expected = ConfigurationRuntimeException.class)
+    public void testConfigurationBeanFactoryCreateUnknownTag()
+    {
+        factory.addProperty("test[@throwExceptionOnMissing]", "true");
+        DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
+                factory, factory.configurationAt("test"));
+        BeanHelper.createBean(decl);
+    }
+
+    /**
+     * Tests loading a simple configuration definition file.
+     */
+    @Test
+    public void testLoadConfiguration() throws ConfigurationException
+    {
+        factory.setFile(TEST_FILE);
+        checkConfiguration();
+    }
+
+    /**
+     * Tests the file constructor.
+     */
+    @Test
+    public void testLoadConfigurationFromFile() throws ConfigurationException
+    {
+        factory = new DefaultConfigurationBuilder(TEST_FILE);
+        checkConfiguration();
+    }
+
+    /**
+     * Tests the file name constructor.
+     */
+    @Test
+    public void testLoadConfigurationFromFileName()
+            throws ConfigurationException
+    {
+        factory = new DefaultConfigurationBuilder(TEST_FILE.getAbsolutePath());
+        checkConfiguration();
+    }
+
+    /**
+     * Tests the URL constructor.
+     */
+    @Test
+    public void testLoadConfigurationFromURL() throws Exception
+    {
+        factory = new DefaultConfigurationBuilder(TEST_FILE.toURI().toURL());
+        checkConfiguration();
+    }
+
+    /**
+     * Tests if the configuration was correctly created by the factory.
+     */
+    private void checkConfiguration() throws ConfigurationException
+    {
+        CombinedConfiguration compositeConfiguration = (CombinedConfiguration) factory
+                .getConfiguration();
+
+        assertEquals("Number of configurations", 3, compositeConfiguration
+                .getNumberOfConfigurations());
+        assertEquals(PropertiesConfiguration.class, compositeConfiguration
+                .getConfiguration(0).getClass());
+        assertEquals(XMLPropertiesConfiguration.class, compositeConfiguration
+                .getConfiguration(1).getClass());
+        assertEquals(XMLConfiguration.class, compositeConfiguration
+                .getConfiguration(2).getClass());
+
+        // check the first configuration
+        PropertiesConfiguration pc = (PropertiesConfiguration) compositeConfiguration
+                .getConfiguration(0);
+        assertNotNull("Make sure we have a fileName: " + pc.getFileName(), pc
+                .getFileName());
+
+        // check some properties
+        checkProperties(compositeConfiguration);
+    }
+
+    /**
+     * Checks if the passed in configuration contains the expected properties.
+     *
+     * @param compositeConfiguration the configuration to check
+     */
+    private void checkProperties(Configuration compositeConfiguration)
+    {
+        assertTrue("Make sure we have loaded our key", compositeConfiguration
+                .getBoolean("test.boolean"));
+        assertEquals("I'm complex!", compositeConfiguration
+                .getProperty("element2.subelement.subsubelement"));
+        assertEquals("property in the XMLPropertiesConfiguration", "value1",
+                compositeConfiguration.getProperty("key1"));
+    }
+
+    /**
+     * Tests loading a configuration definition file with an additional section.
+     */
+    @Test
+    public void testLoadAdditional() throws ConfigurationException
+    {
+        factory.setFile(ADDITIONAL_FILE);
+        CombinedConfiguration compositeConfiguration = (CombinedConfiguration) factory
+                .getConfiguration();
+        assertEquals("Verify how many configs", 2, compositeConfiguration
+                .getNumberOfConfigurations());
+
+        // Test if union was constructed correctly
+        Object prop = compositeConfiguration.getProperty("tables.table.name");
+        assertTrue(prop instanceof Collection);
+        assertEquals(3, ((Collection<?>) prop).size());
+        assertEquals("users", compositeConfiguration
+                .getProperty("tables.table(0).name"));
+        assertEquals("documents", compositeConfiguration
+                .getProperty("tables.table(1).name"));
+        assertEquals("tasks", compositeConfiguration
+                .getProperty("tables.table(2).name"));
+
+        prop = compositeConfiguration
+                .getProperty("tables.table.fields.field.name");
+        assertTrue(prop instanceof Collection);
+        assertEquals(17, ((Collection<?>) prop).size());
+
+        assertEquals("smtp.mydomain.org", compositeConfiguration
+                .getString("mail.host.smtp"));
+        assertEquals("pop3.mydomain.org", compositeConfiguration
+                .getString("mail.host.pop"));
+
+        // This was overridden
+        assertEquals("masterOfPost", compositeConfiguration
+                .getString("mail.account.user"));
+        assertEquals("topsecret", compositeConfiguration
+                .getString("mail.account.psswd"));
+
+        // This was overridden, too, but not in additional section
+        assertEquals("enhanced factory", compositeConfiguration
+                .getString("test.configuration"));
+    }
+
+    /**
+     * Tests whether a default log error listener is registered at the builder
+     * instance.
+     */
+    @Test
+    public void testLogErrorListener()
+    {
+        assertEquals("No default error listener registered", 1,
+                new DefaultConfigurationBuilder().getErrorListeners().size());
+    }
+
+    /**
+     * Tests loading a definition file that contains optional configurations.
+     */
+    @Test
+    public void testLoadOptional() throws Exception
+    {
+        factory.setURL(OPTIONAL_FILE.toURI().toURL());
+        Configuration config = factory.getConfiguration();
+        assertTrue(config.getBoolean("test.boolean"));
+        assertEquals("value", config.getProperty("element"));
+    }
+
+    /**
+     * Tests whether loading a failing optional configuration causes an error
+     * event.
+     */
+    @Test
+    public void testLoadOptionalErrorEvent() throws Exception
+    {
+        factory.clearErrorListeners();
+        ConfigurationErrorListenerImpl listener = new ConfigurationErrorListenerImpl();
+        factory.addErrorListener(listener);
+        prepareOptionalTest("configuration", false);
+        listener.verify(DefaultConfigurationBuilder.EVENT_ERR_LOAD_OPTIONAL,
+                OPTIONAL_NAME, null);
+    }
+
+    /**
+     * Tests loading a definition file with optional and non optional
+     * configuration sources. One non optional does not exist, so this should
+     * cause an exception.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testLoadOptionalWithException() throws ConfigurationException
+    {
+        factory.setFile(OPTIONALEX_FILE);
+        factory.getConfiguration();
+    }
+
+    /**
+     * Tries to load a configuration file with an optional, non file-based
+     * configuration. The optional attribute should work for other configuration
+     * classes, too.
+     */
+    @Test
+    public void testLoadOptionalNonFileBased() throws ConfigurationException
+    {
+        CombinedConfiguration config = prepareOptionalTest("configuration", false);
+        assertTrue("Configuration not empty", config.isEmpty());
+        assertEquals("Wrong number of configurations", 0, config
+                .getNumberOfConfigurations());
+    }
+
+    /**
+     * Tests an optional, non existing configuration with the forceCreate
+     * attribute. This configuration should be added to the resulting
+     * configuration.
+     */
+    @Test
+    public void testLoadOptionalForceCreate() throws ConfigurationException
+    {
+        factory.setBasePath(TEST_FILE.getParent());
+        CombinedConfiguration config = prepareOptionalTest("xml", true);
+        assertEquals("Wrong number of configurations", 1, config
+                .getNumberOfConfigurations());
+        FileConfiguration fc = (FileConfiguration) config
+                .getConfiguration(OPTIONAL_NAME);
+        assertNotNull("Optional config not found", fc);
+        assertEquals("File name was not set", "nonExisting.xml", fc
+                .getFileName());
+        assertNotNull("Base path was not set", fc.getBasePath());
+    }
+
+    /**
+     * Tests loading an embedded optional configuration builder with the force
+     * create attribute.
+     */
+    @Test
+    public void testLoadOptionalBuilderForceCreate()
+            throws ConfigurationException
+    {
+        CombinedConfiguration config = prepareOptionalTest("configuration",
+                true);
+        assertEquals("Wrong number of configurations", 1, config
+                .getNumberOfConfigurations());
+        assertTrue(
+                "Wrong optional configuration type",
+                config.getConfiguration(OPTIONAL_NAME) instanceof CombinedConfiguration);
+    }
+
+    /**
+     * Tests loading an optional configuration with the force create attribute
+     * set. The provider will always throw an exception. In this case the
+     * configuration will not be added to the resulting combined configuration.
+     */
+    @Test
+    public void testLoadOptionalForceCreateWithException()
+            throws ConfigurationException
+    {
+        factory.addConfigurationProvider("test",
+                new DefaultConfigurationBuilder.ConfigurationBuilderProvider()
+                {
+                    // Throw an exception here, too
+                    @Override
+                    public AbstractConfiguration getEmptyConfiguration(
+                            DefaultConfigurationBuilder.ConfigurationDeclaration decl) throws Exception
+                    {
+                        throw new Exception("Unable to create configuration!");
+                    }
+                });
+        CombinedConfiguration config = prepareOptionalTest("test", true);
+        assertEquals("Optional configuration could be created", 0, config
+                .getNumberOfConfigurations());
+    }
+
+    /**
+     * Prepares a test for loading a configuration definition file with an
+     * optional configuration declaration.
+     *
+     * @param tag the tag name with the optional configuration
+     * @param force the forceCreate attribute
+     * @return the combined configuration obtained from the builder
+     * @throws org.apache.commons.configuration.ConfigurationException if an error occurs
+     */
+    private CombinedConfiguration prepareOptionalTest(String tag, boolean force)
+            throws ConfigurationException
+    {
+        String prefix = "override." + tag;
+        factory.addProperty(prefix + "[@fileName]", "nonExisting.xml");
+        factory.addProperty(prefix + "[@config-optional]", Boolean.TRUE);
+        factory.addProperty(prefix + "[@config-name]", OPTIONAL_NAME);
+        if (force)
+        {
+            factory.addProperty(prefix + "[@config-forceCreate]", Boolean.TRUE);
+        }
+        return factory.getConfiguration(false);
+    }
+
+    /**
+     * Tests loading a definition file with multiple different sources.
+     */
+    @Test
+    public void testLoadDifferentSources() throws ConfigurationException
+    {
+        factory.setFile(MULTI_FILE);
+        Configuration config = factory.getConfiguration();
+        assertFalse(config.isEmpty());
+        assertTrue(config instanceof CombinedConfiguration);
+        CombinedConfiguration cc = (CombinedConfiguration) config;
+        assertEquals("Wrong number of configurations", 1, cc
+                .getNumberOfConfigurations());
+
+        assertNotNull(config
+                .getProperty("tables.table(0).fields.field(2).name"));
+        assertNotNull(config.getProperty("element2.subelement.subsubelement"));
+        assertEquals("value", config.getProperty("element3"));
+        assertEquals("foo", config.getProperty("element3[@name]"));
+        assertNotNull(config.getProperty("mail.account.user"));
+
+        // test JNDIConfiguration
+        assertNotNull(config.getProperty("test.onlyinjndi"));
+        assertTrue(config.getBoolean("test.onlyinjndi"));
+
+        Configuration subset = config.subset("test");
+        assertNotNull(subset.getProperty("onlyinjndi"));
+        assertTrue(subset.getBoolean("onlyinjndi"));
+
+        // test SystemConfiguration
+        assertNotNull(config.getProperty("java.version"));
+        assertEquals(System.getProperty("java.version"), config
+                .getString("java.version"));
+    }
+
+    /**
+     * Tests if the base path is correctly evaluated.
+     */
+    @Test
+    public void testSetConfigurationBasePath() throws ConfigurationException
+    {
+        factory.addProperty("properties[@fileName]", "test.properties");
+        File deepDir = new File(ConfigurationAssert.TEST_DIR, "config/deep");
+        factory.setConfigurationBasePath(deepDir.getAbsolutePath());
+
+        Configuration config = factory.getConfiguration(false);
+        assertEquals("Wrong property value", "somevalue", config
+                .getString("somekey"));
+    }
+
+    /**
+     * Tests reading a configuration definition file that contains complex
+     * initialization of properties of the declared configuration sources.
+     */
+    @Test
+    public void testComplexInitialization() throws ConfigurationException
+    {
+        factory.setFile(INIT_FILE);
+        CombinedConfiguration cc = (CombinedConfiguration) factory
+                .getConfiguration();
+
+        assertEquals("System property not found", "test.xml",
+                cc.getString("test_file_xml"));
+        PropertiesConfiguration c1 = (PropertiesConfiguration) cc
+                .getConfiguration(1);
+        assertTrue(
+                "Reloading strategy was not set",
+                c1.getReloadingStrategy() instanceof FileChangedReloadingStrategy);
+        assertEquals("Refresh delay was not set", 10000,
+                ((FileChangedReloadingStrategy) c1.getReloadingStrategy())
+                        .getRefreshDelay());
+
+        Configuration xmlConf = cc.getConfiguration("xml");
+        assertEquals("Property not found", "I'm complex!", xmlConf
+                .getString("element2/subelement/subsubelement"));
+        assertEquals("List index not found", "two", xmlConf
+                .getString("list[0]/item[1]"));
+        assertEquals("Property in combiner file not found", "yellow", cc
+                .getString("/gui/selcolor"));
+
+        assertTrue("Delimiter flag was not set", cc
+                .isDelimiterParsingDisabled());
+        assertTrue("Expression engine was not set",
+                cc.getExpressionEngine() instanceof XPathExpressionEngine);
+    }
+
+    /**
+     * Tests if the returned combined configuration has the expected structure.
+     */
+    @Test
+    public void testCombinedConfiguration() throws ConfigurationException
+    {
+        factory.setFile(INIT_FILE);
+        CombinedConfiguration cc = (CombinedConfiguration) factory
+                .getConfiguration();
+        assertNotNull("Properties configuration not found", cc
+                .getConfiguration("properties"));
+        assertNotNull("XML configuration not found", cc.getConfiguration("xml"));
+        assertEquals("Wrong number of contained configs", 4, cc
+                .getNumberOfConfigurations());
+
+        CombinedConfiguration cc2 = (CombinedConfiguration) cc
+                .getConfiguration(DefaultConfigurationBuilder.ADDITIONAL_NAME);
+        assertNotNull("No additional configuration found", cc2);
+        Set<String> names = cc2.getConfigurationNames();
+        assertEquals("Wrong number of contained additional configs", 2, names
+                .size());
+        assertTrue("Config 1 not contained", names.contains("combiner1"));
+        assertTrue("Config 2 not contained", names.contains("combiner2"));
+    }
+
+    /**
+     * Tests the structure of the returned combined configuration if there is no
+     * additional section.
+     */
+    @Test
+    public void testCombinedConfigurationNoAdditional()
+            throws ConfigurationException
+    {
+        factory.setFile(TEST_FILE);
+        CombinedConfiguration cc = factory.getConfiguration(true);
+        assertNull("Additional configuration was found", cc
+                .getConfiguration(DefaultConfigurationBuilder.ADDITIONAL_NAME));
+    }
+
+    /**
+     * Tests whether the list node definition was correctly processed.
+     */
+    @Test
+    public void testCombinedConfigurationListNodes()
+            throws ConfigurationException
+    {
+        factory.setFile(INIT_FILE);
+        CombinedConfiguration cc = factory.getConfiguration(true);
+        Set<String> listNodes = cc.getNodeCombiner().getListNodes();
+        assertEquals("Wrong number of list nodes", 2, listNodes.size());
+        assertTrue("table node not a list node", listNodes.contains("table"));
+        assertTrue("list node not a list node", listNodes.contains("list"));
+
+        CombinedConfiguration cca = (CombinedConfiguration) cc
+                .getConfiguration(DefaultConfigurationBuilder.ADDITIONAL_NAME);
+        listNodes = cca.getNodeCombiner().getListNodes();
+        assertTrue("Found list nodes for additional combiner", listNodes
+                .isEmpty());
+    }
+
+    /**
+     * Tests whether a configuration builder can itself be declared in a
+     * configuration definition file.
+     */
+    @Test
+    public void testConfigurationBuilderProvider()
+            throws ConfigurationException
+    {
+        factory.addProperty("override.configuration[@fileName]", TEST_FILE
+                .getAbsolutePath());
+        CombinedConfiguration cc = factory.getConfiguration(false);
+        assertEquals("Wrong number of configurations", 1, cc
+                .getNumberOfConfigurations());
+        checkProperties(cc);
+    }
+
+    /**
+     * Tests whether XML settings can be inherited.
+     */
+    @Test
+    public void testLoadXMLWithSettings() throws Exception
+    {
+        File confDir = new File("conf");
+        File targetDir = new File("target");
+        File testXMLValidationSource = new File(confDir,
+                "testValidateInvalid.xml");
+        File testSavedXML = new File(targetDir, "testSave.xml");
+        File testSavedFactory = new File(targetDir, "testSaveFactory.xml");
+        URL dtdFile = getClass().getResource("/properties.dtd");
+        final String publicId = "http://commons.apache.org/test.dtd";
+
+        XMLConfiguration config = new XMLConfiguration("testDtd.xml");
+        config.setPublicID(publicId);
+        config.save(testSavedXML);
+        factory.addProperty("xml[@fileName]", testSavedXML.getAbsolutePath());
+        factory.addProperty("xml(0)[@validating]", "true");
+        factory.addProperty("xml(-1)[@fileName]", testXMLValidationSource
+                .getAbsolutePath());
+        factory.addProperty("xml(1)[@config-optional]", "true");
+        factory.addProperty("xml(1)[@validating]", "true");
+        factory.save(testSavedFactory);
+
+        factory = new DefaultConfigurationBuilder();
+        factory.setFile(testSavedFactory);
+        factory.registerEntityId(publicId, dtdFile);
+        factory.clearErrorListeners();
+        Configuration c = factory.getConfiguration();
+        assertEquals("Wrong property value", "value1", c.getString("entry(0)"));
+        assertFalse("Invalid XML source was loaded", c
+                .containsKey("table.name"));
+
+        testSavedXML.delete();
+        testSavedFactory.delete();
+    }
+
+    /**
+     * Tests loading a configuration definition file that defines a custom
+     * result class.
+     */
+    @Test
+    public void testExtendedClass() throws ConfigurationException
+    {
+        factory.setFile(CLASS_FILE);
+        CombinedConfiguration cc = factory.getConfiguration(true);
+        String prop = (String)cc.getProperty("test");
+        assertEquals("Expected 'Extended', actual '" + prop + "'", "Extended", prop);
+        assertTrue("Wrong result class: " + cc.getClass(),
+                cc instanceof TestDefaultConfigurationBuilder.ExtendedCombinedConfiguration);
+    }
+
+    /**
+     * Tests loading a configuration definition file that defines new providers.
+     */
+    @Test
+    public void testConfigurationProvider() throws ConfigurationException
+    {
+        factory.setFile(PROVIDER_FILE);
+        factory.getConfiguration(true);
+        DefaultConfigurationBuilder.ConfigurationProvider provider = factory
+                .providerForTag("test");
+        assertNotNull("Provider 'test' not registered", provider);
+    }
+
+    /**
+     * Tests loading a configuration definition file that defines new providers.
+     */
+    @Test
+    public void testExtendedXMLConfigurationProvider() throws ConfigurationException
+    {
+        factory.setFile(EXTENDED_PROVIDER_FILE);
+        CombinedConfiguration cc = factory.getConfiguration(true);
+        DefaultConfigurationBuilder.ConfigurationProvider provider = factory
+                .providerForTag("test");
+        assertNotNull("Provider 'test' not registered", provider);
+        Configuration config = cc.getConfiguration("xml");
+        assertNotNull("Test configuration not present", config);
+        assertTrue("Configuration is not ExtendedXMLConfiguration, is " +
+                config.getClass().getName(),
+                config instanceof TestDefaultConfigurationBuilder.ExtendedXMLConfiguration);
+    }
+
+    @Test
+    public void testGlobalLookup() throws Exception
+    {
+        factory.setFile(GLOBAL_LOOKUP_FILE);
+        CombinedConfiguration cc = factory.getConfiguration(true);
+        String value = cc.getInterpolator().lookup("test:test_key");
+        assertNotNull("The test key was not located", value);
+        assertEquals("Incorrect value retrieved","test.value",value);
+    }
+
+    @Test
+    public void testSystemProperties() throws Exception
+    {
+        factory.setFile(SYSTEM_PROPS_FILE);
+        factory.getConfiguration(true);
+        String value = System.getProperty("key1");
+        assertNotNull("The test key was not located", value);
+        assertEquals("Incorrect value retrieved","value1",value);
+    }
+
+    @Test
+    public void testValidation() throws Exception
+    {
+        factory.setFile(VALIDATION_FILE);
+        CombinedConfiguration cc = factory.getConfiguration(true);
+        String value = cc.getString("key1");
+        assertNotNull("The test key was not located", value);
+        assertEquals("Incorrect value retrieved","value1",value);
+    }
+
+    @Test
+    public void testValidation2() throws Exception
+    {
+        factory.setFile(VALIDATION2_FILE);
+        CombinedConfiguration cc = factory.getConfiguration(true);
+        String value = cc.getString("key1");
+        assertNotNull("The test key was not located", value);
+        assertEquals("Incorrect value retrieved","value1",value);
+    }
+
+    @Test
+    public void testMultiTenentConfiguration() throws Exception
+    {
+        factory.setFile(MULTI_TENENT_FILE);
+        System.getProperties().remove("Id");
+
+        CombinedConfiguration config = factory.getConfiguration(true);
+        assertTrue("Incorrect configuration", config instanceof DynamicCombinedConfiguration);
+
+        verify(null, config, 50);
+        verify("1001", config, 15);
+        verify("1002", config, 25);
+        verify("1003", config, 35);
+        verify("1004", config, 50);
+        verify("1005", config, 50);
+    }
+
+    @Test
+    public void testMultiTenentConfiguration2() throws Exception
+    {
+        factory.setFile(MULTI_TENENT_FILE);
+        System.setProperty("Id", "1004");
+
+        CombinedConfiguration config = factory.getConfiguration(true);
+        assertTrue("Incorrect configuration", config instanceof DynamicCombinedConfiguration);
+
+        verify("1001", config, 15);
+        verify("1002", config, 25);
+        verify("1003", config, 35);
+        verify("1004", config, 50);
+        verify("1005", config, 50);
+    }
+
+    @Test
+    public void testMultiTenentConfiguration3() throws Exception
+    {
+        factory.setFile(MULTI_TENENT_FILE);
+        System.setProperty("Id", "1005");
+
+        CombinedConfiguration config = factory.getConfiguration(true);
+        assertTrue("Incorrect configuration", config instanceof DynamicCombinedConfiguration);
+
+        verify("1001", config, 15);
+        verify("1002", config, 25);
+        verify("1003", config, 35);
+        verify("1004", config, 50);
+        verify("1005", config, 50);
+    }
+
+    @Test
+    public void testSetFileSystem() throws Exception
+    {
+        factory.setFile(PROVIDER_FILE);
+        FileSystem fs = new VFSFileSystem();
+        factory.setFileSystem(fs);
+        FileSystem.resetDefaultFileSystem();
+        System.getProperties().remove("Id");
+
+        CombinedConfiguration config = factory.getConfiguration(true);
+        List<AbstractConfiguration> list = config.getConfigurations();
+        assertTrue("Incorrect number of configurations - " + list.size(), list.size() == 4);
+        Iterator<AbstractConfiguration> iter = list.iterator();
+        while (iter.hasNext())
+        {
+            Configuration conf = iter.next();
+            if (conf instanceof FileSystemBased)
+            {
+                assertTrue("Incorrect file system for Configuration " + conf,
+                        ((FileSystemBased)conf).getFileSystem() == fs);
+            }
+            else if (conf instanceof CombinedConfiguration)
+            {
+                Iterator<AbstractConfiguration> it = ((CombinedConfiguration)conf).getConfigurations().iterator();
+                while (it.hasNext())
+                {
+                    conf = it.next();
+                    if (conf instanceof FileSystemBased)
+                    {
+                        assertTrue("Incorrect file system for Configuration " + conf,
+                            ((FileSystemBased)conf).getFileSystem() == fs);
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testConfiguredFileSystem() throws Exception
+    {
+        factory.setFile(FILESYSTEM_FILE);
+        FileSystem.resetDefaultFileSystem();
+        System.getProperties().remove("Id");
+
+        CombinedConfiguration config = factory.getConfiguration(true);
+        FileSystem fs = factory.getFileSystem();
+        assertNotNull("No File System",fs);
+        assertTrue("Incorrect File System", fs instanceof VFSFileSystem);
+        List<AbstractConfiguration> list = config.getConfigurations();
+        assertTrue("Incorrect number of configurations - " + list.size(), list.size() == 4);
+        Iterator<AbstractConfiguration> iter = list.iterator();
+        while (iter.hasNext())
+        {
+            Configuration conf = iter.next();
+            if (conf instanceof FileSystemBased)
+            {
+                assertTrue("Incorrect file system for Configuration " + conf,
+                        ((FileSystemBased)conf).getFileSystem() == fs);
+            }
+            else if (conf instanceof CombinedConfiguration)
+            {
+                Iterator<AbstractConfiguration> it = ((CombinedConfiguration)conf).getConfigurations().iterator();
+                while (it.hasNext())
+                {
+                    conf = it.next();
+                    if (conf instanceof FileSystemBased)
+                    {
+                        assertTrue("Incorrect file system for Configuration " + conf,
+                            ((FileSystemBased)conf).getFileSystem() == fs);
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testFileReload1() throws Exception
+    {
+        // create a new configuration
+        File input = new File("target/test-classes/testMultiConfiguration_1001.xml");
+        File output = new File("target/test-classes/testwrite/testMultiConfiguration_1001.xml");
+        output.delete();
+        output.getParentFile().mkdir();
+        copyFile(input, output);
+        // Sleep to make sure the file timestamp is not in the same second as "now".
+        Thread.sleep(1100);
+
+        factory.setFile(FILERELOAD_FILE);
+        FileSystem.resetDefaultFileSystem();
+        System.getProperties().remove("Id");
+
+        CombinedConfiguration config = factory.getConfiguration(true);
+        assertNotNull(config);
+        verify("1001", config, 15);
+        Thread.sleep(1100);
+        XMLConfiguration x = new XMLConfiguration(output);
+        x.setProperty("rowsPerPage", "50");
+        x.save();
+        verify("1001", config, 50);
+        output.delete();
+    }
+
+    @Test
+    public void testFileReload2() throws Exception
+    {
+        // create a new configuration
+        File input = new File("target/test-classes/testMultiConfiguration_1002.xml");
+        File output = new File("target/test-classes/testwrite/testMultiConfiguration_1002.xml");
+        output.delete();
+
+        factory.setFile(FILERELOAD_FILE);
+        FileSystem.resetDefaultFileSystem();
+        System.getProperties().remove("Id");
+
+        CombinedConfiguration config = factory.getConfiguration(true);
+        assertNotNull(config);
+
+        verify("1002", config, 50);
+        // Sleep to make sure the file timestamp is not in the same second as "now".
+        Thread.sleep(1100);
+        output.getParentFile().mkdir();
+        copyFile(input, output);
+        verify("1002", config, 25);
+        output.delete();
+    }
+
+    @Test
+    public void testFileReload3() throws Exception
+    {
+        // create a new configuration
+        File input = new File("target/test-classes/testMultiConfiguration_1001.xml");
+        File output = new File("target/test-classes/testwrite/testMultiConfiguration_1001.xml");
+        output.delete();
+        output.getParentFile().mkdir();
+
+        factory.setFile(FILERELOAD_FILE);
+        FileSystem.resetDefaultFileSystem();
+        System.getProperties().remove("Id");
+
+        CombinedConfiguration config = factory.getConfiguration(true);
+        assertNotNull(config);
+        verify("1001", config, 50);
+        copyFile(input, output);
+        // Sleep to make sure the file timestamp is not in the same second as "now".
+        Thread.sleep(1100);
+        verify("1001", config, 15);
+        XMLConfiguration x = new XMLConfiguration(output);
+        x.setProperty("rowsPerPage", "25");
+        x.save();
+         // Sleep to make sure the file timestamp is not in the same second as "now".
+        Thread.sleep(1100);
+        verify("1001", config, 25);
+        output.delete();
+    }
+
+    @Test
+    public void testReloadDefault() throws Exception
+    {
+        // create a new configuration
+        String defaultName = "target/test-classes/testMultiConfiguration_default.xml";
+        File input = new File(defaultName);
+
+        System.getProperties().remove("Id");
+        factory.setFile(MULTI_RELOAD_FILE1);
+        CombinedConfiguration config = factory.getConfiguration(true);
+        assertNotNull(config);
+        verify("3001", config, 15);
+        verify("3002", config, 25);
+        System.setProperty("Id", "3002");
+        config.addProperty("/ TestProp", "Test");
+        assertTrue("Property not added", "Test".equals(config.getString("TestProp")));
+        System.getProperties().remove("Id");
+        //Sleep so refreshDelay elapses
+        Thread.sleep(600);
+        long time = System.currentTimeMillis();
+        long original = input.lastModified();
+        input.setLastModified(time);
+        File defaultFile = new File(defaultName);
+        long newTime = defaultFile.lastModified();
+        assertTrue("time mismatch", original != newTime);
+        Thread.sleep(600);
+        verify("3001", config, 15);
+        verify("3002", config, 25);
+        System.setProperty("Id", "3002");
+        String test = config.getString("TestProp");
+        assertNull("Property was not cleared by reload", test);
+    }
+
+    @Test
+    public void testFileReloadSchemaValidationError() throws Exception
+    {
+        System.getProperties().remove("Id");
+        factory.setFile(MULTI_RELOAD_FILE2);
+        CombinedConfiguration config = factory.getConfiguration(true);
+
+        // create a new configuration
+        File input = new File("target/test-classes/testMultiConfiguration_3001.xml");
+        File output = new File("target/test-classes/testwrite/testMultiConfiguration_3001.xml");
+        output.delete();
+        output.getParentFile().mkdir();
+        copyFile(input, output);
+
+        assertNotNull(config);
+        verify("3001", config, 15);
+        Thread.sleep(1100);
+        XMLConfiguration x = new XMLConfiguration();
+        x.setFile(output);
+        x.setAttributeSplittingDisabled(true);
+        x.setDelimiterParsingDisabled(true);
+        x.load();
+        x.setProperty("rowsPerPage", "test");
+        //Insure orginal timestamp and new timestamp aren't the same second.
+        Thread.sleep(1100);
+        x.save();
+        System.setProperty("Id", "3001");
+        try
+        {
+            config.getInt("rowsPerPage");
+            fail("No exception was thrown");
+        }
+        catch (Exception ex)
+        {
+
+        }
+
+        output.delete();
+    }
+
+    private void copyFile(File input, File output) throws IOException
+    {
+        Reader reader = new FileReader(input);
+        Writer writer = new FileWriter(output);
+        char[] buffer = new char[4096];
+        int n = 0;
+        while (-1 != (n = reader.read(buffer)))
+        {
+            writer.write(buffer, 0, n);
+        }
+        reader.close();
+        writer.close();
+    }
+
+    private void verify(String key, CombinedConfiguration config, int rows)
+    {
+        if (key == null)
+        {
+            System.getProperties().remove("Id");
+        }
+        else
+        {
+            System.setProperty("Id", key);
+        }
+        int actual = config.getInt("rowsPerPage");
+        assertTrue("expected: " + rows + " actual: " + actual, actual == rows);
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/commons/configuration/TestWebdavConfigurationBuilder.java b/src/test/java/org/apache/commons/configuration/TestWebdavConfigurationBuilder.java
new file mode 100644
index 0000000..0347552
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestWebdavConfigurationBuilder.java
@@ -0,0 +1,1059 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.configuration.beanutils.BeanHelper;
+import org.apache.commons.configuration.event.ConfigurationEvent;
+import org.apache.commons.configuration.event.ConfigurationListener;
+import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
+import org.apache.commons.configuration.tree.DefaultConfigurationNode;
+import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
+import org.apache.commons.vfs2.FileName;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemManager;
+import org.apache.commons.vfs2.FileSystemOptions;
+import org.apache.commons.vfs2.VFS;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for DefaultConfigurationBuilder.
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: TestWebdavConfigurationBuilder.java 1225331 2011-12-28 20:55:49Z oheger $
+ */
+public class TestWebdavConfigurationBuilder
+        implements FileOptionsProvider, ConfigurationListener
+{
+    /** Test configuration definition file. */
+    private static final String TEST_FILE =
+            "testDigesterConfiguration.xml";
+
+    private static final String ADDITIONAL_FILE =
+            "testDigesterConfiguration2.xml";
+
+    private static final String OPTIONAL_FILE =
+            "testDigesterOptionalConfiguration.xml";
+
+    private static final String OPTIONALEX_FILE =
+            "testDigesterOptionalConfigurationEx.xml";
+
+    private static final String MULTI_FILE =
+            "testDigesterConfiguration3.xml";
+
+    private static final String INIT_FILE =
+            "testComplexInitialization.xml";
+
+    private static final String CLASS_FILE =
+            "testExtendedClass.xml";
+
+    private static final String PROVIDER_FILE =
+            "testConfigurationProvider.xml";
+
+    private static final String EXTENDED_PROVIDER_FILE =
+            "testExtendedXMLConfigurationProvider.xml";
+
+    private static final String GLOBAL_LOOKUP_FILE =
+            "testGlobalLookup.xml";
+
+    private static final String SYSTEM_PROPS_FILE =
+            "testSystemProperties.xml";
+
+    private static final String VALIDATION_FILE =
+            "testValidation.xml";
+
+    private static final String MULTI_TENENT_FILE =
+            "testMultiTenentConfigurationBuilder.xml";
+
+    private static final String FILERELOAD2_FILE =
+            "testFileReloadConfigurationBuilder2.xml";
+
+    private static final String FILERELOAD_1001_FILE =
+            "testwrite/testMultiConfiguration_1001.xml";
+
+    private static final String FILERELOAD_1002_FILE =
+            "testwrite/testMultiConfiguration_1002.xml";
+
+    private static final String TEST_PROPERTIES = "test.properties.xml";
+
+    private static final String TEST_SAVE = "testsave.xml";
+
+    /** Constant for the name of an optional configuration.*/
+    private static final String OPTIONAL_NAME = "optionalConfig";
+
+    private Map<String, Object> options;
+
+    /** true when a file is changed */
+    private boolean configChanged = false;
+
+    /** Stores the object to be tested. */
+    DefaultConfigurationBuilder factory;
+
+    private String getBasePath()
+    {
+        String path = System.getProperty("test.webdav.base");
+        assertNotNull("No base url provided", path);
+        return path;
+    }
+
+    @Before
+    public void setUp() throws Exception
+    {
+        System.setProperty("java.naming.factory.initial",
+                        "org.apache.commons.configuration.MockInitialContextFactory");
+        System.setProperty("test_file_xml", "test.xml");
+        System.setProperty("test_file_combine", "testcombine1.xml");
+        System.setProperty("basePath", getBasePath());
+        FileSystem fs = new VFSFileSystem();
+        fs.setFileOptionsProvider(this);
+        FileSystem.setDefaultFileSystem(fs);
+        factory = new DefaultConfigurationBuilder();
+        factory.setBasePath(getBasePath());
+        factory.clearErrorListeners();  // avoid exception messages
+    }
+
+    @After
+    public void tearDown() throws Exception
+    {
+        FileSystem.resetDefaultFileSystem();
+    }
+
+    /**
+     * Tests the isReservedNode() method of ConfigurationDeclaration.
+     */
+    @Test
+    public void testConfigurationDeclarationIsReserved()
+    {
+        DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
+                factory, factory);
+        DefaultConfigurationNode parent = new DefaultConfigurationNode();
+        DefaultConfigurationNode nd = new DefaultConfigurationNode("at");
+        parent.addAttribute(nd);
+        assertTrue("Attribute at not recognized", decl.isReservedNode(nd));
+        nd = new DefaultConfigurationNode("optional");
+        parent.addAttribute(nd);
+        assertTrue("Attribute optional not recognized", decl.isReservedNode(nd));
+        nd = new DefaultConfigurationNode("config-class");
+        parent.addAttribute(nd);
+        assertTrue("Inherited attribute not recognized", decl
+                .isReservedNode(nd));
+        nd = new DefaultConfigurationNode("different");
+        parent.addAttribute(nd);
+        assertFalse("Wrong reserved attribute", decl.isReservedNode(nd));
+        nd = new DefaultConfigurationNode("at");
+        parent.addChild(nd);
+        assertFalse("Node type not evaluated", decl.isReservedNode(nd));
+    }
+
+    /**
+     * Tests if the at attribute is correctly detected as reserved attribute.
+     */
+    @Test
+    public void testConfigurationDeclarationIsReservedAt()
+    {
+        checkOldReservedAttribute("at");
+    }
+
+    /**
+     * Tests if the optional attribute is correctly detected as reserved
+     * attribute.
+     */
+    @Test
+    public void testConfigurationDeclarationIsReservedOptional()
+    {
+        checkOldReservedAttribute("optional");
+    }
+
+    /**
+     * Tests if special reserved attributes are recognized by the
+     * isReservedNode() method. For compatibility reasons the attributes "at"
+     * and "optional" are also treated as reserved attributes, but only if there
+     * are no corresponding attributes with the "config-" prefix.
+     *
+     * @param name the attribute name
+     */
+    private void checkOldReservedAttribute(String name)
+    {
+        DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
+                factory, factory);
+        DefaultConfigurationNode parent = new DefaultConfigurationNode();
+        DefaultConfigurationNode nd = new DefaultConfigurationNode("config-"
+                + name);
+        parent.addAttribute(nd);
+        assertTrue("config-" + name + " attribute not recognized", decl
+                .isReservedNode(nd));
+        DefaultConfigurationNode nd2 = new DefaultConfigurationNode(name);
+        parent.addAttribute(nd2);
+        assertFalse(name + " is reserved though config- exists", decl
+                .isReservedNode(nd2));
+        assertTrue("config- attribute not recognized when " + name + " exists",
+                decl.isReservedNode(nd));
+    }
+
+    /**
+     * Tests access to certain reserved attributes of a
+     * ConfigurationDeclaration.
+     */
+    @Test
+    public void testConfigurationDeclarationGetAttributes()
+    {
+        factory.addProperty("xml.fileName", "test.xml");
+        DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
+                factory, factory.configurationAt("xml"));
+        assertNull("Found an at attribute", decl.getAt());
+        assertFalse("Found an optional attribute", decl.isOptional());
+        factory.addProperty("xml[@config-at]", "test1");
+        assertEquals("Wrong value of at attribute", "test1", decl.getAt());
+        factory.addProperty("xml[@at]", "test2");
+        assertEquals("Wrong value of config-at attribute", "test1", decl.getAt());
+        factory.clearProperty("xml[@config-at]");
+        assertEquals("Old at attribute not detected", "test2", decl.getAt());
+        factory.addProperty("xml[@config-optional]", "true");
+        assertTrue("Wrong value of optional attribute", decl.isOptional());
+        factory.addProperty("xml[@optional]", "false");
+        assertTrue("Wrong value of config-optional attribute", decl.isOptional());
+        factory.clearProperty("xml[@config-optional]");
+        factory.setProperty("xml[@optional]", Boolean.TRUE);
+        assertTrue("Old optional attribute not detected", decl.isOptional());
+    }
+
+    /**
+     * Tests whether an invalid value of an optional attribute is detected.
+     */
+    @Test(expected = ConfigurationRuntimeException.class)
+    public void testConfigurationDeclarationOptionalAttributeInvalid()
+    {
+        factory.addProperty("xml.fileName", "test.xml");
+        DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
+                factory, factory.configurationAt("xml"));
+        factory.setProperty("xml[@optional]", "invalid value");
+        decl.isOptional();
+    }
+
+    /**
+     * Tests adding a new configuration provider.
+     */
+    @Test
+    public void testAddConfigurationProvider()
+    {
+        DefaultConfigurationBuilder.ConfigurationProvider provider = new DefaultConfigurationBuilder.ConfigurationProvider();
+        assertNull("Provider already registered", factory
+                .providerForTag("test"));
+        factory.addConfigurationProvider("test", provider);
+        assertSame("Provider not registered", provider, factory
+                .providerForTag("test"));
+    }
+
+    /**
+     * Tries to register a null configuration provider. This should cause an
+     * exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testAddConfigurationProviderNull()
+    {
+        factory.addConfigurationProvider("test", null);
+    }
+
+    /**
+     * Tries to register a configuration provider for a null tag. This should
+     * cause an exception to be thrown.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testAddConfigurationProviderNullTag()
+    {
+        factory.addConfigurationProvider(null,
+                new DefaultConfigurationBuilder.ConfigurationProvider());
+    }
+
+    /**
+     * Tests removing configuration providers.
+     */
+    @Test
+    public void testRemoveConfigurationProvider()
+    {
+        assertNull("Removing unknown provider", factory
+                .removeConfigurationProvider("test"));
+        assertNull("Removing provider for null tag", factory
+                .removeConfigurationProvider(null));
+        DefaultConfigurationBuilder.ConfigurationProvider provider = new DefaultConfigurationBuilder.ConfigurationProvider();
+        factory.addConfigurationProvider("test", provider);
+        assertSame("Failed to remove provider", provider, factory
+                .removeConfigurationProvider("test"));
+        assertNull("Provider still registered", factory.providerForTag("test"));
+    }
+
+    /**
+     * Tests creating a configuration object from a configuration declaration.
+     */
+    @Test
+    public void testConfigurationBeanFactoryCreateBean()
+    {
+        factory.addConfigurationProvider("test",
+                new DefaultConfigurationBuilder.ConfigurationProvider(
+                        PropertiesConfiguration.class));
+        factory.addProperty("test[@throwExceptionOnMissing]", "true");
+        DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
+                factory, factory.configurationAt("test"));
+        PropertiesConfiguration conf = (PropertiesConfiguration) BeanHelper
+                .createBean(decl);
+        assertTrue("Property was not initialized", conf
+                .isThrowExceptionOnMissing());
+    }
+
+    /**
+     * Tests creating a configuration object from an unknown tag. This should
+     * cause an exception.
+     */
+    @Test(expected = ConfigurationRuntimeException.class)
+    public void testConfigurationBeanFactoryCreateUnknownTag()
+    {
+        factory.addProperty("test[@throwExceptionOnMissing]", "true");
+        DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
+                factory, factory.configurationAt("test"));
+        BeanHelper.createBean(decl);
+    }
+
+    /**
+     * Tests loading a simple configuration definition file.
+     */
+    @Test
+    public void testLoadConfiguration() throws ConfigurationException
+    {
+        factory.setFileName(TEST_FILE);
+        checkConfiguration();
+    }
+
+    /**
+     * Tests the file constructor.
+     */
+    @Test
+    public void testLoadConfigurationFromFile() throws ConfigurationException
+    {
+        factory = new DefaultConfigurationBuilder(getBasePath() + TEST_FILE);
+        checkConfiguration();
+    }
+
+    /**
+     * This test doesn't test DefaultConfigurationBuilder. It tests saving a file
+     * using Webdav file options.
+     */
+    @Test
+    public void testSaveConfiguration() throws ConfigurationException
+    {
+        options = new HashMap<String, Object>();
+        options.put(FileOptionsProvider.VERSIONING, Boolean.TRUE);
+        options.put(FileOptionsProvider.CURRENT_USER, "TestUser");
+        XMLConfiguration conf = new XMLConfiguration();
+        conf.setFileName(getBasePath() + TEST_PROPERTIES);
+        conf.load();
+        conf.save(getBasePath() + TEST_SAVE);
+        XMLConfiguration checkConfig = new XMLConfiguration();
+        checkConfig.setFileName(getBasePath() + TEST_SAVE);
+        checkSavedConfig(conf, checkConfig);
+        options = null;
+    }
+
+    /**
+     * Tests if the configuration was correctly created by the factory.
+     */
+    private void checkConfiguration() throws ConfigurationException
+    {
+        CombinedConfiguration compositeConfiguration = (CombinedConfiguration) factory
+                .getConfiguration();
+
+        assertEquals("Number of configurations", 3, compositeConfiguration
+                .getNumberOfConfigurations());
+        assertEquals(PropertiesConfiguration.class, compositeConfiguration
+                .getConfiguration(0).getClass());
+        assertEquals(XMLPropertiesConfiguration.class, compositeConfiguration
+                .getConfiguration(1).getClass());
+        assertEquals(XMLConfiguration.class, compositeConfiguration
+                .getConfiguration(2).getClass());
+
+        // check the first configuration
+        PropertiesConfiguration pc = (PropertiesConfiguration) compositeConfiguration
+                .getConfiguration(0);
+        assertNotNull("Make sure we have a fileName: " + pc.getFileName(), pc
+                .getFileName());
+
+        // check some properties
+        checkProperties(compositeConfiguration);
+    }
+
+    /**
+     * Checks if the passed in configuration contains the expected properties.
+     *
+     * @param compositeConfiguration the configuration to check
+     */
+    private void checkProperties(Configuration compositeConfiguration)
+    {
+        assertTrue("Make sure we have loaded our key", compositeConfiguration
+                .getBoolean("test.boolean"));
+        assertEquals("I'm complex!", compositeConfiguration
+                .getProperty("element2.subelement.subsubelement"));
+        assertEquals("property in the XMLPropertiesConfiguration", "value1",
+                compositeConfiguration.getProperty("key1"));
+    }
+
+    /**
+     * Tests loading a configuration definition file with an additional section.
+     */
+    @Test
+    public void testLoadAdditional() throws ConfigurationException
+    {
+        factory.setFileName(ADDITIONAL_FILE);
+        CombinedConfiguration compositeConfiguration = (CombinedConfiguration) factory
+                .getConfiguration();
+        assertEquals("Verify how many configs", 2, compositeConfiguration
+                .getNumberOfConfigurations());
+
+        // Test if union was constructed correctly
+        Object prop = compositeConfiguration.getProperty("tables.table.name");
+        assertTrue(prop instanceof Collection);
+        assertEquals(3, ((Collection<?>) prop).size());
+        assertEquals("users", compositeConfiguration
+                .getProperty("tables.table(0).name"));
+        assertEquals("documents", compositeConfiguration
+                .getProperty("tables.table(1).name"));
+        assertEquals("tasks", compositeConfiguration
+                .getProperty("tables.table(2).name"));
+
+        prop = compositeConfiguration
+                .getProperty("tables.table.fields.field.name");
+        assertTrue(prop instanceof Collection);
+        assertEquals(17, ((Collection<?>) prop).size());
+
+        assertEquals("smtp.mydomain.org", compositeConfiguration
+                .getString("mail.host.smtp"));
+        assertEquals("pop3.mydomain.org", compositeConfiguration
+                .getString("mail.host.pop"));
+
+        // This was overridden
+        assertEquals("masterOfPost", compositeConfiguration
+                .getString("mail.account.user"));
+        assertEquals("topsecret", compositeConfiguration
+                .getString("mail.account.psswd"));
+
+        // This was overridden, too, but not in additional section
+        assertEquals("enhanced factory", compositeConfiguration
+                .getString("test.configuration"));
+    }
+
+    /**
+     * Tests whether a default log error listener is registered at the builder
+     * instance.
+     */
+    @Test
+    public void testLogErrorListener()
+    {
+        assertEquals("No default error listener registered", 1,
+                new DefaultConfigurationBuilder().getErrorListeners().size());
+    }
+
+    /**
+     * Tests loading a definition file that contains optional configurations.
+     */
+    @Test
+    public void testLoadOptional() throws Exception
+    {
+        factory.setFileName(OPTIONAL_FILE);
+        Configuration config = factory.getConfiguration();
+        assertTrue(config.getBoolean("test.boolean"));
+        assertEquals("value", config.getProperty("element"));
+    }
+
+    /**
+     * Tests whether loading a failing optional configuration causes an error
+     * event.
+     */
+    @Test
+    public void testLoadOptionalErrorEvent() throws Exception
+    {
+        factory.clearErrorListeners();
+        ConfigurationErrorListenerImpl listener = new ConfigurationErrorListenerImpl();
+        factory.addErrorListener(listener);
+        prepareOptionalTest("configuration", false);
+        listener.verify(DefaultConfigurationBuilder.EVENT_ERR_LOAD_OPTIONAL,
+                OPTIONAL_NAME, null);
+    }
+
+    /**
+     * Tests loading a definition file with optional and non optional
+     * configuration sources. One non optional does not exist, so this should
+     * cause an exception.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testLoadOptionalWithException() throws ConfigurationException
+    {
+        factory.setFileName(OPTIONALEX_FILE);
+        factory.getConfiguration();
+    }
+
+    /**
+     * Tries to load a configuration file with an optional, non file-based
+     * configuration. The optional attribute should work for other configuration
+     * classes, too.
+     */
+    @Test
+    public void testLoadOptionalNonFileBased() throws ConfigurationException
+    {
+        CombinedConfiguration config = prepareOptionalTest("configuration", false);
+        assertTrue("Configuration not empty", config.isEmpty());
+        assertEquals("Wrong number of configurations", 0, config
+                .getNumberOfConfigurations());
+    }
+
+    /**
+     * Tests loading an embedded optional configuration builder with the force
+     * create attribute.
+     */
+    @Test
+    public void testLoadOptionalBuilderForceCreate()
+            throws ConfigurationException
+    {
+        CombinedConfiguration config = prepareOptionalTest("configuration",
+                true);
+        assertEquals("Wrong number of configurations", 1, config
+                .getNumberOfConfigurations());
+        assertTrue(
+                "Wrong optional configuration type",
+                config.getConfiguration(OPTIONAL_NAME) instanceof CombinedConfiguration);
+    }
+
+    /**
+     * Tests loading an optional configuration with the force create attribute
+     * set. The provider will always throw an exception. In this case the
+     * configuration will not be added to the resulting combined configuration.
+     */
+    @Test
+    public void testLoadOptionalForceCreateWithException()
+            throws ConfigurationException
+    {
+        factory.addConfigurationProvider("test",
+                new DefaultConfigurationBuilder.ConfigurationBuilderProvider()
+                {
+                    // Throw an exception here, too
+                    @Override
+                    public AbstractConfiguration getEmptyConfiguration(
+                            DefaultConfigurationBuilder.ConfigurationDeclaration decl) throws Exception
+                    {
+                        throw new Exception("Unable to create configuration!");
+                    }
+                });
+        CombinedConfiguration config = prepareOptionalTest("test", true);
+        assertEquals("Optional configuration could be created", 0, config
+                .getNumberOfConfigurations());
+    }
+
+    /**
+     * Prepares a test for loading a configuration definition file with an
+     * optional configuration declaration.
+     *
+     * @param tag the tag name with the optional configuration
+     * @param force the forceCreate attribute
+     * @return the combined configuration obtained from the builder
+     * @throws ConfigurationException if an error occurs
+     */
+    private CombinedConfiguration prepareOptionalTest(String tag, boolean force)
+            throws ConfigurationException
+    {
+        String prefix = "override." + tag;
+        factory.addProperty(prefix + "[@fileName]", "nonExisting.xml");
+        factory.addProperty(prefix + "[@config-optional]", Boolean.TRUE);
+        factory.addProperty(prefix + "[@config-name]", OPTIONAL_NAME);
+        if (force)
+        {
+            factory.addProperty(prefix + "[@config-forceCreate]", Boolean.TRUE);
+        }
+        return factory.getConfiguration(false);
+    }
+
+    /**
+     * Tests loading a definition file with multiple different sources.
+     */
+    @Test
+    public void testLoadDifferentSources() throws ConfigurationException
+    {
+        factory.setFileName(MULTI_FILE);
+        Configuration config = factory.getConfiguration();
+        assertFalse(config.isEmpty());
+        assertTrue(config instanceof CombinedConfiguration);
+        CombinedConfiguration cc = (CombinedConfiguration) config;
+        assertEquals("Wrong number of configurations", 1, cc
+                .getNumberOfConfigurations());
+
+        assertNotNull(config
+                .getProperty("tables.table(0).fields.field(2).name"));
+        assertNotNull(config.getProperty("element2.subelement.subsubelement"));
+        assertEquals("value", config.getProperty("element3"));
+        assertEquals("foo", config.getProperty("element3[@name]"));
+        assertNotNull(config.getProperty("mail.account.user"));
+
+        // test JNDIConfiguration
+        assertNotNull(config.getProperty("test.onlyinjndi"));
+        assertTrue(config.getBoolean("test.onlyinjndi"));
+
+        Configuration subset = config.subset("test");
+        assertNotNull(subset.getProperty("onlyinjndi"));
+        assertTrue(subset.getBoolean("onlyinjndi"));
+
+        // test SystemConfiguration
+        assertNotNull(config.getProperty("java.version"));
+        assertEquals(System.getProperty("java.version"), config
+                .getString("java.version"));
+    }
+
+    /**
+     * Tests if the base path is correctly evaluated.
+     */
+    @Test
+    public void testSetConfigurationBasePath() throws ConfigurationException
+    {
+        factory.addProperty("properties[@fileName]", "test.properties");
+        File deepDir = new File("conf/config/deep");
+        factory.setConfigurationBasePath(deepDir.getAbsolutePath());
+
+        Configuration config = factory.getConfiguration(false);
+        assertEquals("Wrong property value", "somevalue", config
+                .getString("somekey"));
+    }
+
+    /**
+     * Tests reading a configuration definition file that contains complex
+     * initialization of properties of the declared configuration sources.
+     */
+    @Test
+    public void testComplexInitialization() throws ConfigurationException
+    {
+        factory.setFileName(INIT_FILE);
+        CombinedConfiguration cc = (CombinedConfiguration) factory
+                .getConfiguration();
+
+        assertEquals("System property not found", "test.xml",
+                cc.getString("test_file_xml"));
+        PropertiesConfiguration c1 = (PropertiesConfiguration) cc
+                .getConfiguration(1);
+        assertTrue(
+                "Reloading strategy was not set",
+                c1.getReloadingStrategy() instanceof FileChangedReloadingStrategy);
+        assertEquals("Refresh delay was not set", 10000,
+                ((FileChangedReloadingStrategy) c1.getReloadingStrategy())
+                        .getRefreshDelay());
+
+        Configuration xmlConf = cc.getConfiguration("xml");
+        assertEquals("Property not found", "I'm complex!", xmlConf
+                .getString("element2/subelement/subsubelement"));
+        assertEquals("List index not found", "two", xmlConf
+                .getString("list[0]/item[1]"));
+        assertEquals("Property in combiner file not found", "yellow", cc
+                .getString("/gui/selcolor"));
+
+        assertTrue("Delimiter flag was not set", cc
+                .isDelimiterParsingDisabled());
+        assertTrue("Expression engine was not set",
+                cc.getExpressionEngine() instanceof XPathExpressionEngine);
+    }
+
+    /**
+     * Tests if the returned combined configuration has the expected structure.
+     */
+    @Test
+    public void testCombinedConfiguration() throws ConfigurationException
+    {
+        factory.setFileName(INIT_FILE);
+        CombinedConfiguration cc = (CombinedConfiguration) factory
+                .getConfiguration();
+        assertNotNull("Properties configuration not found", cc
+                .getConfiguration("properties"));
+        assertNotNull("XML configuration not found", cc.getConfiguration("xml"));
+        assertEquals("Wrong number of contained configs", 4, cc
+                .getNumberOfConfigurations());
+
+        CombinedConfiguration cc2 = (CombinedConfiguration) cc
+                .getConfiguration(DefaultConfigurationBuilder.ADDITIONAL_NAME);
+        assertNotNull("No additional configuration found", cc2);
+        Set<String> names = cc2.getConfigurationNames();
+        assertEquals("Wrong number of contained additional configs", 2, names
+                .size());
+        assertTrue("Config 1 not contained", names.contains("combiner1"));
+        assertTrue("Config 2 not contained", names.contains("combiner2"));
+    }
+
+    /**
+     * Tests the structure of the returned combined configuration if there is no
+     * additional section.
+     */
+    @Test
+    public void testCombinedConfigurationNoAdditional()
+            throws ConfigurationException
+    {
+        factory.setFileName(TEST_FILE);
+        CombinedConfiguration cc = factory.getConfiguration(true);
+        assertNull("Additional configuration was found", cc
+                .getConfiguration(DefaultConfigurationBuilder.ADDITIONAL_NAME));
+    }
+
+    /**
+     * Tests whether the list node definition was correctly processed.
+     */
+    @Test
+    public void testCombinedConfigurationListNodes()
+            throws ConfigurationException
+    {
+        factory.setFileName(INIT_FILE);
+        CombinedConfiguration cc = factory.getConfiguration(true);
+        Set<String> listNodes = cc.getNodeCombiner().getListNodes();
+        assertEquals("Wrong number of list nodes", 2, listNodes.size());
+        assertTrue("table node not a list node", listNodes.contains("table"));
+        assertTrue("list node not a list node", listNodes.contains("list"));
+
+        CombinedConfiguration cca = (CombinedConfiguration) cc
+                .getConfiguration(DefaultConfigurationBuilder.ADDITIONAL_NAME);
+        listNodes = cca.getNodeCombiner().getListNodes();
+        assertTrue("Found list nodes for additional combiner", listNodes
+                .isEmpty());
+    }
+
+    /**
+     * Tests whether XML settings can be inherited.
+     */
+    @Test
+    public void testLoadXMLWithSettings() throws ConfigurationException,
+            IOException
+    {
+        File confDir = new File("conf");
+        File targetDir = new File("target");
+        File testXMLSource = new File(confDir, "testDtd.xml");
+        File testXMLValidationSource = new File(confDir,
+                "testValidateInvalid.xml");
+        File testSavedXML = new File(targetDir, "testSave.xml");
+        File testSavedFactory = new File(targetDir, "testSaveFactory.xml");
+        File dtdFile = new File(confDir, "properties.dtd");
+        final String publicId = "http://commons.apache.org/test.dtd";
+
+        XMLConfiguration config = new XMLConfiguration(testXMLSource);
+        config.setPublicID(publicId);
+        config.save(testSavedXML);
+        factory.addProperty("xml[@fileName]", testSavedXML.getAbsolutePath());
+        factory.addProperty("xml(0)[@validating]", "true");
+        factory.addProperty("xml(-1)[@fileName]", testXMLValidationSource
+                .getAbsolutePath());
+        factory.addProperty("xml(1)[@config-optional]", "true");
+        factory.addProperty("xml(1)[@validating]", "true");
+        factory.save(testSavedFactory);
+
+        factory = new DefaultConfigurationBuilder();
+        factory.setFile(testSavedFactory);
+        factory.registerEntityId(publicId, dtdFile.toURI().toURL());
+        factory.clearErrorListeners();
+        Configuration c = factory.getConfiguration();
+        assertEquals("Wrong property value", "value1", c.getString("entry(0)"));
+        assertFalse("Invalid XML source was loaded", c
+                .containsKey("table.name"));
+
+        testSavedXML.delete();
+        testSavedFactory.delete();
+    }
+
+    /**
+     * Tests loading a configuration definition file that defines a custom
+     * result class.
+     */
+    @Test
+    public void testExtendedClass() throws ConfigurationException
+    {
+        factory.setFileName(CLASS_FILE);
+        CombinedConfiguration cc = factory.getConfiguration(true);
+        String prop = (String)cc.getProperty("test");
+        assertEquals("Expected 'Extended', actual '" + prop + "'", "Extended", prop);
+        assertTrue("Wrong result class: " + cc.getClass(),
+                cc instanceof TestDefaultConfigurationBuilder.ExtendedCombinedConfiguration);
+    }
+
+    /**
+     * Tests loading a configuration definition file that defines new providers.
+     */
+    @Test
+    public void testConfigurationProvider() throws ConfigurationException
+    {
+        factory.setFileName(PROVIDER_FILE);
+        factory.getConfiguration(true);
+        DefaultConfigurationBuilder.ConfigurationProvider provider = factory
+                .providerForTag("test");
+        assertNotNull("Provider 'test' not registered", provider);
+    }
+
+    /**
+     * Tests loading a configuration definition file that defines new providers.
+     */
+    @Test
+    public void testExtendedXMLConfigurationProvider() throws ConfigurationException
+    {
+        factory.setFileName(EXTENDED_PROVIDER_FILE);
+        CombinedConfiguration cc = factory.getConfiguration(true);
+        DefaultConfigurationBuilder.ConfigurationProvider provider = factory
+                .providerForTag("test");
+        assertNotNull("Provider 'test' not registered", provider);
+        Configuration config = cc.getConfiguration("xml");
+        assertNotNull("Test configuration not present", config);
+        assertTrue("Configuration is not ExtendedXMLConfiguration, is " +
+                config.getClass().getName(),
+                config instanceof TestDefaultConfigurationBuilder.ExtendedXMLConfiguration);
+    }
+
+    @Test
+    public void testGlobalLookup() throws Exception
+    {
+        factory.setFileName(GLOBAL_LOOKUP_FILE);
+        CombinedConfiguration cc = factory.getConfiguration(true);
+        String value = cc.getInterpolator().lookup("test:test_key");
+        assertNotNull("The test key was not located", value);
+        assertEquals("Incorrect value retrieved","test.value",value);
+    }
+
+    @Test
+    public void testSystemProperties() throws Exception
+    {
+        factory.setFileName(SYSTEM_PROPS_FILE);
+        factory.getConfiguration(true);
+        String value = System.getProperty("key1");
+        assertNotNull("The test key was not located", value);
+        assertEquals("Incorrect value retrieved","value1",value);
+    }
+
+    @Test
+    public void testValidation() throws Exception
+    {
+        factory.setFileName(VALIDATION_FILE);
+        factory.getConfiguration(true);
+        String value = System.getProperty("key1");
+        assertNotNull("The test key was not located", value);
+        assertEquals("Incorrect value retrieved","value1",value);
+    }
+
+    @Test
+    public void testMultiTenentConfiguration() throws Exception
+    {
+        factory.setFileName(MULTI_TENENT_FILE);
+        System.getProperties().remove("Id");
+
+        CombinedConfiguration config = factory.getConfiguration(true);
+        assertTrue("Incorrect configuration", config instanceof DynamicCombinedConfiguration);
+
+        verify("1001", config, 15);
+        verify("1002", config, 25);
+        verify("1003", config, 35);
+        verify("1004", config, 50);
+        verify("1005", config, 50);
+    }
+
+    @Test
+    public void testMultiTenentConfiguration2() throws Exception
+    {
+        factory.setFileName(MULTI_TENENT_FILE);
+        System.setProperty("Id", "1004");
+
+        CombinedConfiguration config = factory.getConfiguration(true);
+        assertTrue("Incorrect configuration", config instanceof DynamicCombinedConfiguration);
+
+        verify("1001", config, 15);
+        verify("1002", config, 25);
+        verify("1003", config, 35);
+        verify("1004", config, 50);
+        verify("1005", config, 50);
+    }
+
+    @Test
+    public void testMultiTenentConfiguration3() throws Exception
+    {
+        factory.setFileName(MULTI_TENENT_FILE);
+        System.setProperty("Id", "1005");
+
+        CombinedConfiguration config = factory.getConfiguration(true);
+        assertTrue("Incorrect configuration", config instanceof DynamicCombinedConfiguration);
+
+        verify("1001", config, 15);
+        verify("1002", config, 25);
+        verify("1003", config, 35);
+        verify("1004", config, 50);
+        verify("1005", config, 50);
+    }
+
+    @Test
+    public void testFileChanged() throws Exception
+    {
+        // create a new configuration
+        File input = new File("target/test-classes/testMultiConfiguration_1001.xml");
+        FileObject output = getFile(getBasePath() + FILERELOAD_1001_FILE);
+        output.delete();
+        output.getParent().createFolder();
+        copyFile(input, output);
+
+        factory.setFileName(getBasePath() + FILERELOAD2_FILE);
+        System.getProperties().remove("Id");
+
+        CombinedConfiguration config = factory.getConfiguration(true);
+        assertNotNull(config);
+        config.addConfigurationListener(this);
+        verify("1001", config, 15);
+
+        // Allow time for FileMonitor to set up.
+        Thread.sleep(1000);
+        XMLConfiguration x = new XMLConfiguration(getBasePath() + FILERELOAD_1001_FILE);
+        x.setProperty("rowsPerPage", "50");
+        x.save();
+        // Let FileMonitor detect the change.
+        //Thread.sleep(2000);
+        waitForChange();
+        verify("1001", config, 50);
+        output.delete();
+    }
+
+    @Test
+    public void testFileChanged2() throws Exception
+    {
+        // create a new configuration
+        File input = new File("target/test-classes/testMultiConfiguration_1002.xml");
+        FileObject output = getFile(getBasePath() + FILERELOAD_1002_FILE);
+        output.delete();
+
+        factory.setFileName(getBasePath() + FILERELOAD2_FILE);
+        System.getProperties().remove("Id");
+
+        CombinedConfiguration config = factory.getConfiguration(true);
+        assertNotNull(config);
+        config.addConfigurationListener(this);
+
+        verify("1002", config, 50);
+        Thread.sleep(1000);
+
+        output.getParent().createFolder();
+        copyFile(input, output);
+
+        // Allow time for the monitor to notice the change.
+        //Thread.sleep(2000);
+        waitForChange();
+        verify("1002", config, 25);
+        output.delete();
+    }
+
+    private void verify(String key, CombinedConfiguration config, int rows)
+    {
+        System.setProperty("Id", key);
+        int actual = config.getInt("rowsPerPage");
+        assertTrue("expected: " + rows + " actual: " + actual, actual == rows);
+    }
+
+    public Map<String, Object> getOptions()
+    {
+        return this.options;
+    }
+
+    /**
+     * Helper method for checking if a save operation was successful. Loads a
+     * saved configuration and then tests against a reference configuration.
+     * @param conf the original configuration
+     * @param newConfig the configuration to check
+     * @throws ConfigurationException if an error occurs
+     */
+    private void checkSavedConfig(XMLConfiguration conf, FileConfiguration newConfig)
+        throws ConfigurationException
+    {
+        newConfig.load();
+        ConfigurationAssert.assertEquals(conf, newConfig);
+    }
+
+    private FileObject getFile(String fileName) throws Exception
+    {
+        FileSystemManager manager = VFS.getManager();
+        FileName file = manager.resolveURI(fileName);
+        FileName base = file.getParent();
+        FileName path = manager.resolveName(base, file.getBaseName());
+        FileSystemOptions opts = new FileSystemOptions();
+        return manager.resolveFile(path.getURI(), opts);
+    }
+
+    private void copyFile(File input, FileObject output) throws IOException
+    {
+        Reader reader = new FileReader(input);
+        Writer writer = new OutputStreamWriter(output.getContent().getOutputStream());
+        char[] buffer = new char[4096];
+        int n = 0;
+        while (-1 != (n = reader.read(buffer)))
+        {
+            writer.write(buffer, 0, n);
+        }
+        reader.close();
+        writer.close();
+    }
+
+    private void waitForChange()
+    {
+        synchronized(this)
+        {
+            try
+            {
+                int count = 0;
+                while (!configChanged && count++ <= 3)
+                {
+                    this.wait(5000);
+                }
+            }
+            catch (InterruptedException ie)
+            {
+                throw new IllegalStateException("wait timed out");
+            }
+            finally
+            {
+                configChanged = false;
+            }
+        }
+    }
+
+    public void configurationChanged(ConfigurationEvent event)
+    {
+        if (event.getType() == AbstractFileConfiguration.EVENT_CONFIG_CHANGED)
+        {
+            synchronized(this)
+            {
+                configChanged = true;
+                this.notify();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/commons/configuration/TestXMLConfiguration.java b/src/test/java/org/apache/commons/configuration/TestXMLConfiguration.java
new file mode 100644
index 0000000..da1552e
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestXMLConfiguration.java
@@ -0,0 +1,1948 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.commons.configuration.reloading.FileAlwaysReloadingStrategy;
+import org.apache.commons.configuration.reloading.InvariantReloadingStrategy;
+import org.apache.commons.configuration.resolver.CatalogResolver;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
+import org.junit.Before;
+import org.junit.Test;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * test for loading and saving xml properties files
+ *
+ * @version $Id: TestXMLConfiguration.java 1534371 2013-10-21 21:07:12Z henning $
+ */
+public class TestXMLConfiguration
+{
+    /** XML Catalog */
+    private static final String CATALOG_FILES = ConfigurationAssert
+            .getTestFile("catalog.xml").getAbsolutePath();
+
+    /** Constant for the used encoding.*/
+    static final String ENCODING = "ISO-8859-1";
+
+    /** Constant for the test system ID.*/
+    static final String SYSTEM_ID = "properties.dtd";
+
+    /** Constant for the test public ID.*/
+    static final String PUBLIC_ID = "-//Commons Configuration//DTD Test Configuration 1.3//EN";
+
+    /** Constant for the DOCTYPE declaration.*/
+    static final String DOCTYPE_DECL = " PUBLIC \"" + PUBLIC_ID + "\" \"" + SYSTEM_ID + "\">";
+
+    /** Constant for the DOCTYPE prefix.*/
+    static final String DOCTYPE = "<!DOCTYPE ";
+
+    /** Constant for the transformer factory property.*/
+    static final String PROP_FACTORY = "javax.xml.transform.TransformerFactory";
+
+    /** The File that we test with */
+    private String testProperties = ConfigurationAssert.getTestFile("test.xml").getAbsolutePath();
+    private String testProperties2 = ConfigurationAssert.getTestFile("testDigesterConfigurationInclude1.xml").getAbsolutePath();
+    private String testBasePath = ConfigurationAssert.TEST_DIR.getAbsolutePath();
+    private File testSaveConf = ConfigurationAssert.getOutFile("testsave.xml");
+    private File testSaveFile = ConfigurationAssert.getOutFile("testsample2.xml");
+    private String testFile2 = ConfigurationAssert.getTestFile("sample.xml").getAbsolutePath();
+
+    /** Constant for the number of test threads. */
+    private static final int THREAD_COUNT = 5;
+
+    /** Constant for the number of loops in tests with multiple threads. */
+    private static final int LOOP_COUNT = 100;
+
+    private XMLConfiguration conf;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        conf = new XMLConfiguration();
+        conf.setFile(new File(testProperties));
+        conf.load();
+        removeTestFile();
+    }
+
+    @Test
+    public void testGetProperty()
+    {
+        assertEquals("value", conf.getProperty("element"));
+    }
+
+    @Test
+    public void testGetCommentedProperty()
+    {
+        assertEquals("", conf.getProperty("test.comment"));
+    }
+
+    @Test
+    public void testGetPropertyWithXMLEntity()
+    {
+        assertEquals("1<2", conf.getProperty("test.entity"));
+    }
+
+    @Test
+    public void testClearProperty() throws Exception
+    {
+        // test non-existent element
+        String key = "clearly";
+        conf.clearProperty(key);
+        assertNull(key, conf.getProperty(key));
+        assertNull(key, conf.getProperty(key));
+
+        // test single element
+        conf.load();
+        key = "clear.element";
+        conf.clearProperty(key);
+        assertNull(key, conf.getProperty(key));
+        assertNull(key, conf.getProperty(key));
+
+        // test single element with attribute
+        conf.load();
+        key = "clear.element2";
+        conf.clearProperty(key);
+        assertNull(key, conf.getProperty(key));
+        assertNull(key, conf.getProperty(key));
+        key = "clear.element2[@id]";
+        assertNotNull(key, conf.getProperty(key));
+        assertNotNull(key, conf.getProperty(key));
+
+        // test non-text/cdata element
+        conf.load();
+        key = "clear.comment";
+        conf.clearProperty(key);
+        assertNull(key, conf.getProperty(key));
+        assertNull(key, conf.getProperty(key));
+
+        // test cdata element
+        conf.load();
+        key = "clear.cdata";
+        conf.clearProperty(key);
+        assertNull(key, conf.getProperty(key));
+        assertNull(key, conf.getProperty(key));
+
+        // test multiple sibling elements
+        conf.load();
+        key = "clear.list.item";
+        conf.clearProperty(key);
+        assertNull(key, conf.getProperty(key));
+        assertNull(key, conf.getProperty(key));
+        key = "clear.list.item[@id]";
+        assertNotNull(key, conf.getProperty(key));
+        assertNotNull(key, conf.getProperty(key));
+
+        // test multiple, disjoined elements
+        conf.load();
+        key = "list.item";
+        conf.clearProperty(key);
+        assertNull(key, conf.getProperty(key));
+        assertNull(key, conf.getProperty(key));
+    }
+
+    @Test
+    public void testgetProperty() {
+        // test non-leaf element
+        Object property = conf.getProperty("clear");
+        assertNull(property);
+
+        // test non-existent element
+        property = conf.getProperty("e");
+        assertNull(property);
+
+        // test non-existent element
+        property = conf.getProperty("element3[@n]");
+        assertNull(property);
+
+        // test single element
+        property = conf.getProperty("element");
+        assertNotNull(property);
+        assertTrue(property instanceof String);
+        assertEquals("value", property);
+
+        // test single attribute
+        property = conf.getProperty("element3[@name]");
+        assertNotNull(property);
+        assertTrue(property instanceof String);
+        assertEquals("foo", property);
+
+        // test non-text/cdata element
+        property = conf.getProperty("test.comment");
+        assertEquals("", property);
+
+        // test cdata element
+        property = conf.getProperty("test.cdata");
+        assertNotNull(property);
+        assertTrue(property instanceof String);
+        assertEquals("<cdata value>", property);
+
+        // test multiple sibling elements
+        property = conf.getProperty("list.sublist.item");
+        assertNotNull(property);
+        assertTrue(property instanceof List);
+        List<?> list = (List<?>) property;
+        assertEquals(2, list.size());
+        assertEquals("five", list.get(0));
+        assertEquals("six", list.get(1));
+
+        // test multiple, disjoined elements
+        property = conf.getProperty("list.item");
+        assertNotNull(property);
+        assertTrue(property instanceof List);
+        list = (List<?>) property;
+        assertEquals(4, list.size());
+        assertEquals("one", list.get(0));
+        assertEquals("two", list.get(1));
+        assertEquals("three", list.get(2));
+        assertEquals("four", list.get(3));
+
+        // test multiple, disjoined attributes
+        property = conf.getProperty("list.item[@name]");
+        assertNotNull(property);
+        assertTrue(property instanceof List);
+        list = (List<?>) property;
+        assertEquals(2, list.size());
+        assertEquals("one", list.get(0));
+        assertEquals("three", list.get(1));
+    }
+
+    @Test
+    public void testGetAttribute()
+    {
+        assertEquals("element3[@name]", "foo", conf.getProperty("element3[@name]"));
+    }
+
+    @Test
+    public void testClearAttribute() throws Exception
+    {
+        // test non-existent attribute
+        String key = "clear[@id]";
+        conf.clearProperty(key);
+        assertNull(key, conf.getProperty(key));
+        assertNull(key, conf.getProperty(key));
+
+        // test single attribute
+        conf.load();
+        key = "clear.element2[@id]";
+        conf.clearProperty(key);
+        assertNull(key, conf.getProperty(key));
+        assertNull(key, conf.getProperty(key));
+        key = "clear.element2";
+        assertNotNull(key, conf.getProperty(key));
+        assertNotNull(key, conf.getProperty(key));
+
+        // test multiple, disjoined attributes
+        conf.load();
+        key = "clear.list.item[@id]";
+        conf.clearProperty(key);
+        assertNull(key, conf.getProperty(key));
+        assertNull(key, conf.getProperty(key));
+        key = "clear.list.item";
+        assertNotNull(key, conf.getProperty(key));
+        assertNotNull(key, conf.getProperty(key));
+    }
+
+    @Test
+    public void testSetAttribute()
+    {
+        // replace an existing attribute
+        conf.setProperty("element3[@name]", "bar");
+        assertEquals("element3[@name]", "bar", conf.getProperty("element3[@name]"));
+
+        // set a new attribute
+        conf.setProperty("foo[@bar]", "value");
+        assertEquals("foo[@bar]", "value", conf.getProperty("foo[@bar]"));
+
+        conf.setProperty("name1","value1");
+        assertEquals("value1",conf.getProperty("name1"));
+    }
+
+    @Test
+    public void testAddAttribute()
+    {
+        conf.addProperty("element3[@name]", "bar");
+
+        List<Object> list = conf.getList("element3[@name]");
+        assertNotNull("null list", list);
+        assertTrue("'foo' element missing", list.contains("foo"));
+        assertTrue("'bar' element missing", list.contains("bar"));
+        assertEquals("list size", 2, list.size());
+    }
+
+    @Test
+    public void testAddObjectAttribute()
+    {
+        conf.addProperty("test.boolean[@value]", Boolean.TRUE);
+        assertTrue("test.boolean[@value]", conf.getBoolean("test.boolean[@value]"));
+    }
+
+    /**
+     * Tests setting an attribute on the root element.
+     */
+    @Test
+    public void testSetRootAttribute() throws ConfigurationException
+    {
+        conf.setProperty("[@test]", "true");
+        assertEquals("Root attribute not set", "true", conf
+                .getString("[@test]"));
+        conf.save(testSaveConf);
+        XMLConfiguration checkConf = new XMLConfiguration();
+        checkConf.setFile(testSaveConf);
+        checkSavedConfig(checkConf);
+        assertTrue("Attribute not found after save", checkConf
+                .containsKey("[@test]"));
+        checkConf.setProperty("[@test]", "newValue");
+        checkConf.save();
+        conf = checkConf;
+        checkConf = new XMLConfiguration();
+        checkConf.setFile(testSaveConf);
+        checkSavedConfig(checkConf);
+        assertEquals("Attribute not modified after save", "newValue", checkConf
+                .getString("[@test]"));
+    }
+
+    /**
+     * Tests whether the configuration's root node is initialized with a
+     * reference to the corresponding XML element.
+     */
+    @Test
+    public void testGetRootReference()
+    {
+        assertNotNull("Root node has no reference", conf.getRootNode()
+                .getReference());
+    }
+
+    @Test
+    public void testAddList()
+    {
+        conf.addProperty("test.array", "value1");
+        conf.addProperty("test.array", "value2");
+
+        List<Object> list = conf.getList("test.array");
+        assertNotNull("null list", list);
+        assertTrue("'value1' element missing", list.contains("value1"));
+        assertTrue("'value2' element missing", list.contains("value2"));
+        assertEquals("list size", 2, list.size());
+    }
+
+    @Test
+    public void testGetComplexProperty()
+    {
+        assertEquals("I'm complex!", conf.getProperty("element2.subelement.subsubelement"));
+    }
+
+    @Test
+    public void testSettingFileNames()
+    {
+        conf = new XMLConfiguration();
+        conf.setFileName(testProperties);
+        assertEquals(testProperties.toString(), conf.getFileName());
+
+        conf.setBasePath(testBasePath);
+        conf.setFileName("hello.xml");
+        assertEquals("hello.xml", conf.getFileName());
+        assertEquals(testBasePath.toString(), conf.getBasePath());
+        assertEquals(new File(testBasePath, "hello.xml"), conf.getFile());
+
+        conf.setBasePath(testBasePath);
+        conf.setFileName("subdir/hello.xml");
+        assertEquals("subdir/hello.xml", conf.getFileName());
+        assertEquals(testBasePath.toString(), conf.getBasePath());
+        assertEquals(new File(testBasePath, "subdir/hello.xml"), conf.getFile());
+    }
+
+    @Test
+    public void testLoad() throws Exception
+    {
+        conf = new XMLConfiguration();
+        conf.setFileName(testProperties);
+        conf.load();
+
+        assertEquals("I'm complex!", conf.getProperty("element2.subelement.subsubelement"));
+    }
+
+    @Test
+    public void testLoadWithBasePath() throws Exception
+    {
+        conf = new XMLConfiguration();
+
+        conf.setFileName("test.xml");
+        conf.setBasePath(testBasePath);
+        conf.load();
+
+        assertEquals("I'm complex!", conf.getProperty("element2.subelement.subsubelement"));
+    }
+
+    /**
+     * Tests constructing an XMLConfiguration from a non existing file and
+     * later saving to this file.
+     */
+    @Test
+    public void testLoadAndSaveFromFile() throws Exception
+    {
+        // If the file does not exist, an empty config is created
+        conf = new XMLConfiguration(testSaveConf);
+        assertTrue(conf.isEmpty());
+        conf.addProperty("test", "yes");
+        conf.save();
+
+        conf = new XMLConfiguration(testSaveConf);
+        assertEquals("yes", conf.getString("test"));
+    }
+
+    /**
+     * Tests loading a configuration from a URL.
+     */
+    @Test
+    public void testLoadFromURL() throws Exception
+    {
+        URL url = new File(testProperties).toURI().toURL();
+        conf = new XMLConfiguration(url);
+        assertEquals("value", conf.getProperty("element"));
+        assertEquals(url, conf.getURL());
+    }
+
+    /**
+     * Tests loading from a stream.
+     */
+    @Test
+    public void testLoadFromStream() throws Exception
+    {
+        String xml = "<?xml version=\"1.0\"?><config><test>1</test></config>";
+        conf = new XMLConfiguration();
+        conf.load(new ByteArrayInputStream(xml.getBytes()));
+        assertEquals(1, conf.getInt("test"));
+
+        conf = new XMLConfiguration();
+        conf.load(new ByteArrayInputStream(xml.getBytes()), "UTF8");
+        assertEquals(1, conf.getInt("test"));
+    }
+
+    /**
+     * Tests loading a non well formed XML from a string.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testLoadInvalidXML() throws Exception
+    {
+        String xml = "<?xml version=\"1.0\"?><config><test>1</rest></config>";
+        conf = new XMLConfiguration();
+        conf.load(new StringReader(xml));
+    }
+
+    @Test
+    public void testSetProperty() throws Exception
+    {
+        conf.setProperty("element.string", "hello");
+
+        assertEquals("'element.string'", "hello", conf.getString("element.string"));
+        assertEquals("XML value of element.string", "hello", conf.getProperty("element.string"));
+    }
+
+    @Test
+    public void testAddProperty()
+    {
+        // add a property to a non initialized xml configuration
+        XMLConfiguration config = new XMLConfiguration();
+        config.addProperty("test.string", "hello");
+
+        assertEquals("'test.string'", "hello", config.getString("test.string"));
+    }
+
+    @Test
+    public void testAddObjectProperty()
+    {
+        // add a non string property
+        conf.addProperty("test.boolean", Boolean.TRUE);
+        assertTrue("'test.boolean'", conf.getBoolean("test.boolean"));
+    }
+
+    @Test
+    public void testSave() throws Exception
+    {
+        // add an array of strings to the configuration
+        conf.addProperty("string", "value1");
+        for (int i = 1; i < 5; i++)
+        {
+            conf.addProperty("test.array", "value" + i);
+        }
+
+        // add an array of strings in an attribute
+        for (int i = 1; i < 5; i++)
+        {
+           conf.addProperty("test.attribute[@array]", "value" + i);
+        }
+
+        // add comma delimited lists with escaped delimiters
+        conf.addProperty("split.list5", "a\\,b\\,c");
+        conf.setProperty("element3", "value\\,value1\\,value2");
+        conf.setProperty("element3[@name]", "foo\\,bar");
+
+        // save the configuration
+        conf.save(testSaveConf.getAbsolutePath());
+
+        // read the configuration and compare the properties
+        XMLConfiguration checkConfig = new XMLConfiguration();
+        checkConfig.setFileName(testSaveConf.getAbsolutePath());
+        checkSavedConfig(checkConfig);
+    }
+
+    /**
+     * Tests saving to a URL.
+     */
+    @Test
+    public void testSaveToURL() throws Exception
+    {
+        conf.save(testSaveConf.toURI().toURL());
+        XMLConfiguration checkConfig = new XMLConfiguration();
+        checkConfig.setFile(testSaveConf);
+        checkSavedConfig(checkConfig);
+    }
+
+    /**
+     * Tests saving to a stream.
+     */
+    @Test
+    public void testSaveToStream() throws Exception
+    {
+        assertNull(conf.getEncoding());
+        conf.setEncoding("UTF8");
+        FileOutputStream out = null;
+        try
+        {
+            out = new FileOutputStream(testSaveConf);
+            conf.save(out);
+        }
+        finally
+        {
+            if(out != null)
+            {
+                out.close();
+            }
+        }
+
+        XMLConfiguration checkConfig = new XMLConfiguration();
+        checkConfig.setFile(testSaveConf);
+        checkSavedConfig(checkConfig);
+
+        try
+        {
+            out = new FileOutputStream(testSaveConf);
+            conf.save(out, "UTF8");
+        }
+        finally
+        {
+            if(out != null)
+            {
+                out.close();
+            }
+        }
+
+        checkConfig.clear();
+        checkSavedConfig(checkConfig);
+    }
+
+    @Test
+    public void testAutoSave() throws Exception
+    {
+        conf.setFile(testSaveConf);
+        assertFalse(conf.isAutoSave());
+        conf.setAutoSave(true);
+        assertTrue(conf.isAutoSave());
+        conf.setProperty("autosave", "ok");
+
+        // reload the configuration
+        XMLConfiguration conf2 = new XMLConfiguration(conf.getFile());
+        assertEquals("'autosave' property", "ok", conf2.getString("autosave"));
+
+        conf.clearTree("clear");
+        conf2 = new XMLConfiguration(conf.getFile());
+        Configuration sub = conf2.subset("clear");
+        assertTrue(sub.isEmpty());
+    }
+
+    /**
+     * Tests if a second file can be appended to a first.
+     */
+    @Test
+    public void testAppend() throws Exception
+    {
+        conf = new XMLConfiguration();
+        conf.setFileName(testProperties);
+        conf.load();
+        conf.load(testProperties2);
+        assertEquals("value", conf.getString("element"));
+        assertEquals("tasks", conf.getString("table.name"));
+
+        conf.save(testSaveConf);
+        conf = new XMLConfiguration(testSaveConf);
+        assertEquals("value", conf.getString("element"));
+        assertEquals("tasks", conf.getString("table.name"));
+        assertEquals("application", conf.getString("table[@tableType]"));
+    }
+
+    /**
+     * Tests saving attributes (related to issue 34442).
+     */
+    @Test
+    public void testSaveAttributes() throws Exception
+    {
+        conf.clear();
+        conf.load();
+        conf.save(testSaveConf);
+        conf = new XMLConfiguration();
+        conf.load(testSaveConf);
+        assertEquals("foo", conf.getString("element3[@name]"));
+    }
+
+    /**
+     * Tests collaboration between XMLConfiguration and a reloading strategy.
+     */
+    @Test
+    public void testReloading() throws Exception
+    {
+        assertNotNull(conf.getReloadingStrategy());
+        assertTrue(conf.getReloadingStrategy() instanceof InvariantReloadingStrategy);
+        PrintWriter out = null;
+
+        try
+        {
+            out = new PrintWriter(new FileWriter(testSaveConf));
+            out.println("<?xml version=\"1.0\"?><config><test>1</test></config>");
+            out.close();
+            out = null;
+            conf.setFile(testSaveConf);
+            FileAlwaysReloadingStrategy strategy = new FileAlwaysReloadingStrategy();
+            strategy.setRefreshDelay(100);
+            conf.setReloadingStrategy(strategy);
+            assertEquals(strategy, conf.getReloadingStrategy());
+            assertEquals("Wrong file monitored", testSaveConf.getAbsolutePath(),
+                    strategy.getMonitoredFile().getAbsolutePath());
+            conf.load();
+            assertEquals(1, conf.getInt("test"));
+
+            out = new PrintWriter(new FileWriter(testSaveConf));
+            out.println("<?xml version=\"1.0\"?><config><test>2</test></config>");
+            out.close();
+            out = null;
+
+            int value = conf.getInt("test");
+            assertEquals("No reloading performed", 2, value);
+        }
+        finally
+        {
+            if (out != null)
+            {
+                out.close();
+            }
+        }
+    }
+
+    @Test
+    public void testReloadingOOM() throws Exception
+    {
+        assertNotNull(conf.getReloadingStrategy());
+        assertTrue(conf.getReloadingStrategy() instanceof InvariantReloadingStrategy);
+        PrintWriter out = null;
+
+        try
+        {
+            out = new PrintWriter(new FileWriter(testSaveConf));
+            out.println("<?xml version=\"1.0\"?><config><test>1</test></config>");
+            out.close();
+            out = null;
+            conf.setFile(testSaveConf);
+            FileAlwaysReloadingStrategy strategy = new FileAlwaysReloadingStrategy();
+            strategy.setRefreshDelay(100);
+            conf.setReloadingStrategy(strategy);
+            conf.load();
+            assertEquals(1, conf.getInt("test"));
+
+            for (int i = 1; i < LOOP_COUNT; ++i)
+            {
+               assertEquals(1, conf.getInt("test"));
+            }
+        }
+        finally
+        {
+            if (out != null)
+            {
+                out.close();
+            }
+        }
+    }
+
+    /**
+     * Tests the refresh() method.
+     */
+    @Test
+    public void testRefresh() throws ConfigurationException
+    {
+        conf.setProperty("element", "anotherValue");
+        conf.refresh();
+        assertEquals("Wrong property after refresh", "value",
+                conf.getString("element"));
+    }
+
+    /**
+     * Tries to call refresh() when the configuration is not associated with a
+     * file.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testRefreshNoFile() throws ConfigurationException
+    {
+        conf = new XMLConfiguration();
+        conf.refresh();
+    }
+
+    /**
+     * Tests access to tag names with delimiter characters.
+     */
+    @Test
+    public void testComplexNames()
+    {
+        assertEquals("Name with dot", conf.getString("complexNames.my..elem"));
+        assertEquals("Another dot", conf.getString("complexNames.my..elem.sub..elem"));
+    }
+
+    /**
+     * Creates a validating document builder.
+     * @return the document builder
+     * @throws ParserConfigurationException if an error occurs
+     */
+    private DocumentBuilder createValidatingDocBuilder()
+            throws ParserConfigurationException
+    {
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setValidating(true);
+        DocumentBuilder builder = factory.newDocumentBuilder();
+        builder.setErrorHandler(new DefaultHandler() {
+            @Override
+            public void error(SAXParseException ex) throws SAXException
+            {
+                throw ex;
+            }
+        });
+        return builder;
+    }
+
+    /**
+     * Tests setting a custom document builder.
+     */
+    @Test
+    public void testCustomDocBuilder() throws Exception
+    {
+        // Load an invalid XML file with the default (non validating)
+        // doc builder. This should work...
+        conf = new XMLConfiguration();
+        conf.load(ConfigurationAssert.getTestFile("testValidateInvalid.xml"));
+        assertEquals("customers", conf.getString("table.name"));
+        assertFalse(conf.containsKey("table.fields.field(1).type"));
+    }
+
+    /**
+     * Tests whether a validating document builder detects a validation error.
+     */
+    @Test(expected = ConfigurationException.class)
+    public void testCustomDocBuilderValidationError() throws Exception
+    {
+        DocumentBuilder builder = createValidatingDocBuilder();
+        conf = new XMLConfiguration();
+        conf.setDocumentBuilder(builder);
+        conf.load(new File("conf/testValidateInvalid.xml"));
+    }
+
+    /**
+     * Tests whether a valid document can be loaded with a validating document builder.
+     */
+    @Test
+    public void testCustomDocBuilderValidationSuccess() throws Exception
+    {
+        DocumentBuilder builder = createValidatingDocBuilder();
+        conf = new XMLConfiguration();
+        conf.setDocumentBuilder(builder);
+        conf.load(ConfigurationAssert.getTestFile("testValidateValid.xml"));
+        assertTrue(conf.containsKey("table.fields.field(1).type"));
+    }
+
+    /**
+     * Tests the clone() method.
+     */
+    @Test
+    public void testClone()
+    {
+        Configuration c = (Configuration) conf.clone();
+        assertTrue(c instanceof XMLConfiguration);
+        XMLConfiguration copy = (XMLConfiguration) c;
+        assertNotNull(conf.getDocument());
+        assertNull(copy.getDocument());
+        assertNotNull(conf.getFileName());
+        assertNull(copy.getFileName());
+
+        copy.setProperty("element3", "clonedValue");
+        assertEquals("value", conf.getString("element3"));
+        conf.setProperty("element3[@name]", "originalFoo");
+        assertEquals("foo", copy.getString("element3[@name]"));
+    }
+
+    /**
+     * Tests saving a configuration after cloning to ensure that the clone and
+     * the original are completely detached.
+     */
+    @Test
+    public void testCloneWithSave() throws ConfigurationException
+    {
+        XMLConfiguration c = (XMLConfiguration) conf.clone();
+        c.addProperty("test.newProperty", Boolean.TRUE);
+        conf.addProperty("test.orgProperty", Boolean.TRUE);
+        c.save(testSaveConf);
+        XMLConfiguration c2 = new XMLConfiguration(testSaveConf);
+        assertTrue("New property after clone() was not saved", c2
+                .getBoolean("test.newProperty"));
+        assertFalse("Property of original config was saved", c2
+                .containsKey("test.orgProperty"));
+    }
+
+    /**
+     * Tests the subset() method. There was a bug that calling subset() had
+     * undesired side effects.
+     */
+    @Test
+    public void testSubset() throws ConfigurationException
+    {
+        conf = new XMLConfiguration();
+        conf.load(ConfigurationAssert.getTestFile("testHierarchicalXMLConfiguration.xml"));
+        conf.subset("tables.table(0)");
+        conf.save(testSaveConf);
+
+        conf = new XMLConfiguration(testSaveConf);
+        assertEquals("users", conf.getString("tables.table(0).name"));
+    }
+
+    /**
+     * Tests string properties with list delimiters and escaped delimiters.
+     */
+    @Test
+    public void testSplitLists()
+    {
+        assertEquals("a", conf.getString("split.list3[@values]"));
+        assertEquals(2, conf.getMaxIndex("split.list3[@values]"));
+        assertEquals("a,b,c", conf.getString("split.list4[@values]"));
+        assertEquals("a", conf.getString("split.list1"));
+        assertEquals(2, conf.getMaxIndex("split.list1"));
+        assertEquals("a,b,c", conf.getString("split.list2"));
+    }
+
+    /**
+     * Tests string properties with list delimiters when delimiter parsing
+     * is disabled
+     */
+    @Test
+    public void testDelimiterParsingDisabled() throws ConfigurationException {
+        XMLConfiguration conf2 = new XMLConfiguration();
+        conf2.setDelimiterParsingDisabled(true);
+        conf2.setFile(new File(testProperties));
+        conf2.load();
+
+        assertEquals("a,b,c", conf2.getString("split.list3[@values]"));
+        assertEquals(0, conf2.getMaxIndex("split.list3[@values]"));
+        assertEquals("a\\,b\\,c", conf2.getString("split.list4[@values]"));
+        assertEquals("a,b,c", conf2.getString("split.list1"));
+        assertEquals(0, conf2.getMaxIndex("split.list1"));
+        assertEquals("a\\,b\\,c", conf2.getString("split.list2"));
+        conf2 = new XMLConfiguration();
+        conf2.setExpressionEngine(new XPathExpressionEngine());
+        conf2.setDelimiterParsingDisabled(true);
+        conf2.setFile(new File(testProperties));
+        conf2.load();
+
+        assertEquals("a,b,c", conf2.getString("split/list3/@values"));
+        assertEquals(0, conf2.getMaxIndex("split/list3/@values"));
+        assertEquals("a\\,b\\,c", conf2.getString("split/list4/@values"));
+        assertEquals("a,b,c", conf2.getString("split/list1"));
+        assertEquals(0, conf2.getMaxIndex("split/list1"));
+        assertEquals("a\\,b\\,c", conf2.getString("split/list2"));
+    }
+
+     /**
+     * Tests string properties with list delimiters when delimiter parsing
+     * is disabled
+     */
+    @Test
+    public void testSaveWithDelimiterParsingDisabled() throws ConfigurationException {
+        XMLConfiguration conf = new XMLConfiguration();
+        conf.setExpressionEngine(new XPathExpressionEngine());
+        conf.setDelimiterParsingDisabled(true);
+        conf.setAttributeSplittingDisabled(true);
+        conf.setFile(new File(testProperties));
+        conf.load();
+
+        assertEquals("a,b,c", conf.getString("split/list3/@values"));
+        assertEquals(0, conf.getMaxIndex("split/list3/@values"));
+        assertEquals("a\\,b\\,c", conf.getString("split/list4/@values"));
+        assertEquals("a,b,c", conf.getString("split/list1"));
+        assertEquals(0, conf.getMaxIndex("split/list1"));
+        assertEquals("a\\,b\\,c", conf.getString("split/list2"));
+        // save the configuration
+        conf.save(testSaveConf.getAbsolutePath());
+
+        // read the configuration and compare the properties
+        XMLConfiguration checkConfig = new XMLConfiguration();
+        checkConfig.setFileName(testSaveConf.getAbsolutePath());
+        checkSavedConfig(checkConfig);
+        XMLConfiguration config = new XMLConfiguration();
+        config.setFileName(testFile2);
+        //config.setExpressionEngine(new XPathExpressionEngine());
+        config.setDelimiterParsingDisabled(true);
+        config.setAttributeSplittingDisabled(true);
+        config.load();
+        config.setProperty("Employee[@attr1]", "3,2,1");
+        assertEquals("3,2,1", config.getString("Employee[@attr1]"));
+        config.save(testSaveFile.getAbsolutePath());
+        config = new XMLConfiguration();
+        config.setFileName(testSaveFile.getAbsolutePath());
+        //config.setExpressionEngine(new XPathExpressionEngine());
+        config.setDelimiterParsingDisabled(true);
+        config.setAttributeSplittingDisabled(true);
+        config.load();
+        config.setProperty("Employee[@attr1]", "1,2,3");
+        assertEquals("1,2,3", config.getString("Employee[@attr1]"));
+        config.setProperty("Employee[@attr2]", "one, two, three");
+        assertEquals("one, two, three", config.getString("Employee[@attr2]"));
+        config.setProperty("Employee.text", "a,b,d");
+        assertEquals("a,b,d", config.getString("Employee.text"));
+        config.setProperty("Employee.Salary", "100,000");
+        assertEquals("100,000", config.getString("Employee.Salary"));
+        config.save(testSaveFile.getAbsolutePath());
+        checkConfig = new XMLConfiguration();
+        checkConfig.setFileName(testSaveFile.getAbsolutePath());
+        checkConfig.setExpressionEngine(new XPathExpressionEngine());
+        checkConfig.setDelimiterParsingDisabled(true);
+        checkConfig.setAttributeSplittingDisabled(true);
+        checkConfig.load();
+        assertEquals("1,2,3", checkConfig.getString("Employee/@attr1"));
+        assertEquals("one, two, three", checkConfig.getString("Employee/@attr2"));
+        assertEquals("a,b,d", checkConfig.getString("Employee/text"));
+        assertEquals("100,000", checkConfig.getString("Employee/Salary"));
+    }
+
+    /**
+     * Tests whether a DTD can be accessed.
+     */
+    @Test
+    public void testDtd() throws ConfigurationException
+    {
+        conf = new XMLConfiguration("testDtd.xml");
+        assertEquals("value1", conf.getString("entry(0)"));
+        assertEquals("test2", conf.getString("entry(1)[@key]"));
+    }
+
+    /**
+     * Tests DTD validation using the setValidating() method.
+     */
+    @Test
+    public void testValidating() throws ConfigurationException
+    {
+        File nonValidFile = ConfigurationAssert.getTestFile("testValidateInvalid.xml");
+        conf = new XMLConfiguration();
+        assertFalse(conf.isValidating());
+
+        // Load a non valid XML document. Should work for isValidating() == false
+        conf.load(nonValidFile);
+        assertEquals("customers", conf.getString("table.name"));
+        assertFalse(conf.containsKey("table.fields.field(1).type"));
+
+        // Now set the validating flag to true
+        conf.setValidating(true);
+        try
+        {
+            conf.load(nonValidFile);
+            fail("Validation was not performed!");
+        }
+        catch(ConfigurationException cex)
+        {
+            //ok
+        }
+    }
+
+    /**
+     * Tests handling of empty elements.
+     */
+    @Test
+    public void testEmptyElements() throws ConfigurationException
+    {
+        assertTrue(conf.containsKey("empty"));
+        assertEquals("", conf.getString("empty"));
+        conf.addProperty("empty2", "");
+        conf.setProperty("empty", "no more empty");
+        conf.save(testSaveConf);
+
+        conf = new XMLConfiguration(testSaveConf);
+        assertEquals("no more empty", conf.getString("empty"));
+        assertEquals("", conf.getProperty("empty2"));
+    }
+
+    /**
+     * Tests the isEmpty() method for an empty configuration that was reloaded.
+     */
+    @Test
+    public void testEmptyReload() throws ConfigurationException
+    {
+        XMLConfiguration config = new XMLConfiguration();
+        assertTrue("Newly created configuration not empty", config.isEmpty());
+        config.save(testSaveConf);
+        config.load(testSaveConf);
+        assertTrue("Reloaded configuration not empty", config.isEmpty());
+    }
+
+    /**
+     * Tests whether the encoding is correctly detected by the XML parser. This
+     * is done by loading an XML file with the encoding "UTF-16". If this
+     * encoding is not detected correctly, an exception will be thrown that
+     * "Content is not allowed in prolog". This test case is related to issue
+     * 34204.
+     */
+    @Test
+    public void testLoadWithEncoding() throws ConfigurationException
+    {
+        File file = ConfigurationAssert.getTestFile("testEncoding.xml");
+        conf = new XMLConfiguration();
+        conf.load(file);
+        assertEquals("test3_yoge", conf.getString("yoge"));
+    }
+
+    /**
+     * Tests whether the encoding is written to the generated XML file.
+     */
+    @Test
+    public void testSaveWithEncoding() throws ConfigurationException
+    {
+        conf = new XMLConfiguration();
+        conf.setProperty("test", "a value");
+        conf.setEncoding(ENCODING);
+
+        StringWriter out = new StringWriter();
+        conf.save(out);
+        assertTrue("Encoding was not written to file", out.toString().indexOf(
+                "encoding=\"" + ENCODING + "\"") >= 0);
+    }
+
+    /**
+     * Tests whether a default encoding is used if no specific encoding is set.
+     * According to the XSLT specification (http://www.w3.org/TR/xslt#output)
+     * this should be either UTF-8 or UTF-16.
+     */
+    @Test
+    public void testSaveWithNullEncoding() throws ConfigurationException
+    {
+        conf = new XMLConfiguration();
+        conf.setProperty("testNoEncoding", "yes");
+        conf.setEncoding(null);
+
+        StringWriter out = new StringWriter();
+        conf.save(out);
+        assertTrue("Encoding was written to file", out.toString().indexOf(
+                "encoding=\"UTF-") >= 0);
+    }
+
+    /**
+     * Tests whether the DOCTYPE survives a save operation.
+     */
+    @Test
+    public void testSaveWithDoctype() throws ConfigurationException
+    {
+        String content = "<?xml  version=\"1.0\"?>"
+                + DOCTYPE
+                + "properties"
+                + DOCTYPE_DECL
+                + "<properties version=\"1.0\"><entry key=\"test\">value</entry></properties>";
+        StringReader in = new StringReader(content);
+        conf = new XMLConfiguration();
+        conf.setFileName("testDtd.xml");
+        conf.load();
+        conf.clear();
+        conf.load(in);
+
+        assertEquals("Wrong public ID", PUBLIC_ID, conf.getPublicID());
+        assertEquals("Wrong system ID", SYSTEM_ID, conf.getSystemID());
+        StringWriter out = new StringWriter();
+        conf.save(out);
+        assertTrue("Did not find DOCTYPE", out.toString().indexOf(DOCTYPE) >= 0);
+    }
+
+    /**
+     * Tests setting public and system IDs for the D'OCTYPE and then saving the
+     * configuration. This should generate a DOCTYPE declaration.
+     */
+    @Test
+    public void testSaveWithDoctypeIDs() throws ConfigurationException
+    {
+        assertNull("A public ID was found", conf.getPublicID());
+        assertNull("A system ID was found", conf.getSystemID());
+        conf.setPublicID(PUBLIC_ID);
+        conf.setSystemID(SYSTEM_ID);
+        StringWriter out = new StringWriter();
+        conf.save(out);
+        assertTrue("Did not find DOCTYPE", out.toString().indexOf(
+                DOCTYPE + "testconfig" + DOCTYPE_DECL) >= 0);
+    }
+
+    /**
+     * Tests saving a configuration when an invalid transformer factory is
+     * specified. In this case the error thrown by the TransformerFactory class
+     * should be caught and re-thrown as a ConfigurationException.
+     */
+    @Test
+    public void testSaveWithInvalidTransformerFactory()
+    {
+        System.setProperty(PROP_FACTORY, "an.invalid.Class");
+        try
+        {
+            conf.save(testSaveConf);
+            fail("Could save with invalid TransformerFactory!");
+        }
+        catch (ConfigurationException cex)
+        {
+            // ok
+        }
+        finally
+        {
+            System.getProperties().remove(PROP_FACTORY);
+        }
+    }
+
+    /**
+     * Tests if reloads are recognized by subset().
+     */
+    @Test
+    public void testSubsetWithReload() throws ConfigurationException
+    {
+        XMLConfiguration c = setUpReloadTest();
+        Configuration sub = c.subset("test");
+        assertEquals("New value not read", "newValue", sub.getString("entity"));
+    }
+
+    /**
+     * Tests if reloads are recognized by configurationAt().
+     */
+    @Test
+    public void testConfigurationAtWithReload() throws ConfigurationException
+    {
+        XMLConfiguration c = setUpReloadTest();
+        HierarchicalConfiguration sub = c.configurationAt("test(0)");
+        assertEquals("New value not read", "newValue", sub.getString("entity"));
+    }
+
+    /**
+     * Tests if reloads are recognized by configurationsAt().
+     */
+    @Test
+    public void testConfigurationsAtWithReload() throws ConfigurationException
+    {
+        XMLConfiguration c = setUpReloadTest();
+        List<HierarchicalConfiguration> configs = c.configurationsAt("test");
+        assertEquals("New value not read", "newValue",
+                configs.get(0).getString("entity"));
+    }
+
+    /**
+     * Tests whether reloads are recognized when querying the configuration's
+     * keys.
+     */
+    @Test
+    public void testGetKeysWithReload() throws ConfigurationException
+    {
+        XMLConfiguration c = setUpReloadTest();
+        conf.addProperty("aNewKey", "aNewValue");
+        conf.save(testSaveConf);
+        boolean found = false;
+        for (Iterator<String> it = c.getKeys(); it.hasNext();)
+        {
+            if ("aNewKey".equals(it.next()))
+            {
+                found = true;
+            }
+        }
+        assertTrue("Reload not performed", found);
+    }
+
+    /**
+     * Tests accessing properties when the XPATH expression engine is set.
+     */
+    @Test
+    public void testXPathExpressionEngine()
+    {
+        conf.setExpressionEngine(new XPathExpressionEngine());
+        assertEquals("Wrong attribute value", "foo\"bar", conf
+                .getString("test[1]/entity/@name"));
+        conf.clear();
+        assertNull(conf.getString("test[1]/entity/@name"));
+    }
+
+    /**
+     * Tests the copy constructor.
+     */
+    @Test
+    public void testInitCopy() throws ConfigurationException
+    {
+        XMLConfiguration copy = new XMLConfiguration(conf);
+        assertEquals("value", copy.getProperty("element"));
+        assertNull("Document was copied, too", copy.getDocument());
+        ConfigurationNode root = copy.getRootNode();
+        for (ConfigurationNode node : root.getChildren())
+        {
+            assertNull("Reference was not cleared", node.getReference());
+        }
+
+        removeTestFile();
+        copy.setFile(testSaveConf);
+        copy.save();
+        copy.clear();
+        checkSavedConfig(copy);
+    }
+
+    /**
+     * Tests setting text of the root element.
+     */
+    @Test
+    public void testSetTextRootElement() throws ConfigurationException
+    {
+        conf.setProperty("", "Root text");
+        conf.save(testSaveConf);
+        XMLConfiguration copy = new XMLConfiguration();
+        copy.setFile(testSaveConf);
+        checkSavedConfig(copy);
+    }
+
+    /**
+     * Tests removing the text of the root element.
+     */
+    @Test
+    public void testClearTextRootElement() throws ConfigurationException
+    {
+        final String xml = "<e a=\"v\">text</e>";
+        conf.clear();
+        StringReader in = new StringReader(xml);
+        conf.load(in);
+        assertEquals("Wrong text of root", "text", conf.getString(""));
+
+        conf.clearProperty("");
+        conf.save(testSaveConf);
+        XMLConfiguration copy = new XMLConfiguration();
+        copy.setFile(testSaveConf);
+        checkSavedConfig(copy);
+    }
+
+    /**
+     * Tests list nodes with multiple values and attributes.
+     */
+    @Test
+    public void testListWithAttributes()
+    {
+        assertEquals("Wrong number of <a> elements", 6, conf.getList(
+                "attrList.a").size());
+        assertEquals("Wrong value of first element", "ABC", conf
+                .getString("attrList.a(0)"));
+        assertEquals("Wrong value of first name attribute", "x", conf
+                .getString("attrList.a(0)[@name]"));
+        assertEquals("Wrong number of name attributes", 6, conf.getList(
+                "attrList.a[@name]").size());
+    }
+
+    /**
+     * Tests a list node with attributes that has multiple values separated by
+     * the list delimiter. In this scenario the attribute should be added to all
+     * list nodes.
+     */
+    @Test
+    public void testListWithAttributesMultiValue()
+    {
+        assertEquals("Wrong value of 2nd element", "1",
+                conf.getString("attrList.a(1)"));
+        assertEquals("Wrong value of 2nd name attribute", "y",
+                conf.getString("attrList.a(1)[@name]"));
+        for (int i = 1; i <= 3; i++)
+        {
+            assertEquals("Wrong value of element " + (i + 1), i,
+                    conf.getInt("attrList.a(" + i + ")"));
+            assertEquals("Wrong name attribute for element " + (i), "y",
+                    conf.getString("attrList.a(" + i + ")[@name]"));
+        }
+    }
+
+    /**
+     * Tests a list node with multiple values and multiple attributes. All
+     * attribute values should be assigned to all list nodes.
+     */
+    @Test
+    public void testListWithMultipleAttributesMultiValue()
+    {
+        for (int i = 1; i <= 2; i++)
+        {
+            String idxStr = String.format("(%d)", Integer.valueOf(i + 3));
+            String nodeKey = "attrList.a" + idxStr;
+            assertEquals("Wrong value of multi-valued node", "value" + i,
+                    conf.getString(nodeKey));
+            assertEquals("Wrong name attribute at " + i, "u",
+                    conf.getString(nodeKey + "[@name]"));
+            assertEquals("Wrong test attribute at " + i, "yes",
+                    conf.getString(nodeKey + "[@test]"));
+        }
+    }
+
+    /**
+     * Tests whether the auto save mechanism is triggered by changes at a
+     * subnode configuration.
+     */
+    @Test
+    public void testAutoSaveWithSubnodeConfig() throws ConfigurationException
+    {
+        final String newValue = "I am autosaved";
+        conf.setFile(testSaveConf);
+        conf.setAutoSave(true);
+        Configuration sub = conf.configurationAt("element2.subelement");
+        sub.setProperty("subsubelement", newValue);
+        assertEquals("Change not visible to parent", newValue, conf
+                .getString("element2.subelement.subsubelement"));
+        XMLConfiguration conf2 = new XMLConfiguration(testSaveConf);
+        assertEquals("Change was not saved", newValue, conf2
+                .getString("element2.subelement.subsubelement"));
+    }
+
+    /**
+     * Tests whether a subnode configuration created from another subnode
+     * configuration of a XMLConfiguration can trigger the auto save mechanism.
+     */
+    @Test
+    public void testAutoSaveWithSubSubnodeConfig() throws ConfigurationException
+    {
+        final String newValue = "I am autosaved";
+        conf.setFile(testSaveConf);
+        conf.setAutoSave(true);
+        SubnodeConfiguration sub1 = conf.configurationAt("element2");
+        SubnodeConfiguration sub2 = sub1.configurationAt("subelement");
+        sub2.setProperty("subsubelement", newValue);
+        assertEquals("Change not visible to parent", newValue, conf
+                .getString("element2.subelement.subsubelement"));
+        XMLConfiguration conf2 = new XMLConfiguration(testSaveConf);
+        assertEquals("Change was not saved", newValue, conf2
+                .getString("element2.subelement.subsubelement"));
+    }
+
+    /**
+     * Tests saving and loading a configuration when delimiter parsing is
+     * disabled.
+     */
+    @Test
+    public void testSaveDelimiterParsingDisabled()
+            throws ConfigurationException
+    {
+        checkSaveDelimiterParsingDisabled("list.delimiter.test");
+    }
+
+    /**
+     * Tests saving and loading a configuration when delimiter parsing is
+     * disabled and attributes are involved.
+     */
+    @Test
+    public void testSaveDelimiterParsingDisabledAttrs()
+            throws ConfigurationException
+    {
+        checkSaveDelimiterParsingDisabled("list.delimiter.test[@attr]");
+    }
+
+    /**
+     * Helper method for testing saving and loading a configuration when
+     * delimiter parsing is disabled.
+     *
+     * @param key the key to be checked
+     * @throws ConfigurationException if an error occurs
+     */
+    private void checkSaveDelimiterParsingDisabled(String key)
+            throws ConfigurationException
+    {
+        conf.clear();
+        conf.setDelimiterParsingDisabled(true);
+        conf.load();
+        conf.setProperty(key, "C:\\Temp\\,C:\\Data\\");
+        conf.addProperty(key, "a,b,c");
+        conf.save(testSaveConf);
+        XMLConfiguration checkConf = new XMLConfiguration();
+        checkConf.setDelimiterParsingDisabled(true);
+        checkConf.setFile(testSaveConf);
+        checkSavedConfig(checkConf);
+    }
+
+    /**
+     * Tests multiple attribute values in delimiter parsing disabled mode.
+     */
+    @Test
+    public void testDelimiterParsingDisabledMultiAttrValues() throws ConfigurationException
+    {
+        conf.clear();
+        conf.setDelimiterParsingDisabled(true);
+        conf.load();
+        List<Object> expr = conf.getList("expressions[@value]");
+        assertEquals("Wrong list size", 2, expr.size());
+        assertEquals("Wrong element 1", "a || (b && c)", expr.get(0));
+        assertEquals("Wrong element 2", "!d", expr.get(1));
+    }
+
+    /**
+     * Tests using multiple attribute values, which are partly escaped when
+     * delimiter parsing is not disabled.
+     */
+    @Test
+    public void testMultipleAttrValuesEscaped() throws ConfigurationException
+    {
+        conf.addProperty("test.dir[@name]", "C:\\Temp\\");
+        conf.addProperty("test.dir[@name]", "C:\\Data\\");
+        conf.save(testSaveConf);
+        XMLConfiguration checkConf = new XMLConfiguration();
+        checkConf.setFile(testSaveConf);
+        checkSavedConfig(checkConf);
+    }
+
+    /**
+     * Tests a combination of auto save = true and an associated reloading
+     * strategy.
+     */
+    @Test
+    public void testAutoSaveWithReloadingStrategy() throws ConfigurationException
+    {
+        conf.setFile(testSaveConf);
+        conf.save();
+        conf.setReloadingStrategy(new FileAlwaysReloadingStrategy());
+        conf.setAutoSave(true);
+        assertEquals("Value not found", "value", conf.getProperty("element"));
+    }
+
+    /**
+     * Tests adding nodes from another configuration.
+     */
+    @Test
+    public void testAddNodesCopy() throws ConfigurationException
+    {
+        XMLConfiguration c2 = new XMLConfiguration(testProperties2);
+        conf.addNodes("copiedProperties", c2.getRootNode().getChildren());
+        conf.save(testSaveConf);
+        XMLConfiguration checkConf = new XMLConfiguration();
+        checkConf.setFile(testSaveConf);
+        checkSavedConfig(checkConf);
+    }
+
+    /**
+     * Tests whether the addNodes() method triggers an auto save.
+     */
+    @Test
+    public void testAutoSaveAddNodes() throws ConfigurationException
+    {
+        conf.setFile(testSaveConf);
+        conf.setAutoSave(true);
+        HierarchicalConfiguration.Node node = new HierarchicalConfiguration.Node(
+                "addNodesTest", Boolean.TRUE);
+        Collection<ConfigurationNode> nodes = new ArrayList<ConfigurationNode>(1);
+        nodes.add(node);
+        conf.addNodes("test.autosave", nodes);
+        XMLConfiguration c2 = new XMLConfiguration(testSaveConf);
+        assertTrue("Added nodes are not saved", c2
+                .getBoolean("test.autosave.addNodesTest"));
+    }
+
+    /**
+     * Tests saving a configuration after a node was added. Test for
+     * CONFIGURATION-294.
+     */
+    @Test
+    public void testAddNodesAndSave() throws ConfigurationException
+    {
+        ConfigurationNode node = new HierarchicalConfiguration.Node("test");
+        ConfigurationNode child = new HierarchicalConfiguration.Node("child");
+        node.addChild(child);
+        ConfigurationNode attr = new HierarchicalConfiguration.Node("attr");
+        node.addAttribute(attr);
+        ConfigurationNode node2 = conf.createNode("test2");
+        Collection<ConfigurationNode> nodes = new ArrayList<ConfigurationNode>(2);
+        nodes.add(node);
+        nodes.add(node2);
+        conf.addNodes("add.nodes", nodes);
+        conf.setFile(testSaveConf);
+        conf.save();
+        conf.setProperty("add.nodes.test", "true");
+        conf.setProperty("add.nodes.test.child", "yes");
+        conf.setProperty("add.nodes.test[@attr]", "existing");
+        conf.setProperty("add.nodes.test2", "anotherValue");
+        conf.save();
+        XMLConfiguration c2 = new XMLConfiguration(testSaveConf);
+        assertEquals("Value was not saved", "true", c2
+                .getString("add.nodes.test"));
+        assertEquals("Child value was not saved", "yes", c2
+                .getString("add.nodes.test.child"));
+        assertEquals("Attr value was not saved", "existing", c2
+                .getString("add.nodes.test[@attr]"));
+        assertEquals("Node2 not saved", "anotherValue", c2
+                .getString("add.nodes.test2"));
+    }
+
+    /**
+     * Tests registering the publicId of a DTD.
+     */
+    @Test
+    public void testRegisterEntityId() throws Exception
+    {
+        URL dtdURL = getClass().getResource("/properties.dtd");
+        final String publicId = "http://commons.apache.org/test/properties.dtd";
+        conf = new XMLConfiguration("testDtd.xml");
+        conf.setPublicID(publicId);
+        conf.save(testSaveConf);
+        XMLConfiguration checkConfig = new XMLConfiguration();
+        checkConfig.setFile(testSaveConf);
+        checkConfig.registerEntityId(publicId, dtdURL);
+        checkConfig.setValidating(true);
+        checkSavedConfig(checkConfig);
+    }
+
+    /**
+     * Tries to register a null public ID. This should cause an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testRegisterEntityIdNull() throws IOException
+    {
+        conf.registerEntityId(null, new URL("http://commons.apache.org"));
+    }
+
+    /**
+     * Tests saving a configuration that was created from a hierarchical
+     * configuration. This test exposes bug CONFIGURATION-301.
+     */
+    @Test
+    public void testSaveAfterCreateWithCopyConstructor()
+            throws ConfigurationException
+    {
+        HierarchicalConfiguration hc = conf.configurationAt("element2");
+        conf = new XMLConfiguration(hc);
+        conf.save(testSaveConf);
+        XMLConfiguration checkConfig = new XMLConfiguration();
+        checkConfig.setFile(testSaveConf);
+        checkSavedConfig(checkConfig);
+        assertEquals("Wrong name of root element", "element2", checkConfig
+                .getRootElementName());
+    }
+
+    /**
+     * Tests whether the name of the root element is copied when a configuration
+     * is created using the copy constructor.
+     */
+    @Test
+    public void testCopyRootName() throws ConfigurationException
+    {
+        final String rootName = "rootElement";
+        final String xml = "<" + rootName + "><test>true</test></" + rootName
+                + ">";
+        conf.clear();
+        conf.load(new StringReader(xml));
+        XMLConfiguration copy = new XMLConfiguration(conf);
+        assertEquals("Wrong name of root element", rootName, copy
+                .getRootElementName());
+        copy.save(testSaveConf);
+        copy = new XMLConfiguration(testSaveConf);
+        assertEquals("Wrong name of root element after save", rootName, copy
+                .getRootElementName());
+    }
+
+    /**
+     * Tests whether the name of the root element is copied for a configuration
+     * for which not yet a document exists.
+     */
+    @Test
+    public void testCopyRootNameNoDocument() throws ConfigurationException
+    {
+        final String rootName = "rootElement";
+        conf = new XMLConfiguration();
+        conf.setRootElementName(rootName);
+        conf.setProperty("test", Boolean.TRUE);
+        XMLConfiguration copy = new XMLConfiguration(conf);
+        assertEquals("Wrong name of root element", rootName, copy
+                .getRootElementName());
+        copy.save(testSaveConf);
+        copy = new XMLConfiguration(testSaveConf);
+        assertEquals("Wrong name of root element after save", rootName, copy
+                .getRootElementName());
+    }
+
+    /**
+     * Tests adding an attribute node using the addNodes() method.
+     */
+    @Test
+    public void testAddNodesAttributeNode()
+    {
+        conf.addProperty("testAddNodes.property[@name]", "prop1");
+        conf.addProperty("testAddNodes.property(0).value", "value1");
+        conf.addProperty("testAddNodes.property(-1)[@name]", "prop2");
+        conf.addProperty("testAddNodes.property(1).value", "value2");
+        Collection<ConfigurationNode> nodes = new ArrayList<ConfigurationNode>();
+        nodes.add(new HierarchicalConfiguration.Node("property"));
+        conf.addNodes("testAddNodes", nodes);
+        nodes.clear();
+        ConfigurationNode nd = new HierarchicalConfiguration.Node("name",
+                "prop3");
+        nd.setAttribute(true);
+        nodes.add(nd);
+        conf.addNodes("testAddNodes.property(2)", nodes);
+        assertEquals("Attribute not added", "prop3", conf
+                .getString("testAddNodes.property(2)[@name]"));
+    }
+
+    /**
+     * Tests whether spaces are preserved when the xml:space attribute is set.
+     */
+    @Test
+    public void testPreserveSpace()
+    {
+        assertEquals("Wrong value of blanc", " ", conf.getString("space.blanc"));
+        assertEquals("Wrong value of stars", " * * ", conf
+                .getString("space.stars"));
+    }
+
+    /**
+     * Tests whether the xml:space attribute works directly on the current
+     * element. This test is related to CONFIGURATION-555.
+     */
+    @Test
+    public void testPreserveSpaceOnElement()
+    {
+        assertEquals("Wrong value", " preserved ", conf.getString("spaceElement"));
+    }
+
+    /**
+     * Tests whether the xml:space attribute can be overridden in nested
+     * elements.
+     */
+    @Test
+    public void testPreserveSpaceOverride()
+    {
+        assertEquals("Not trimmed", "Some text", conf
+                .getString("space.description"));
+    }
+
+    /**
+     * Tests an xml:space attribute with an invalid value. This will be
+     * interpreted as default.
+     */
+    @Test
+    public void testPreserveSpaceInvalid()
+    {
+        assertEquals("Invalid not trimmed", "Some other text", conf
+                .getString("space.testInvalid"));
+    }
+
+    /**
+     * Tests whether attribute splitting can be disabled.
+     */
+    @Test
+    public void testAttributeSplittingDisabled() throws ConfigurationException
+    {
+        List<Object> values = conf.getList("expressions[@value2]");
+        assertEquals("Wrong number of attribute values", 2, values.size());
+        assertEquals("Wrong value 1", "a", values.get(0));
+        assertEquals("Wrong value 2", "b|c", values.get(1));
+        XMLConfiguration conf2 = new XMLConfiguration();
+        conf2.setAttributeSplittingDisabled(true);
+        conf2.setFile(conf.getFile());
+        conf2.load();
+        assertEquals("Attribute was split", "a,b|c", conf2
+                .getString("expressions[@value2]"));
+    }
+
+    /**
+     * Tests disabling both delimiter parsing and attribute splitting.
+     */
+    @Test
+    public void testAttributeSplittingAndDelimiterParsingDisabled()
+            throws ConfigurationException
+    {
+        conf.clear();
+        conf.setDelimiterParsingDisabled(true);
+        conf.load();
+        List<Object> values = conf.getList("expressions[@value2]");
+        assertEquals("Wrong number of attribute values", 2, values.size());
+        assertEquals("Wrong value 1", "a,b", values.get(0));
+        assertEquals("Wrong value 2", "c", values.get(1));
+        XMLConfiguration conf2 = new XMLConfiguration();
+        conf2.setAttributeSplittingDisabled(true);
+        conf2.setDelimiterParsingDisabled(true);
+        conf2.setFile(conf.getFile());
+        conf2.load();
+        assertEquals("Attribute was split", "a,b|c", conf2
+                .getString("expressions[@value2]"));
+    }
+
+    /**
+     * Tests modifying an XML document and saving it with schema validation enabled.
+     */
+    @Test
+    public void testSaveWithValidation() throws Exception
+    {
+        CatalogResolver resolver = new CatalogResolver();
+        resolver.setCatalogFiles(CATALOG_FILES);
+        conf = new XMLConfiguration();
+        conf.setEntityResolver(resolver);
+        conf.setFileName(testFile2);
+        conf.setSchemaValidation(true);
+        conf.load();
+        conf.setProperty("Employee.SSN", "123456789");
+        conf.validate();
+        conf.save(testSaveConf);
+        conf = new XMLConfiguration(testSaveConf);
+        assertEquals("123456789", conf.getString("Employee.SSN"));
+    }
+
+    /**
+     * Tests modifying an XML document and validating it against the schema.
+     */
+    @Test
+    public void testSaveWithValidationFailure() throws Exception
+    {
+        CatalogResolver resolver = new CatalogResolver();
+        resolver.setCatalogFiles(CATALOG_FILES);
+        conf = new XMLConfiguration();
+        conf.setEntityResolver(resolver);
+        conf.setFileName(testFile2);
+        conf.setSchemaValidation(true);
+        conf.load();
+        conf.setProperty("Employee.Email", "JohnDoe at apache.org");
+        try
+        {
+            conf.validate();
+            fail("No validation failure on save");
+        }
+        catch (Exception e)
+        {
+            Throwable cause = e.getCause();
+            assertNotNull("No cause for exception on save", cause);
+            assertTrue("Incorrect exception on save", cause instanceof SAXParseException);
+        }
+    }
+
+    @Test
+    public void testConcurrentGetAndReload() throws Exception
+    {
+        //final FileConfiguration config = new PropertiesConfiguration("test.properties");
+        final FileConfiguration config = new XMLConfiguration("test.xml");
+        config.setReloadingStrategy(new FileAlwaysReloadingStrategy());
+
+        assertTrue("Property not found", config.getProperty("test.short") != null);
+
+        Thread testThreads[] = new Thread[THREAD_COUNT];
+
+        for (int i = 0; i < testThreads.length; ++i)
+        {
+            testThreads[i] = new ReloadThread(config);
+            testThreads[i].start();
+        }
+
+        for (int i = 0; i < LOOP_COUNT; i++)
+        {
+            assertTrue("Property not found", config.getProperty("test.short") != null);
+        }
+
+        for (int i = 0; i < testThreads.length; ++i)
+        {
+            testThreads[i].join();
+        }
+    }
+
+    /**
+     * Tests whether a windows path can be saved correctly. This test is related
+     * to CONFIGURATION-428.
+     */
+    @Test
+    public void testSaveWindowsPath() throws ConfigurationException
+    {
+        conf.clear();
+        conf.addProperty("path", "C:\\Temp");
+        StringWriter writer = new StringWriter();
+        conf.save(writer);
+        String content = writer.toString();
+        assertTrue("Path not found: " + content,
+                content.indexOf("<path>C:\\Temp</path>") >= 0);
+        conf.save(testSaveFile);
+        XMLConfiguration conf2 = new XMLConfiguration(testSaveFile);
+        assertEquals("Wrong windows path", "C:\\Temp",
+                conf2.getString("path"));
+    }
+
+    /**
+     * Tests whether an attribute can be set to an empty string. This test is
+     * related to CONFIGURATION-446.
+     */
+    @Test
+    public void testEmptyAttribute() throws ConfigurationException
+    {
+        String key = "element3[@value]";
+        conf.setProperty(key, "");
+        assertTrue("Key not found", conf.containsKey(key));
+        assertEquals("Wrong value", "", conf.getString(key));
+        conf.save(testSaveConf);
+        conf = new XMLConfiguration();
+        conf.load(testSaveConf);
+        assertTrue("Key not found after save", conf.containsKey(key));
+        assertEquals("Wrong value after save", "", conf.getString(key));
+    }
+
+    /**
+     * Tests whether it is possible to add nodes to a XMLConfiguration through a
+     * SubnodeConfiguration and whether these nodes have the correct type. This
+     * test is related to CONFIGURATION-472.
+     */
+    @Test
+    public void testAddNodesToSubnodeConfiguration() throws Exception
+    {
+        SubnodeConfiguration sub = conf.configurationAt("element2");
+        sub.addProperty("newKey", "newvalue");
+        ConfigurationNode root = conf.getRootNode();
+        ConfigurationNode elem = root.getChildren("element2").get(0);
+        ConfigurationNode newNode = elem.getChildren("newKey").get(0);
+        assertTrue("Wrong node type: " + newNode,
+                newNode instanceof XMLConfiguration.XMLNode);
+    }
+
+    /**
+     * Tests whether list properties are set correctly if delimiter
+     * parsing is disabled. This test is related to CONFIGURATION-495.
+     */
+    @Test
+    public void testSetPropertyListWithDelimiterParsingDisabled()
+            throws ConfigurationException
+    {
+        String prop = "delimiterListProp";
+        conf.setDelimiterParsingDisabled(true);
+        List<String> list = Arrays.asList("val", "val2", "val3");
+        conf.setProperty(prop, list);
+        conf.setFile(testSaveFile);
+        conf.save();
+        conf.clear();
+        conf.load();
+        assertEquals("Wrong list property", list, conf.getProperty(prop));
+    }
+
+    /**
+     * Tests whether list properties are added correctly if delimiter parsing is
+     * disabled. This test is related to CONFIGURATION-495.
+     */
+    @Test
+    public void testAddPropertyListWithDelimiterParsingDisabled()
+            throws ConfigurationException
+    {
+        String prop = "delimiterListProp";
+        conf.setDelimiterParsingDisabled(true);
+        List<String> list = Arrays.asList("val", "val2", "val3");
+        conf.addProperty(prop, list);
+        conf.setFile(testSaveFile);
+        conf.save();
+        conf.clear();
+        conf.load();
+        assertEquals("Wrong list property", list, conf.getProperty(prop));
+    }
+
+    /**
+     * Prepares a configuration object for testing a reload operation.
+     *
+     * @return the initialized configuration
+     * @throws ConfigurationException if an error occurs
+     */
+    private XMLConfiguration setUpReloadTest() throws ConfigurationException
+    {
+        removeTestFile();
+        conf.save(testSaveConf);
+        XMLConfiguration c = new XMLConfiguration(testSaveConf);
+        c.setReloadingStrategy(new FileAlwaysReloadingStrategy());
+        conf.setProperty("test(0).entity", "newValue");
+        conf.save(testSaveConf);
+        return c;
+    }
+
+    /**
+     * Removes the test output file if it exists.
+     */
+    private void removeTestFile()
+    {
+        if (testSaveConf.exists())
+        {
+            assertTrue(testSaveConf.delete());
+        }
+    }
+
+    /**
+     * Helper method for checking if a save operation was successful. Loads a
+     * saved configuration and then tests against a reference configuration.
+     * @param checkConfig the configuration to check
+     * @throws ConfigurationException if an error occurs
+     */
+    private void checkSavedConfig(FileConfiguration checkConfig) throws ConfigurationException
+    {
+        checkConfig.load();
+        ConfigurationAssert.assertEquals(conf, checkConfig);
+    }
+
+    private class ReloadThread extends Thread
+    {
+        FileConfiguration config;
+
+        ReloadThread(FileConfiguration config)
+        {
+            this.config = config;
+        }
+        @Override
+        public void run()
+        {
+            for (int i = 0; i < LOOP_COUNT; i++)
+            {
+                config.reload();
+            }
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/TestXMLPropertiesConfiguration.java b/src/test/java/org/apache/commons/configuration/TestXMLPropertiesConfiguration.java
new file mode 100644
index 0000000..8006fe2
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/TestXMLPropertiesConfiguration.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.net.URL;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Result;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.junit.Test;
+import org.w3c.dom.Document;
+
+/**
+ * @author Emmanuel Bourg
+ * @version $Id: TestXMLPropertiesConfiguration.java 1534399 2013-10-21 22:25:03Z henning $
+ */
+public class TestXMLPropertiesConfiguration
+{
+    @Test
+    public void testLoad() throws Exception
+    {
+        XMLPropertiesConfiguration conf = new XMLPropertiesConfiguration("test.properties.xml");
+
+        assertEquals("header", "Description of the property list", conf.getHeader());
+
+        assertFalse("The configuration is empty", conf.isEmpty());
+        assertEquals("'key1' property", "value1", conf.getProperty("key1"));
+        assertEquals("'key2' property", "value2", conf.getProperty("key2"));
+        assertEquals("'key3' property", "value3", conf.getProperty("key3"));
+    }
+
+    @Test
+    public void testDOMLoad() throws Exception
+    {
+        URL location = ConfigurationUtils.locate(FileSystem.getDefaultFileSystem(), null, "test.properties.xml");
+        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+        DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
+        File file = new File(location.toURI());
+        Document doc = dBuilder.parse(file);
+        XMLPropertiesConfiguration conf = new XMLPropertiesConfiguration(doc.getDocumentElement());
+
+        assertEquals("header", "Description of the property list", conf.getHeader());
+
+        assertFalse("The configuration is empty", conf.isEmpty());
+        assertEquals("'key1' property", "value1", conf.getProperty("key1"));
+        assertEquals("'key2' property", "value2", conf.getProperty("key2"));
+        assertEquals("'key3' property", "value3", conf.getProperty("key3"));
+    }
+
+    @Test
+    public void testSave() throws Exception
+    {
+        // load the configuration
+        XMLPropertiesConfiguration conf = new XMLPropertiesConfiguration("test.properties.xml");
+
+        // update the configuration
+        conf.addProperty("key4", "value4");
+        conf.clearProperty("key2");
+        conf.setHeader("Description of the new property list");
+
+        // save the configuration
+        File saveFile = new File("target/test2.properties.xml");
+        if (saveFile.exists())
+        {
+            assertTrue(saveFile.delete());
+        }
+        conf.save(saveFile);
+
+        // reload the configuration
+        XMLPropertiesConfiguration conf2 = new XMLPropertiesConfiguration(saveFile);
+
+        // test the configuration
+        assertEquals("header", "Description of the new property list", conf2.getHeader());
+
+        assertFalse("The configuration is empty", conf2.isEmpty());
+        assertEquals("'key1' property", "value1", conf2.getProperty("key1"));
+        assertEquals("'key3' property", "value3", conf2.getProperty("key3"));
+        assertEquals("'key4' property", "value4", conf2.getProperty("key4"));
+    }
+
+    @Test
+    public void testDOMSave() throws Exception
+    {
+        // load the configuration
+        XMLPropertiesConfiguration conf = new XMLPropertiesConfiguration("test.properties.xml");
+
+        // update the configuration
+        conf.addProperty("key4", "value4");
+        conf.clearProperty("key2");
+        conf.setHeader("Description of the new property list");
+
+        // save the configuration
+        File saveFile = new File("target/test2.properties.xml");
+        if (saveFile.exists())
+        {
+            assertTrue(saveFile.delete());
+        }
+
+        // save as DOM into saveFile
+        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+        DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
+        Document document = dBuilder.newDocument();
+        conf.save(document, document);
+        TransformerFactory tFactory = TransformerFactory.newInstance();
+        Transformer transformer = tFactory.newTransformer();
+        DOMSource source = new DOMSource(document);
+        Result result = new StreamResult(saveFile);
+        transformer.transform(source, result);
+
+        // reload the configuration
+        XMLPropertiesConfiguration conf2 = new XMLPropertiesConfiguration(saveFile);
+
+        // test the configuration
+        assertEquals("header", "Description of the new property list", conf2.getHeader());
+
+        assertFalse("The configuration is empty", conf2.isEmpty());
+        assertEquals("'key1' property", "value1", conf2.getProperty("key1"));
+        assertEquals("'key3' property", "value3", conf2.getProperty("key3"));
+        assertEquals("'key4' property", "value4", conf2.getProperty("key4"));
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/beanutils/BeanCreationTestBean.java b/src/test/java/org/apache/commons/configuration/beanutils/BeanCreationTestBean.java
new file mode 100644
index 0000000..d8b29f6
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/beanutils/BeanCreationTestBean.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.beanutils;
+
+/**
+ * A simple bean class used for testing bean creation operations.
+ *
+ * @version $Id: BeanCreationTestBean.java 1534393 2013-10-21 22:02:27Z henning $
+ */
+public class BeanCreationTestBean
+{
+    private String stringValue;
+
+    private int intValue;
+
+    private BeanCreationTestBean buddy;
+
+    public BeanCreationTestBean getBuddy()
+    {
+        return buddy;
+    }
+
+    public void setBuddy(BeanCreationTestBean buddy)
+    {
+        this.buddy = buddy;
+    }
+
+    public int getIntValue()
+    {
+        return intValue;
+    }
+
+    public void setIntValue(int intValue)
+    {
+        this.intValue = intValue;
+    }
+
+    public String getStringValue()
+    {
+        return stringValue;
+    }
+
+    public void setStringValue(String stringValue)
+    {
+        this.stringValue = stringValue;
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/beanutils/BeanCreationTestBeanWithListChild.java b/src/test/java/org/apache/commons/configuration/beanutils/BeanCreationTestBeanWithListChild.java
new file mode 100644
index 0000000..40d3113
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/beanutils/BeanCreationTestBeanWithListChild.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.beanutils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A simple bean class used for testing bean creation operations that has
+ * a list of children of a different bean type.
+ *
+ * @version $Id: BeanCreationTestBeanWithListChild.java 1534410 2013-10-21 23:13:34Z henning $
+ */
+public class BeanCreationTestBeanWithListChild
+{
+    private String stringValue;
+
+    private int intValue;
+
+    private List<BeanCreationTestBean> children = new ArrayList<BeanCreationTestBean>();
+
+    public List<BeanCreationTestBean> getChildren()
+    {
+        return children;
+    }
+
+    public void setChildren(List<BeanCreationTestBean> buddies)
+    {
+        this.children.clear();
+        this.children.addAll(buddies);
+    }
+
+    public int getIntValue()
+    {
+        return intValue;
+    }
+
+    public void setIntValue(int intValue)
+    {
+        this.intValue = intValue;
+    }
+
+    public String getStringValue()
+    {
+        return stringValue;
+    }
+
+    public void setStringValue(String stringValue)
+    {
+        this.stringValue = stringValue;
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/beanutils/TestBeanHelper.java b/src/test/java/org/apache/commons/configuration/beanutils/TestBeanHelper.java
new file mode 100644
index 0000000..0ff1032
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/beanutils/TestBeanHelper.java
@@ -0,0 +1,613 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.beanutils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.configuration.ConfigurationRuntimeException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for BeanHelper.
+ *
+ * @since 1.3
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: TestBeanHelper.java 1534393 2013-10-21 22:02:27Z henning $
+ */
+public class TestBeanHelper
+{
+    /** Constant for the test value of the string property. */
+    private static final String TEST_STRING = "testString";
+
+    /** Constant for the test value of the numeric property. */
+    private static final int TEST_INT = 42;
+
+    /** Constant for the name of the test bean factory. */
+    private static final String TEST_FACTORY = "testFactory";
+
+    /**
+     * Stores the default bean factory. Because this is a static field in
+     * BeanHelper it is temporarily stored and reset after the tests.
+     */
+    private BeanFactory tempDefaultBeanFactory;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        tempDefaultBeanFactory = BeanHelper.getDefaultBeanFactory();
+        deregisterFactories();
+    }
+
+    @After
+    public void tearDown() throws Exception
+    {
+        deregisterFactories();
+
+        // Reset old default bean factory
+        BeanHelper.setDefaultBeanFactory(tempDefaultBeanFactory);
+    }
+
+    /**
+     * Removes all bean factories that might have been registered during a test.
+     */
+    private void deregisterFactories()
+    {
+        for (String name : BeanHelper.registeredFactoryNames())
+        {
+            BeanHelper.deregisterBeanFactory(name);
+        }
+        assertTrue("Remaining registered bean factories", BeanHelper
+                .registeredFactoryNames().isEmpty());
+    }
+
+    /**
+     * Tests registering a new bean factory.
+     */
+    @Test
+    public void testRegisterBeanFactory()
+    {
+        assertTrue("List of registered factories is not empty", BeanHelper
+                .registeredFactoryNames().isEmpty());
+        BeanHelper.registerBeanFactory(TEST_FACTORY, new TestBeanFactory());
+        assertEquals("Wrong number of registered factories", 1, BeanHelper
+                .registeredFactoryNames().size());
+        assertTrue("Test factory is not contained", BeanHelper
+                .registeredFactoryNames().contains(TEST_FACTORY));
+    }
+
+    /**
+     * Tries to register a null factory. This should cause an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testRegisterBeanFactoryNull()
+    {
+        BeanHelper.registerBeanFactory(TEST_FACTORY, null);
+    }
+
+    /**
+     * Tries to register a bean factory with a null name. This should cause an
+     * exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testRegisterBeanFactoryNullName()
+    {
+        BeanHelper.registerBeanFactory(null, new TestBeanFactory());
+    }
+
+    /**
+     * Tests to deregister a bean factory.
+     */
+    @Test
+    public void testDeregisterBeanFactory()
+    {
+        assertNull("deregistering non existing factory", BeanHelper
+                .deregisterBeanFactory(TEST_FACTORY));
+        assertNull("deregistering null factory", BeanHelper
+                .deregisterBeanFactory(null));
+        BeanFactory factory = new TestBeanFactory();
+        BeanHelper.registerBeanFactory(TEST_FACTORY, factory);
+        assertSame("Could not deregister factory", factory, BeanHelper
+                .deregisterBeanFactory(TEST_FACTORY));
+        assertTrue("List of factories is not empty", BeanHelper
+                .registeredFactoryNames().isEmpty());
+    }
+
+    /**
+     * Tests whether the default bean factory is correctly initialized.
+     */
+    @Test
+    public void testGetDefaultBeanFactory()
+    {
+        assertSame("Incorrect default bean factory",
+                DefaultBeanFactory.INSTANCE, tempDefaultBeanFactory);
+    }
+
+    /**
+     * Tests setting the default bean factory to null. This should caus an
+     * exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetDefaultBeanFactoryNull()
+    {
+        BeanHelper.setDefaultBeanFactory(null);
+    }
+
+    /**
+     * Tests initializing a bean.
+     */
+    @Test
+    public void testInitBean()
+    {
+        BeanHelper.setDefaultBeanFactory(new TestBeanFactory());
+        TestBeanDeclaration data = setUpBeanDeclaration();
+        TestBean bean = new TestBean();
+        BeanHelper.initBean(bean, data);
+        checkBean(bean);
+    }
+
+    /**
+     * Tests initializing a bean when the bean declaration does not contain any
+     * data.
+     */
+    @Test
+    public void testInitBeanWithNoData()
+    {
+        TestBeanDeclaration data = new TestBeanDeclaration();
+        TestBean bean = new TestBean();
+        BeanHelper.initBean(bean, data);
+        assertNull("Wrong string property", bean.getStringValue());
+        assertEquals("Wrong int property", 0, bean.getIntValue());
+        assertNull("Buddy was set", bean.getBuddy());
+    }
+
+    /**
+     * Tries to initialize a bean with a bean declaration that contains an
+     * invalid property value. This should cause an exception.
+     */
+    @Test(expected = ConfigurationRuntimeException.class)
+    public void testInitBeanWithInvalidProperty()
+    {
+        TestBeanDeclaration data = setUpBeanDeclaration();
+        data.getBeanProperties().put("nonExistingProperty", Boolean.TRUE);
+        BeanHelper.initBean(new TestBean(), data);
+    }
+
+    /**
+     * Tests creating a bean. All necessary information is stored in the bean
+     * declaration.
+     */
+    @Test
+    public void testCreateBean()
+    {
+        TestBeanFactory factory = new TestBeanFactory();
+        BeanHelper.registerBeanFactory(TEST_FACTORY, factory);
+        TestBeanDeclaration data = setUpBeanDeclaration();
+        data.setBeanFactoryName(TEST_FACTORY);
+        data.setBeanClassName(TestBean.class.getName());
+        checkBean((TestBean) BeanHelper.createBean(data, null));
+        assertNull("A parameter was passed", factory.parameter);
+    }
+
+    @Test
+    public void testCreateBeanWithListChildBean()
+    {
+        TestBeanFactory factory = new TestBeanFactory();
+        BeanHelper.registerBeanFactory(TEST_FACTORY, factory);
+        TestBeanDeclaration data = setUpBeanDeclarationWithListChild();
+        data.setBeanFactoryName(TEST_FACTORY);
+        data.setBeanClassName(BeanCreationTestBeanWithListChild.class.getName());
+        checkBean((BeanCreationTestBeanWithListChild) BeanHelper.createBean(data, null));
+        assertNull("A parameter was passed", factory.parameter);
+    }
+
+    /**
+     * Tests creating a bean when no bean declaration is provided. This should
+     * cause an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testCreateBeanWithNullDeclaration()
+    {
+        BeanHelper.createBean(null);
+    }
+
+    /**
+     * Tests creating a bean. The bean's class is specified as the default class
+     * argument.
+     */
+    @Test
+    public void testCreateBeanWithDefaultClass()
+    {
+        BeanHelper.registerBeanFactory(TEST_FACTORY, new TestBeanFactory());
+        TestBeanDeclaration data = setUpBeanDeclaration();
+        data.setBeanFactoryName(TEST_FACTORY);
+        checkBean((TestBean) BeanHelper.createBean(data, TestBean.class));
+    }
+
+    /**
+     * Tests creating a bean when the bean's class is specified as the default
+     * class of the bean factory.
+     */
+    @Test
+    public void testCreateBeanWithFactoryDefaultClass()
+    {
+        TestBeanFactory factory = new TestBeanFactory();
+        factory.supportsDefaultClass = true;
+        BeanHelper.registerBeanFactory(TEST_FACTORY, factory);
+        TestBeanDeclaration data = setUpBeanDeclaration();
+        data.setBeanFactoryName(TEST_FACTORY);
+        checkBean((TestBean) BeanHelper.createBean(data, null));
+    }
+
+    /**
+     * Tries to create a bean when no class is provided. This should cause an
+     * exception.
+     */
+    @Test(expected = ConfigurationRuntimeException.class)
+    public void testCreateBeanWithNoClass()
+    {
+        BeanHelper.registerBeanFactory(TEST_FACTORY, new TestBeanFactory());
+        TestBeanDeclaration data = setUpBeanDeclaration();
+        data.setBeanFactoryName(TEST_FACTORY);
+        BeanHelper.createBean(data, null);
+    }
+
+    /**
+     * Tries to create a bean with a non existing class. This should cause an
+     * exception.
+     */
+    @Test(expected = ConfigurationRuntimeException.class)
+    public void testCreateBeanWithInvalidClass()
+    {
+        BeanHelper.registerBeanFactory(TEST_FACTORY, new TestBeanFactory());
+        TestBeanDeclaration data = setUpBeanDeclaration();
+        data.setBeanFactoryName(TEST_FACTORY);
+        data.setBeanClassName("non.existing.ClassName");
+        BeanHelper.createBean(data, null);
+    }
+
+    /**
+     * Tests creating a bean using the default bean factory.
+     */
+    @Test
+    public void testCreateBeanWithDefaultFactory()
+    {
+        BeanHelper.setDefaultBeanFactory(new TestBeanFactory());
+        TestBeanDeclaration data = setUpBeanDeclaration();
+        data.setBeanClassName(TestBean.class.getName());
+        checkBean((TestBean) BeanHelper.createBean(data, null));
+    }
+
+    /**
+     * Tests creating a bean using a non registered factory.
+     */
+    @Test(expected = ConfigurationRuntimeException.class)
+    public void testCreateBeanWithUnknownFactory()
+    {
+        TestBeanDeclaration data = setUpBeanDeclaration();
+        data.setBeanFactoryName(TEST_FACTORY);
+        data.setBeanClassName(TestBean.class.getName());
+        BeanHelper.createBean(data, null);
+    }
+
+    /**
+     * Tests creating a bean when the factory throws an exception.
+     */
+    @Test(expected = ConfigurationRuntimeException.class)
+    public void testCreateBeanWithException()
+    {
+        BeanHelper.registerBeanFactory(TEST_FACTORY, new TestBeanFactory());
+        TestBeanDeclaration data = setUpBeanDeclaration();
+        data.setBeanFactoryName(TEST_FACTORY);
+        data.setBeanClassName(getClass().getName());
+        BeanHelper.createBean(data, null);
+    }
+
+    /**
+     * Tests if a parameter is correctly passed to the bean factory.
+     */
+    @Test
+    public void testCreateBeanWithParameter()
+    {
+        Object param = new Integer(42);
+        TestBeanFactory factory = new TestBeanFactory();
+        BeanHelper.registerBeanFactory(TEST_FACTORY, factory);
+        TestBeanDeclaration data = setUpBeanDeclaration();
+        data.setBeanFactoryName(TEST_FACTORY);
+        data.setBeanClassName(TestBean.class.getName());
+        checkBean((TestBean) BeanHelper.createBean(data, null, param));
+        assertSame("Wrong parameter", param, factory.parameter);
+    }
+
+    /**
+     * Returns an initialized bean declaration.
+     *
+     * @return the bean declaration
+     */
+    private TestBeanDeclaration setUpBeanDeclaration()
+    {
+        TestBeanDeclaration data = new TestBeanDeclaration();
+        Map<String, Object> properties = new HashMap<String, Object>();
+        properties.put("stringValue", "testString");
+        properties.put("intValue", "42");
+        data.setBeanProperties(properties);
+        TestBeanDeclaration buddyData = new TestBeanDeclaration();
+        Map<String, Object> properties2 = new HashMap<String, Object>();
+        properties2.put("stringValue", "Another test string");
+        properties2.put("intValue", new Integer(100));
+        buddyData.setBeanProperties(properties2);
+        buddyData.setBeanClassName(TestBean.class.getName());
+        if (BeanHelper.getDefaultBeanFactory() == null)
+        {
+            buddyData.setBeanFactoryName(TEST_FACTORY);
+        }
+
+        Map<String, Object> nested = new HashMap<String, Object>();
+        nested.put("buddy", buddyData);
+        data.setNestedBeanDeclarations(nested);
+        return data;
+    }
+
+    /**
+     * Same as setUpBeanDeclaration, but returns a nested array of beans
+     * as a single property. Tests multi-value (Collection<BeanDeclaration>)
+     * children construction.
+     *
+     * @return The bean declaration with a list child bean proerty
+     */
+    private TestBeanDeclaration setUpBeanDeclarationWithListChild()
+    {
+        TestBeanDeclaration data = new TestBeanDeclaration();
+        Map<String, Object> properties = new HashMap<String, Object>();
+        properties.put("stringValue", TEST_STRING);
+        properties.put("intValue", String.valueOf(TEST_INT));
+        data.setBeanProperties(properties);
+
+        List<BeanDeclaration> childData = new ArrayList<BeanDeclaration>();
+        childData.add(createChildBean("child1"));
+        childData.add(createChildBean("child2"));
+        Map<String, Object> nested = new HashMap<String, Object>();
+        nested.put("children", childData);
+        data.setNestedBeanDeclarations(nested);
+        return data;
+    }
+
+    /**
+     * Create a simple bean declaration that has no children for testing
+     * of nested children bean declarations.
+     *
+     * @param name A name prefix that can be used to disambiguate the children
+     * @return A simple declaration
+     */
+    private TestBeanDeclaration createChildBean(String name)
+    {
+        TestBeanDeclaration childBean = new TestBeanDeclaration();
+        Map<String, Object> properties2 = new HashMap<String, Object>();
+        properties2.put("stringValue", name + " Another test string");
+        properties2.put("intValue", new Integer(100));
+        childBean.setBeanProperties(properties2);
+        childBean.setBeanClassName(BeanCreationTestBean.class.getName());
+        if (BeanHelper.getDefaultBeanFactory() == null)
+        {
+            childBean.setBeanFactoryName(TEST_FACTORY);
+        }
+
+        return childBean;
+    }
+
+    /**
+     * Tests if the bean was correctly initialized from the data of the test
+     * bean declaration.
+     *
+     * @param bean the bean to be checked
+     */
+    private void checkBean(TestBean bean)
+    {
+        assertEquals("Wrong string property", "testString", bean
+                .getStringValue());
+        assertEquals("Wrong int property", 42, bean.getIntValue());
+        TestBean buddy = bean.getBuddy();
+        assertNotNull("Buddy was not set", buddy);
+        assertEquals("Wrong string property in buddy", "Another test string",
+                buddy.getStringValue());
+        assertEquals("Wrong int property in buddy", 100, buddy.getIntValue());
+    }
+
+    /**
+     * A simple bean class used for testing creation operations.
+     */
+    public static class TestBean
+    {
+        private String stringValue;
+
+        private int intValue;
+
+        private TestBean buddy;
+
+        public TestBean getBuddy()
+        {
+            return buddy;
+        }
+
+        public void setBuddy(TestBean buddy)
+        {
+            this.buddy = buddy;
+        }
+
+        public int getIntValue()
+        {
+            return intValue;
+        }
+
+        public void setIntValue(int intValue)
+        {
+            this.intValue = intValue;
+        }
+
+        public String getStringValue()
+        {
+            return stringValue;
+        }
+
+        public void setStringValue(String stringValue)
+        {
+            this.stringValue = stringValue;
+        }
+    }
+
+    /**
+     * Tests if the bean was correctly initialized from the data of the test
+     * bean declaration.
+     *
+     * @param bean the bean to be checked
+     */
+    private void checkBean(BeanCreationTestBeanWithListChild bean)
+    {
+        assertEquals("Wrong string property", TEST_STRING, bean
+                .getStringValue());
+        assertEquals("Wrong int property", TEST_INT, bean.getIntValue());
+        List<BeanCreationTestBean> children = bean.getChildren();
+        assertNotNull("Children were not set", children);
+        assertEquals("Wrong number of children created", children.size(), 2);
+        assertNotNull("First child was set as null", children.get(0));
+        assertNotNull("Second child was set as null", children.get(1));
+    }
+
+    /**
+     * An implementation of the BeanFactory interface used for testing. This
+     * implementation is really simple: If the TestBean class is provided, a new
+     * instance will be created. Otherwise an exception is thrown.
+     */
+    static class TestBeanFactory implements BeanFactory
+    {
+        Object parameter;
+
+        boolean supportsDefaultClass;
+
+        public Object createBean(Class<?> beanClass, BeanDeclaration data, Object param)
+                throws Exception
+        {
+            parameter = param;
+            if (TestBean.class.equals(beanClass))
+            {
+                TestBean bean = new TestBean();
+                BeanHelper.initBean(bean, data);
+                return bean;
+            }
+            else if (BeanCreationTestBeanWithListChild.class.equals(beanClass))
+            {
+                BeanCreationTestBeanWithListChild bean = new BeanCreationTestBeanWithListChild();
+                BeanHelper.initBean(bean, data);
+                return bean;
+            }
+            else
+            {
+                throw new IllegalArgumentException("Unsupported class: "
+                        + beanClass);
+            }
+        }
+
+        /**
+         * Returns the default class, but only if the supportsDefaultClass flag
+         * is set.
+         */
+        public Class<?> getDefaultBeanClass()
+        {
+            return supportsDefaultClass ? TestBean.class : null;
+        }
+    }
+
+    /**
+     * A test implementation of the BeanDeclaration interface. This
+     * implementation allows to set the values directly, which should be
+     * returned by the methods required by the BeanDeclaration interface.
+     */
+    static class TestBeanDeclaration implements BeanDeclaration
+    {
+        private String beanClassName;
+
+        private String beanFactoryName;
+
+        private Object beanFactoryParameter;
+
+        private Map<String, Object> beanProperties;
+
+        private Map<String, Object> nestedBeanDeclarations;
+
+        public String getBeanClassName()
+        {
+            return beanClassName;
+        }
+
+        public void setBeanClassName(String beanClassName)
+        {
+            this.beanClassName = beanClassName;
+        }
+
+        public String getBeanFactoryName()
+        {
+            return beanFactoryName;
+        }
+
+        public void setBeanFactoryName(String beanFactoryName)
+        {
+            this.beanFactoryName = beanFactoryName;
+        }
+
+        public Object getBeanFactoryParameter()
+        {
+            return beanFactoryParameter;
+        }
+
+        public void setBeanFactoryParameter(Object beanFactoryParameter)
+        {
+            this.beanFactoryParameter = beanFactoryParameter;
+        }
+
+        public Map<String, Object> getBeanProperties()
+        {
+            return beanProperties;
+        }
+
+        public void setBeanProperties(Map<String, Object> beanProperties)
+        {
+            this.beanProperties = beanProperties;
+        }
+
+        public Map<String, Object> getNestedBeanDeclarations()
+        {
+            return nestedBeanDeclarations;
+        }
+
+        public void setNestedBeanDeclarations(Map<String, Object> nestedBeanDeclarations)
+        {
+            this.nestedBeanDeclarations = nestedBeanDeclarations;
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/beanutils/TestConfigurationDynaBean.java b/src/test/java/org/apache/commons/configuration/beanutils/TestConfigurationDynaBean.java
new file mode 100644
index 0000000..40bfc27
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/beanutils/TestConfigurationDynaBean.java
@@ -0,0 +1,734 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.beanutils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+import junitx.framework.ObjectAssert;
+
+import org.apache.commons.beanutils.DynaProperty;
+import org.apache.commons.configuration.BaseConfiguration;
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.MapConfiguration;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * <p>Test Case for the <code>ConfigurationDynaBean</code> implementation class.
+ * These tests were based on the ones in <code>BasicDynaBeanTestCase</code>
+ * because the two classes provide similar levels of functionality.</p>
+ *
+ * @author <a href="mailto:ricardo.gladwell at btinternet.com">Ricardo Gladwell</a>
+ * @version $Id: TestConfigurationDynaBean.java 1225349 2011-12-28 21:36:59Z oheger $
+ */
+public class TestConfigurationDynaBean
+{
+    /**
+     * The basic test bean for each test.
+     */
+    private ConfigurationDynaBean bean;
+
+    /**
+     * The set of property names we expect to have returned when calling
+     * <code>getDynaProperties()</code>.  You should update this list
+     * when new properties are added to TestBean.
+     */
+    String[] properties = {
+            "booleanProperty",
+            "booleanSecond",
+            "doubleProperty",
+            "floatProperty",
+            "intProperty",
+            "longProperty",
+            "mappedProperty.key1",
+            "mappedProperty.key2",
+            "mappedProperty.key3",
+            "mappedIntProperty.key1",
+            "shortProperty",
+            "stringProperty",
+            "byteProperty",
+            "charProperty"
+    };
+
+    Object[] values = {
+            Boolean.TRUE,
+            Boolean.TRUE,
+            new Double(Double.MAX_VALUE),
+            new Float(Float.MAX_VALUE),
+            new Integer(Integer.MAX_VALUE),
+            new Long(Long.MAX_VALUE),
+            "First Value",
+            "Second Value",
+            "Third Value",
+            new Integer(Integer.MAX_VALUE),
+            new Short(Short.MAX_VALUE),
+            "This is a string",
+            new Byte(Byte.MAX_VALUE),
+            new Character(Character.MAX_VALUE)
+    };
+
+    int[] intArray = {0, 10, 20, 30, 40};
+    boolean[] booleanArray = {true, false, true, false, true};
+    char[] charArray = {'a', 'b', 'c', 'd', 'e'};
+    byte[] byteArray = {0, 10, 20, 30, 40};
+    long[] longArray = {0, 10, 20, 30, 40};
+    short[] shortArray = {0, 10, 20, 30, 40};
+    float[] floatArray = {0, 10, 20, 30, 40};
+    double[] doubleArray = {0.0, 10.0, 20.0, 30.0, 40.0};
+    String[] stringArray = {"String 0", "String 1", "String 2", "String 3", "String 4"};
+
+
+    /**
+     * Set up instance variables required by this test case.
+     */
+    @Before
+    public void setUp() throws Exception
+    {
+        Configuration configuration = createConfiguration();
+
+        for (int i = 0; i < properties.length; i++)
+        {
+            configuration.setProperty(properties[i], values[i]);
+        }
+
+        for (int a = 0; a < intArray.length; a++)
+        {
+            configuration.addProperty("intIndexed", new Integer(intArray[a]));
+        }
+
+        for (int a = 0; a < stringArray.length; a++)
+        {
+            configuration.addProperty("stringIndexed", stringArray[a]);
+        }
+
+        List<String> list = Arrays.asList(stringArray);
+        configuration.addProperty("listIndexed", list);
+
+        bean = new ConfigurationDynaBean(configuration);
+
+        bean.set("listIndexed", list);
+        bean.set("intArray", intArray);
+        bean.set("booleanArray", booleanArray);
+        bean.set("charArray", charArray);
+        bean.set("longArray", longArray);
+        bean.set("shortArray", shortArray);
+        bean.set("floatArray", floatArray);
+        bean.set("doubleArray", doubleArray);
+        bean.set("byteArray", byteArray);
+        bean.set("stringArray", stringArray);
+    }
+
+    /**
+     * Creates the underlying configuration object for the dyna bean.
+     * @return the underlying configuration object
+     */
+    protected Configuration createConfiguration()
+    {
+        return new BaseConfiguration();
+    }
+
+    /**
+     * Corner cases on getDynaProperty invalid arguments.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetDescriptorArguments()
+    {
+        DynaProperty descriptor = bean.getDynaClass().getDynaProperty("unknown");
+        assertNull("Unknown property descriptor should be null", descriptor);
+        bean.getDynaClass().getDynaProperty(null);
+    }
+
+    /**
+     * Positive getDynaProperty on property <code>booleanProperty</code>.
+     */
+    @Test
+    public void testGetDescriptorBoolean()
+    {
+        testGetDescriptorBase("booleanProperty", Boolean.TYPE);
+    }
+
+    /**
+     * Positive getDynaProperty on property <code>doubleProperty</code>.
+     */
+    @Test
+    public void testGetDescriptorDouble()
+    {
+        testGetDescriptorBase("doubleProperty", Double.TYPE);
+    }
+
+    /**
+     * Positive getDynaProperty on property <code>floatProperty</code>.
+     */
+    @Test
+    public void testGetDescriptorFloat()
+    {
+        testGetDescriptorBase("floatProperty", Float.TYPE);
+    }
+
+    /**
+     * Positive getDynaProperty on property <code>intProperty</code>.
+     */
+    @Test
+    public void testGetDescriptorInt()
+    {
+        testGetDescriptorBase("intProperty", Integer.TYPE);
+    }
+
+    /**
+     * Positive getDynaProperty on property <code>longProperty</code>.
+     */
+    @Test
+    public void testGetDescriptorLong()
+    {
+        testGetDescriptorBase("longProperty", Long.TYPE);
+    }
+
+    /**
+     * Positive getDynaProperty on property <code>booleanSecond</code>
+     * that uses an "is" method as the getter.
+     */
+    @Test
+    public void testGetDescriptorSecond()
+    {
+        testGetDescriptorBase("booleanSecond", Boolean.TYPE);
+    }
+
+    /**
+     * Positive getDynaProperty on property <code>shortProperty</code>.
+     */
+    @Test
+    public void testGetDescriptorShort()
+    {
+        testGetDescriptorBase("shortProperty", Short.TYPE);
+    }
+
+    /**
+     * Positive getDynaProperty on property <code>stringProperty</code>.
+     */
+    @Test
+    public void testGetDescriptorString()
+    {
+        testGetDescriptorBase("stringProperty", String.class);
+    }
+
+    /**
+     * Positive test for getDynaPropertys().  Each property name
+     * listed in <code>properties</code> should be returned exactly once.
+     */
+    @Test
+    public void testGetDescriptors()
+    {
+        DynaProperty pd[] = bean.getDynaClass().getDynaProperties();
+        assertNotNull("Got descriptors", pd);
+        int count[] = new int[properties.length];
+        for (int i = 0; i < pd.length; i++)
+        {
+            String name = pd[i].getName();
+            for (int j = 0; j < properties.length; j++)
+            {
+                if (name.equals(properties[j]))
+                {
+                    count[j]++;
+                }
+            }
+        }
+
+        for (int j = 0; j < properties.length; j++)
+        {
+            if (count[j] < 0)
+            {
+                fail("Missing property " + properties[j]);
+            }
+            else if (count[j] > 1)
+            {
+                fail("Duplicate property " + properties[j]);
+            }
+        }
+    }
+
+    /**
+     * Corner cases on getIndexedProperty invalid arguments.
+     */
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testGetIndexedArguments()
+    {
+        bean.get("intArray", -1);
+    }
+
+    /**
+     * Positive and negative tests on getIndexedProperty valid arguments.
+     */
+    @Test
+    public void testGetIndexedValues()
+    {
+        for (int i = 0; i < 5; i++)
+        {
+            Object value = bean.get("intArray", i);
+
+            assertNotNull("intArray index " + i + " did not return value.", value);
+            ObjectAssert.assertInstanceOf("intArray index " + i, Integer.class, value);
+            assertEquals("intArray " + i + " returned incorrect value.", i * 10, ((Integer) value).intValue());
+
+            value = bean.get("intIndexed", i);
+
+            assertNotNull("intIndexed index " + i + "returned value " + i, value);
+            ObjectAssert.assertInstanceOf("intIndexed index " + i, Integer.class, value);
+            assertEquals("intIndexed index " + i + "returned correct " + i, i * 10, ((Integer) value).intValue());
+
+            value = bean.get("listIndexed", i);
+
+            assertNotNull("listIndexed index " + i + "returned value " + i, value);
+            ObjectAssert.assertInstanceOf("list index " + i, String.class, value);
+            assertEquals("listIndexed index " + i + "returned correct " + i, "String " + i, value);
+
+            value = bean.get("stringArray", i);
+
+            assertNotNull("stringArray index " + i + " returnde null.", value);
+            assertFalse("stringArray index " + i + " returned array instead of String.", value.getClass().isArray());
+            ObjectAssert.assertInstanceOf("stringArray index " + i, String.class, value);
+            assertEquals("stringArray returned correct " + i, "String " + i, value);
+
+            value = bean.get("stringIndexed", i);
+
+            assertNotNull("stringIndexed returned value " + i, value);
+            ObjectAssert.assertInstanceOf("stringIndexed", String.class, value);
+            assertEquals("stringIndexed returned correct " + i, "String " + i, value);
+        }
+    }
+
+    /**
+     * Corner cases on getMappedProperty invalid arguments.
+     */
+    @Test
+    public void testGetMappedArguments()
+    {
+        try
+        {
+            Object value = bean.get("mappedProperty", "unknown");
+            assertNull("Should not return a value", value);
+        }
+        catch (Throwable t)
+        {
+            fail("Threw " + t + " instead of returning null");
+        }
+    }
+
+    /**
+     * Positive and negative tests on getMappedProperty valid arguments.
+     */
+    @Test
+    public void testGetMappedValues()
+    {
+        Object value = bean.get("mappedProperty", "key1");
+        assertEquals("Can find first value", "First Value", value);
+
+        value = bean.get("mappedProperty", "key2");
+        assertEquals("Can find second value", "Second Value", value);
+
+        value = bean.get("mappedProperty", "key3");
+        assertNotNull("Cannot find third value", value);
+    }
+
+    /**
+     * Corner cases on getSimpleProperty invalid arguments.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetSimpleArguments()
+    {
+        bean.get("a non existing property");
+    }
+
+    /**
+     * Test getSimpleProperty on a boolean property.
+     */
+    @Test
+    public void testGetSimpleBoolean()
+    {
+        Object value = bean.get("booleanProperty");
+        assertNotNull("Got a value", value);
+        ObjectAssert.assertInstanceOf("Got correct type", Boolean.class, value);
+        assertTrue("Got correct value", ((Boolean) value).booleanValue());
+    }
+
+    /**
+     * Test getSimpleProperty on a double property.
+     */
+    @Test
+    public void testGetSimpleDouble()
+    {
+        Object value = bean.get("doubleProperty");
+        assertNotNull("Got a value", value);
+        ObjectAssert.assertInstanceOf("Got correct type", Double.class, value);
+        assertEquals("Got correct value", ((Double) value).doubleValue(), Double.MAX_VALUE, 0.005);
+    }
+
+    /**
+     * Test getSimpleProperty on a float property.
+     */
+    @Test
+    public void testGetSimpleFloat()
+    {
+        Object value = bean.get("floatProperty");
+        assertNotNull("Got a value", value);
+        ObjectAssert.assertInstanceOf("Got correct type", Float.class, value);
+        assertEquals("Got correct value", ((Float) value).floatValue(), Float.MAX_VALUE, 0.005f);
+    }
+
+    /**
+     * Test getSimpleProperty on a int property.
+     */
+    @Test
+    public void testGetSimpleInt()
+    {
+        Object value = bean.get("intProperty");
+        assertNotNull("Failed to get value", value);
+        ObjectAssert.assertInstanceOf("Incorrect type", Integer.class, value);
+        assertEquals("Incorrect value", ((Integer) value).intValue(), Integer.MAX_VALUE);
+    }
+
+    /**
+     * Test getSimpleProperty on a long property.
+     */
+    @Test
+    public void testGetSimpleLong()
+    {
+        Object value = bean.get("longProperty");
+        assertNotNull("Got a value", value);
+        ObjectAssert.assertInstanceOf("Returned incorrect type", Long.class, value);
+        assertEquals("Returned value of Incorrect value", ((Long) value).longValue(), Long.MAX_VALUE);
+    }
+
+    /**
+     * Test getSimpleProperty on a short property.
+     */
+    @Test
+    public void testGetSimpleShort()
+    {
+        Object value = bean.get("shortProperty");
+        assertNotNull("Got a value", value);
+        ObjectAssert.assertInstanceOf("Got correct type", Short.class, value);
+        assertEquals("Got correct value", ((Short) value).shortValue(), Short.MAX_VALUE);
+    }
+
+    /**
+     * Test getSimpleProperty on a String property.
+     */
+    @Test
+    public void testGetSimpleString()
+    {
+        Object value = bean.get("stringProperty");
+        assertNotNull("Got a value", value);
+        ObjectAssert.assertInstanceOf("Got correct type", String.class, value);
+        assertEquals("Got correct value", value, "This is a string");
+    }
+
+    /**
+     * Test <code>contains()</code> method for mapped properties.
+     */
+    @Test
+    public void testMappedContains()
+    {
+        assertTrue("Can't see first key", bean.contains("mappedProperty", "key1"));
+        assertTrue("Can see unknown key", !bean.contains("mappedProperty", "Unknown Key"));
+    }
+
+    /**
+     * Test <code>remove()</code> method for mapped properties.
+     */
+    @Test
+    public void testMappedRemove()
+    {
+        assertTrue("Can see first key", bean.contains("mappedProperty", "key1"));
+        bean.remove("mappedProperty", "key1");
+        assertTrue("Can not see first key", !bean.contains("mappedProperty", "key1"));
+
+        assertTrue("Can not see unknown key", !bean.contains("mappedProperty", "key4"));
+        bean.remove("mappedProperty", "key4");
+        assertTrue("Can not see unknown key", !bean.contains("mappedProperty", "key4"));
+    }
+
+    /**
+     * Corner cases on setIndexedProperty invalid arguments.
+     */
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testSetIndexedArguments()
+    {
+        bean.set("intArray", -1, new Integer(0));
+    }
+
+    /**
+     * Positive and negative tests on setIndexedProperty valid arguments.
+     */
+    @Test
+    public void testSetIndexedValues()
+    {
+        bean.set("intArray", 0, new Integer(1));
+        Object value = bean.get("intArray", 0);
+
+        assertNotNull("Returned new value 0", value);
+        ObjectAssert.assertInstanceOf("Returned Integer new value 0", Integer.class,  value);
+        assertEquals("Returned correct new value 0", 1, ((Integer) value).intValue());
+
+
+        bean.set("intIndexed", 1, new Integer(11));
+        value = bean.get("intIndexed", 1);
+
+        assertNotNull("Returned new value 1", value);
+        ObjectAssert.assertInstanceOf("Returned Integer new value 1", Integer.class,  value);
+        assertEquals("Returned correct new value 1", 11, ((Integer) value).intValue());
+
+
+        bean.set("listIndexed", 2, "New Value 2");
+        value = bean.get("listIndexed", 2);
+
+        assertNotNull("Returned new value 2", value);
+        ObjectAssert.assertInstanceOf("Returned String new value 2", String.class,  value);
+        assertEquals("Returned correct new value 2", "New Value 2", value);
+
+
+        bean.set("stringArray", 3, "New Value 3");
+        value = bean.get("stringArray", 3);
+
+        assertNotNull("Returned new value 3", value);
+        ObjectAssert.assertInstanceOf("Returned String new value 3", String.class,  value);
+        assertEquals("Returned correct new value 3", "New Value 3", value);
+
+
+        bean.set("stringIndexed", 4, "New Value 4");
+        value = bean.get("stringIndexed", 4);
+
+        assertNotNull("Returned new value 4", value);
+        ObjectAssert.assertInstanceOf("Returned String new value 4", String.class,  value);
+        assertEquals("Returned correct new value 4", "New Value 4", value);
+    }
+
+    /**
+     * Test the modification of a configuration property stored internally as an array.
+     */
+    @Test
+    public void testSetArrayValue()
+    {
+        MapConfiguration configuration = new MapConfiguration(new HashMap<String, Object>());
+        configuration.getMap().put("objectArray", new Object[] {"value1", "value2", "value3"});
+
+        ConfigurationDynaBean bean = new ConfigurationDynaBean(configuration);
+
+        bean.set("objectArray", 1, "New Value 1");
+        Object value = bean.get("objectArray", 1);
+
+        assertNotNull("Returned new value 1", value);
+        ObjectAssert.assertInstanceOf("Returned String new value 1", String.class,  value);
+        assertEquals("Returned correct new value 1", "New Value 1", value);
+    }
+
+    /**
+     * Positive and negative tests on setMappedProperty valid arguments.
+     */
+    @Test
+    public void testSetMappedValues()
+    {
+        bean.set("mappedProperty", "First Key", "New First Value");
+        assertEquals("Can replace old value", "New First Value", bean.get("mappedProperty", "First Key"));
+
+        bean.set("mappedProperty", "Fourth Key", "Fourth Value");
+        assertEquals("Can set new value", "Fourth Value", bean.get("mappedProperty", "Fourth Key"));
+    }
+
+    /**
+     * Test setSimpleProperty on a boolean property.
+     */
+    @Test
+    public void testSetSimpleBoolean()
+    {
+        boolean oldValue = ((Boolean) bean.get("booleanProperty")).booleanValue();
+        boolean newValue = !oldValue;
+        bean.set("booleanProperty", new Boolean(newValue));
+        assertTrue("Matched new value", newValue == ((Boolean) bean.get("booleanProperty")).booleanValue());
+    }
+
+    /**
+     * Test setSimpleProperty on a double property.
+     */
+    @Test
+    public void testSetSimpleDouble()
+    {
+        double oldValue = ((Double) bean.get("doubleProperty")).doubleValue();
+        double newValue = oldValue + 1.0;
+        bean.set("doubleProperty", new Double(newValue));
+        assertEquals("Matched new value", newValue, ((Double) bean.get("doubleProperty")).doubleValue(), 0.005);
+    }
+
+    /**
+     * Test setSimpleProperty on a float property.
+     */
+    @Test
+    public void testSetSimpleFloat()
+    {
+        float oldValue = ((Float) bean.get("floatProperty")).floatValue();
+        float newValue = oldValue + (float) 1.0;
+        bean.set("floatProperty", new Float(newValue));
+        assertEquals("Matched new value", newValue, ((Float) bean.get("floatProperty")).floatValue(), 0.005f);
+    }
+
+    /**
+     * Test setSimpleProperty on a int property.
+     */
+    @Test
+    public void testSetSimpleInt()
+    {
+        int oldValue = ((Integer) bean.get("intProperty")).intValue();
+        int newValue = oldValue + 1;
+        bean.set("intProperty", new Integer(newValue));
+        assertEquals("Matched new value", newValue, ((Integer) bean.get("intProperty")).intValue());
+    }
+
+    /**
+     * Test setSimpleProperty on a long property.
+     */
+    @Test
+    public void testSetSimpleLong()
+    {
+        long oldValue = ((Long) bean.get("longProperty")).longValue();
+        long newValue = oldValue + 1;
+        bean.set("longProperty", new Long(newValue));
+        assertEquals("Matched new value", newValue, ((Long) bean.get("longProperty")).longValue());
+    }
+
+    /**
+     * Test setSimpleProperty on a short property.
+     */
+    @Test
+    public void testSetSimpleShort()
+    {
+        short oldValue = ((Short) bean.get("shortProperty")).shortValue();
+        short newValue = (short) (oldValue + 1);
+        bean.set("shortProperty", new Short(newValue));
+        assertEquals("Matched new value", newValue, ((Short) bean.get("shortProperty")).shortValue());
+    }
+
+    /**
+     * Test setSimpleProperty on a String property.
+     */
+    @Test
+    public void testSetSimpleString()
+    {
+        String oldValue = (String) bean.get("stringProperty");
+        String newValue = oldValue + " Extra Value";
+        bean.set("stringProperty", newValue);
+        assertEquals("Matched new value", newValue, bean.get("stringProperty"));
+    }
+
+    /**
+     * Tests set on a null value: should throw NPE.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testAddNullPropertyValue()
+    {
+        bean.set("nullProperty", null);
+    }
+
+    /**
+     * Test the retrieval of a non-existent property.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetNonExistentProperty()
+    {
+        bean.get("nonexistProperty");
+    }
+
+    /**
+     * Base for testGetDescriptorXxxxx() series of tests.
+     *
+     * @param name Name of the property to be retrieved
+     * @param type Expected class type of this property
+     */
+    protected void testGetDescriptorBase(String name, Class<?> type)
+    {
+        DynaProperty descriptor = bean.getDynaClass().getDynaProperty(name);
+
+        assertNotNull("Failed to get descriptor", descriptor);
+        assertEquals("Got incorrect type", type, descriptor.getType());
+    }
+
+    /**
+     * Tests whether nested properties can be accessed.
+     */
+    @Test
+    public void testNestedPropeties()
+    {
+        ConfigurationDynaBean nested = (ConfigurationDynaBean) bean.get("mappedProperty");
+
+        String value = (String) nested.get("key1");
+        assertEquals("Can find first value", "First Value", value);
+
+        nested.set("key1", "undefined");
+        assertEquals("Incorrect value returned", "undefined", bean.get("mappedProperty.key1"));
+    }
+
+    /**
+     * Tests if reading a non-indexed property using the index
+     * get method throws an IllegalArgumentException as it
+     * should.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetNonIndexedProperties()
+    {
+        bean.get("booleanProperty", 0);
+    }
+
+    /**
+     * Tests whether accessing a non-indexed string property using the index get
+     * method causes an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetIndexedString()
+    {
+        bean.set("stringProp", "value");
+        bean.get("stringProp", 0);
+    }
+
+    /**
+     * Tests whether an indexed access to a non-existing property causes an
+     * exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetIndexedNonExisting()
+    {
+        bean.get("Non existing property", 0);
+    }
+
+    /**
+     * Tests if writing a non-indexed property using the index
+     * set method with an index > 0 throws an IllegalArgumentException as it
+     * should.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetNonIndexedProperties()
+    {
+        bean.set("booleanProperty", 1, Boolean.TRUE);
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/beanutils/TestConfigurationDynaBeanXMLConfig.java b/src/test/java/org/apache/commons/configuration/beanutils/TestConfigurationDynaBeanXMLConfig.java
new file mode 100644
index 0000000..ccc7740
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/beanutils/TestConfigurationDynaBeanXMLConfig.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.beanutils;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.XMLConfiguration;
+
+/**
+ * An additional test class for ConfigurationDynaBean. This test class performs
+ * the same tests as the default test class, but uses a XMLConfiguration as
+ * underlying configuration object.
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: TestConfigurationDynaBeanXMLConfig.java 1301994 2012-03-17 20:21:23Z sebb $
+ */
+public class TestConfigurationDynaBeanXMLConfig extends
+        TestConfigurationDynaBean
+{
+    /**
+     * Creates the underlying configuration object. This implementation will
+     * create a XMLConfiguration.
+     * @return the underlying configuration
+     */
+    @Override
+    protected Configuration createConfiguration()
+    {
+        return new XMLConfiguration();
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/beanutils/TestDefaultBeanFactory.java b/src/test/java/org/apache/commons/configuration/beanutils/TestDefaultBeanFactory.java
new file mode 100644
index 0000000..d34e066
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/beanutils/TestDefaultBeanFactory.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.beanutils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for DefaultBeanFactory.
+ *
+ * @since 1.3
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: TestDefaultBeanFactory.java 1225642 2011-12-29 20:31:38Z oheger $
+ */
+public class TestDefaultBeanFactory
+{
+    /** The object to be tested. */
+    DefaultBeanFactory factory;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        factory = new DefaultBeanFactory();
+    }
+
+    /**
+     * Tests obtaining the default class. This should be null.
+     */
+    @Test
+    public void testGetDefaultBeanClass()
+    {
+        assertNull("Default class is not null", factory.getDefaultBeanClass());
+    }
+
+    /**
+     * Tests creating a bean.
+     */
+    @Test
+    public void testCreateBean() throws Exception
+    {
+        Object bean = factory.createBean(PropertiesConfiguration.class,
+                new TestBeanDeclaration(), null);
+        assertNotNull("New bean is null", bean);
+        assertEquals("Bean is of wrong class", PropertiesConfiguration.class,
+                bean.getClass());
+        PropertiesConfiguration config = (PropertiesConfiguration) bean;
+        assertTrue("Bean was not initialized", config
+                .isThrowExceptionOnMissing());
+    }
+
+    /**
+     * A simple implementation of BeanDeclaration used for testing purposes.
+     */
+    static class TestBeanDeclaration implements BeanDeclaration
+    {
+        public String getBeanFactoryName()
+        {
+            return null;
+        }
+
+        public Object getBeanFactoryParameter()
+        {
+            return null;
+        }
+
+        public String getBeanClassName()
+        {
+            return null;
+        }
+
+        public Map<String, Object> getBeanProperties()
+        {
+            Map<String, Object> props = new HashMap<String, Object>();
+            props.put("throwExceptionOnMissing", Boolean.TRUE);
+            return props;
+        }
+
+        public Map<String, Object> getNestedBeanDeclarations()
+        {
+            return null;
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/beanutils/TestXMLBeanDeclaration.java b/src/test/java/org/apache/commons/configuration/beanutils/TestXMLBeanDeclaration.java
new file mode 100644
index 0000000..cf7da51
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/beanutils/TestXMLBeanDeclaration.java
@@ -0,0 +1,425 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.beanutils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Map;
+
+import org.apache.commons.configuration.HierarchicalConfiguration;
+import org.apache.commons.configuration.SubnodeConfiguration;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.junit.Test;
+
+/**
+ * Test class for XMLBeanDeclaration.
+ *
+ * @since 1.3
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: TestXMLBeanDeclaration.java 1225643 2011-12-29 20:37:36Z oheger $
+ */
+public class TestXMLBeanDeclaration
+{
+    /** An array with some test properties. */
+    static final String[] TEST_PROPS =
+    { "firstName", "lastName", "department", "age", "hobby"};
+
+    /** An array with the values for the test properties. */
+    static final String[] TEST_VALUES =
+    { "John", "Smith", "Engineering", "42", "TV"};
+
+    /** An array with the names of nested (complex) properties. */
+    static final String[] COMPLEX_PROPS =
+    { "address", "car"};
+
+    /** An array with the names of the classes of the complex properties. */
+    static final String[] COMPLEX_CLASSES =
+    { "org.apache.commons.configuration.test.AddressTest",
+            "org.apache.commons.configuration.test.CarTest"};
+
+    /** An array with the property names of the complex properties. */
+    static final String[][] COMPLEX_ATTRIBUTES =
+    {
+    { "street", "zip", "city", "country"},
+    { "brand", "color"}};
+
+    /** An array with the values of the complex properties. */
+    static final String[][] COMPLEX_VALUES =
+    {
+    { "Baker Street", "12354", "London", "UK"},
+    { "Bentley", "silver"}};
+
+    /** Constant for the key with the bean declaration. */
+    static final String KEY = "myBean";
+
+    /** Constant for the section with the variables.*/
+    static final String VARS = "variables.";
+
+    /** Stores the object to be tested. */
+    XMLBeanDeclaration decl;
+
+    /**
+     * Tests creating a declaration from a null node. This should cause an
+     * exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInitFromNullNode()
+    {
+        decl = new XMLBeanDeclaration(new HierarchicalConfiguration().configurationAt(null),
+                (ConfigurationNode) null);
+    }
+
+    /**
+     * Tests creating a declaration from a null configuration. This should cause
+     * an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInitFromNullConfiguration()
+    {
+        decl = new XMLBeanDeclaration((HierarchicalConfiguration) null);
+    }
+
+    /**
+     * Tests creating a declaration from a null configuration with a key. This
+     * should cause an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInitFromNullConfigurationAndKey()
+    {
+        decl = new XMLBeanDeclaration(null, KEY);
+    }
+
+    /**
+     * Tests creating a declaration from a null configuration with a node. This
+     * should cause an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInitFromNullConfigurationAndNode()
+    {
+        decl = new XMLBeanDeclaration(null, new HierarchicalConfiguration()
+                .getRoot());
+    }
+
+    /**
+     * Tests fetching the bean's class name.
+     */
+    @Test
+    public void testGetBeanClassName()
+    {
+        HierarchicalConfiguration config = new HierarchicalConfiguration();
+        config.addProperty(KEY + "[@config-class]", getClass().getName());
+        decl = new XMLBeanDeclaration(config, KEY);
+        assertEquals("Wrong class name", getClass().getName(), decl
+                .getBeanClassName());
+    }
+
+    /**
+     * Tests fetching the bean's class name if it is undefined.
+     */
+    @Test
+    public void testGetBeanClassNameUndefined()
+    {
+        decl = new XMLBeanDeclaration(new HierarchicalConfiguration());
+        assertNull(decl.getBeanClassName());
+    }
+
+    /**
+     * Tests fetching the name of the bean factory.
+     */
+    @Test
+    public void testGetBeanFactoryName()
+    {
+        HierarchicalConfiguration config = new HierarchicalConfiguration();
+        config.addProperty(KEY + "[@config-factory]", "myFactory");
+        decl = new XMLBeanDeclaration(config, KEY);
+        assertEquals("Wrong factory name", "myFactory", decl
+                .getBeanFactoryName());
+    }
+
+    /**
+     * Tests fetching the name of the bean factory if it is undefined.
+     */
+    @Test
+    public void testGetBeanFactoryNameUndefined()
+    {
+        decl = new XMLBeanDeclaration(new HierarchicalConfiguration());
+        assertNull(decl.getBeanFactoryName());
+    }
+
+    /**
+     * Tests fetching the parameter for the bean factory.
+     */
+    @Test
+    public void testGetBeanFactoryParameter()
+    {
+        HierarchicalConfiguration config = new HierarchicalConfiguration();
+        config
+                .addProperty(KEY + "[@config-factoryParam]",
+                        "myFactoryParameter");
+        decl = new XMLBeanDeclaration(config, KEY);
+        assertEquals("Wrong factory parameter", "myFactoryParameter", decl
+                .getBeanFactoryParameter());
+    }
+
+    /**
+     * Tests fetching the parameter for the bean factory if it is undefined.
+     */
+    @Test
+    public void testGetBeanFactoryParameterUndefined()
+    {
+        decl = new XMLBeanDeclaration(new HierarchicalConfiguration());
+        assertNull(decl.getBeanFactoryParameter());
+    }
+
+    /**
+     * Tests if the bean's properties are correctly extracted from the
+     * configuration object.
+     */
+    @Test
+    public void testGetBeanProperties()
+    {
+        HierarchicalConfiguration config = new HierarchicalConfiguration();
+        setupBeanDeclaration(config, KEY, TEST_PROPS, TEST_VALUES);
+        decl = new XMLBeanDeclaration(config, KEY);
+        checkProperties(decl, TEST_PROPS, TEST_VALUES);
+    }
+
+    /**
+     * Tests obtaining the bean's properties when reserved attributes are
+     * involved. These should be ignored.
+     */
+    @Test
+    public void testGetBeanPropertiesWithReservedAttributes()
+    {
+        HierarchicalConfiguration config = new HierarchicalConfiguration();
+        setupBeanDeclaration(config, KEY, TEST_PROPS, TEST_VALUES);
+        config.addProperty(KEY + "[@config-testattr]", "yes");
+        config.addProperty(KEY + "[@config-anothertest]", "this, too");
+        decl = new XMLBeanDeclaration(config, KEY);
+        checkProperties(decl, TEST_PROPS, TEST_VALUES);
+    }
+
+    /**
+     * Tests fetching properties if none are defined.
+     */
+    @Test
+    public void testGetBeanPropertiesEmpty()
+    {
+        decl = new XMLBeanDeclaration(new HierarchicalConfiguration());
+        Map<String, Object> props = decl.getBeanProperties();
+        assertTrue("Properties found", props == null || props.isEmpty());
+    }
+
+    /**
+     * Creates a configuration with data for testing nested bean declarations.
+     * @return the initialized test configuration
+     */
+    private HierarchicalConfiguration prepareNestedBeanDeclarations()
+    {
+        HierarchicalConfiguration config = new HierarchicalConfiguration();
+        setupBeanDeclaration(config, KEY, TEST_PROPS, TEST_VALUES);
+        for (int i = 0; i < COMPLEX_PROPS.length; i++)
+        {
+            setupBeanDeclaration(config, KEY + '.' + COMPLEX_PROPS[i],
+                    COMPLEX_ATTRIBUTES[i], COMPLEX_VALUES[i]);
+            config.addProperty(
+                    KEY + '.' + COMPLEX_PROPS[i] + "[@config-class]",
+                    COMPLEX_CLASSES[i]);
+        }
+        return config;
+    }
+
+    /**
+     * Tests fetching nested bean declarations.
+     */
+    @Test
+    public void testGetNestedBeanDeclarations()
+    {
+        HierarchicalConfiguration config = prepareNestedBeanDeclarations();
+        decl = new XMLBeanDeclaration(config, KEY);
+        checkProperties(decl, TEST_PROPS, TEST_VALUES);
+
+        Map<String, Object> nested = decl.getNestedBeanDeclarations();
+        assertEquals("Wrong number of nested declarations",
+                COMPLEX_PROPS.length, nested.size());
+        for (int i = 0; i < COMPLEX_PROPS.length; i++)
+        {
+            XMLBeanDeclaration d = (XMLBeanDeclaration) nested
+                    .get(COMPLEX_PROPS[i]);
+            assertNotNull("No declaration found for " + COMPLEX_PROPS[i], d);
+            checkProperties(d, COMPLEX_ATTRIBUTES[i], COMPLEX_VALUES[i]);
+            assertEquals("Wrong bean class", COMPLEX_CLASSES[i], d
+                    .getBeanClassName());
+        }
+    }
+
+    /**
+     * Tests whether the factory method for creating nested bean declarations
+     * gets called.
+     */
+    @Test
+    public void testGetNestedBeanDeclarationsFactoryMethod()
+    {
+        HierarchicalConfiguration config = prepareNestedBeanDeclarations();
+        decl = new XMLBeanDeclaration(config, KEY)
+        {
+            @Override
+            protected BeanDeclaration createBeanDeclaration(
+                    ConfigurationNode node)
+            {
+                return new XMLBeanDeclarationTestImpl(getConfiguration()
+                        .configurationAt(node.getName()), node);
+            }
+        };
+        Map<String, Object> nested = decl.getNestedBeanDeclarations();
+        for (int i = 0; i < COMPLEX_PROPS.length; i++)
+        {
+            Object d = nested.get(COMPLEX_PROPS[i]);
+            assertTrue("Wrong class for bean declaration: " + d,
+                    d instanceof XMLBeanDeclarationTestImpl);
+        }
+    }
+
+    /**
+     * Tests fetching nested bean declarations if none are defined.
+     */
+    @Test
+    public void testGetNestedBeanDeclarationsEmpty()
+    {
+        HierarchicalConfiguration config = new HierarchicalConfiguration();
+        setupBeanDeclaration(config, KEY, TEST_PROPS, TEST_VALUES);
+        decl = new XMLBeanDeclaration(config, KEY);
+        Map<String, Object> nested = decl.getNestedBeanDeclarations();
+        assertTrue("Found nested declarations", nested == null
+                || nested.isEmpty());
+    }
+
+    /**
+     * Tests whether interpolation of bean properties works.
+     */
+    @Test
+    public void testGetInterpolatedBeanProperties()
+    {
+        HierarchicalConfiguration config = new HierarchicalConfiguration();
+        String[] varValues = new String[TEST_PROPS.length];
+        for(int i = 0; i < TEST_PROPS.length; i++)
+        {
+            varValues[i] = "${" + VARS + TEST_PROPS[i] + "}";
+            config.addProperty(VARS + TEST_PROPS[i], TEST_VALUES[i]);
+        }
+        setupBeanDeclaration(config, KEY, TEST_PROPS, varValues);
+        decl = new XMLBeanDeclaration(config, KEY);
+        checkProperties(decl, TEST_PROPS, TEST_VALUES);
+    }
+
+    /**
+     * Tests constructing a bean declaration from an undefined key. This should
+     * cause an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInitFromUndefinedKey()
+    {
+        HierarchicalConfiguration config = new HierarchicalConfiguration();
+        setupBeanDeclaration(config, KEY, TEST_PROPS, TEST_VALUES);
+        decl = new XMLBeanDeclaration(config, "undefined_key");
+    }
+
+    /**
+     * Tests constructing a bean declaration from a key, which is undefined when
+     * the optional flag is set. In this case an empty declaration should be
+     * created, which can be used for creating beans as long as a default class
+     * is provided.
+     */
+    @Test
+    public void testInitFromUndefinedKeyOptional()
+    {
+        HierarchicalConfiguration config = new HierarchicalConfiguration();
+        setupBeanDeclaration(config, KEY, TEST_PROPS, TEST_VALUES);
+        decl = new XMLBeanDeclaration(config, "undefined_key", true);
+        assertNull("Found a bean class", decl.getBeanClassName());
+    }
+
+    /**
+     * Tests constructing a bean declaration from a key with multiple values.
+     * This should cause an exception because keys must be unique.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInitFromMultiValueKey()
+    {
+        HierarchicalConfiguration config = new HierarchicalConfiguration();
+        config.addProperty(KEY, "myFirstKey");
+        config.addProperty(KEY, "mySecondKey");
+        decl = new XMLBeanDeclaration(config, KEY);
+    }
+
+    /**
+     * Initializes a configuration object with a bean declaration. Under the
+     * specified key the given properties will be added.
+     *
+     * @param config the configuration to initialize
+     * @param key the key of the bean declaration
+     * @param names an array with the names of the properties
+     * @param values an array with the corresponding values
+     */
+    private void setupBeanDeclaration(HierarchicalConfiguration config,
+            String key, String[] names, String[] values)
+    {
+        for (int i = 0; i < names.length; i++)
+        {
+            config.addProperty(key + "[@" + names[i] + "]", values[i]);
+        }
+    }
+
+    /**
+     * Checks the properties returned by a bean declaration.
+     *
+     * @param beanDecl the bean declaration
+     * @param names an array with the expected property names
+     * @param values an array with the expected property values
+     */
+    private void checkProperties(BeanDeclaration beanDecl, String[] names,
+            String[] values)
+    {
+        Map<String, Object> props = beanDecl.getBeanProperties();
+        assertEquals("Wrong number of properties", names.length, props.size());
+        for (int i = 0; i < names.length; i++)
+        {
+            assertTrue("Property " + names[i] + " not contained", props
+                    .containsKey(names[i]));
+            assertEquals("Wrong value for property " + names[i], values[i],
+                    props.get(names[i]));
+        }
+    }
+
+    /**
+     * A helper class used for testing the createBeanDeclaration() factory
+     * method.
+     */
+    private static class XMLBeanDeclarationTestImpl extends XMLBeanDeclaration
+    {
+        public XMLBeanDeclarationTestImpl(SubnodeConfiguration config,
+                ConfigurationNode node)
+        {
+            super(config, node);
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/event/AbstractTestConfigurationEvents.java b/src/test/java/org/apache/commons/configuration/event/AbstractTestConfigurationEvents.java
new file mode 100644
index 0000000..9c49f39
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/event/AbstractTestConfigurationEvents.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.event;
+
+
+import org.apache.commons.configuration.AbstractConfiguration;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Base class for testing events generated by configuration classes derived from
+ * AbstractConfiguration. This class implements a couple of tests related to
+ * event generation. Concrete sub classes only have to implement the
+ * {@code createConfiguration()} method for creating an instance of a
+ * specific configuration class. Because tests for detail events depend on a
+ * concrete implementation an exact sequence of events cannot be checked.
+ * Instead the corresponding test methods check whether the enclosing events
+ * (not the detail events) are of the expected type.
+ *
+ * @version $Id: AbstractTestConfigurationEvents.java 1225648 2011-12-29 20:55:07Z oheger $
+ */
+public abstract class AbstractTestConfigurationEvents
+{
+    /** Constant for a test property name. */
+    static final String TEST_PROPNAME = "event.test";
+
+    /** Constant for a test property value. */
+    static final String TEST_PROPVALUE = "a value";
+
+    /** Constant for an existing property. */
+    static final String EXIST_PROPERTY = "event.property";
+
+    /** The configuration to be tested. */
+    protected AbstractConfiguration config;
+
+    /** A test event listener. */
+    protected ConfigurationListenerTestImpl l;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        config = createConfiguration();
+        config.addProperty(EXIST_PROPERTY, "existing value");
+        l = new ConfigurationListenerTestImpl(config);
+        config.addConfigurationListener(l);
+    }
+
+    /**
+     * Creates the configuration instance to be tested.
+     *
+     * @return the configuration instance under test
+     */
+    protected abstract AbstractConfiguration createConfiguration();
+
+    /**
+     * Tests events generated by addProperty().
+     */
+    @Test
+    public void testAddPropertyEvent()
+    {
+        config.addProperty(TEST_PROPNAME, TEST_PROPVALUE);
+        l.checkEvent(AbstractConfiguration.EVENT_ADD_PROPERTY, TEST_PROPNAME,
+                TEST_PROPVALUE, true);
+        l.checkEvent(AbstractConfiguration.EVENT_ADD_PROPERTY, TEST_PROPNAME,
+                TEST_PROPVALUE, false);
+        l.done();
+    }
+
+    /**
+     * Tests events generated by addProperty() when detail events are enabled.
+     */
+    @Test
+    public void testAddPropertyEventWithDetails()
+    {
+        config.setDetailEvents(true);
+        config.addProperty(TEST_PROPNAME, TEST_PROPVALUE);
+        l.checkEventCount(2);
+        l.checkEvent(AbstractConfiguration.EVENT_ADD_PROPERTY, TEST_PROPNAME,
+                TEST_PROPVALUE, true);
+        l.skipToLast(AbstractConfiguration.EVENT_ADD_PROPERTY);
+        l.checkEvent(AbstractConfiguration.EVENT_ADD_PROPERTY, TEST_PROPNAME,
+                TEST_PROPVALUE, false);
+        l.done();
+    }
+
+    /**
+     * Tests events generated by clearProperty().
+     */
+    @Test
+    public void testClearPropertyEvent()
+    {
+        config.clearProperty(EXIST_PROPERTY);
+        l.checkEvent(AbstractConfiguration.EVENT_CLEAR_PROPERTY,
+                EXIST_PROPERTY, null, true);
+        l.checkEvent(AbstractConfiguration.EVENT_CLEAR_PROPERTY,
+                EXIST_PROPERTY, null, false);
+        l.done();
+    }
+
+    /**
+     * Tests events generated by clearProperty() when detail events are enabled.
+     */
+    @Test
+    public void testClearPropertyEventWithDetails()
+    {
+        config.setDetailEvents(true);
+        config.clearProperty(EXIST_PROPERTY);
+        l.checkEventCount(2);
+        l.checkEvent(AbstractConfiguration.EVENT_CLEAR_PROPERTY,
+                EXIST_PROPERTY, null, true);
+        l.skipToLast(AbstractConfiguration.EVENT_CLEAR_PROPERTY);
+        l.checkEvent(AbstractConfiguration.EVENT_CLEAR_PROPERTY,
+                EXIST_PROPERTY, null, false);
+        l.done();
+    }
+
+    /**
+     * Tests events generated by setProperty().
+     */
+    @Test
+    public void testSetPropertyEvent()
+    {
+        config.setProperty(EXIST_PROPERTY, TEST_PROPVALUE);
+        l.checkEvent(AbstractConfiguration.EVENT_SET_PROPERTY, EXIST_PROPERTY,
+                TEST_PROPVALUE, true);
+        l.checkEvent(AbstractConfiguration.EVENT_SET_PROPERTY, EXIST_PROPERTY,
+                TEST_PROPVALUE, false);
+        l.done();
+    }
+
+    /**
+     * Tests events generated by setProperty() when detail events are enabled.
+     */
+    @Test
+    public void testSetPropertyEventWithDetails()
+    {
+        config.setDetailEvents(true);
+        config.setProperty(EXIST_PROPERTY, TEST_PROPVALUE);
+        l.checkEventCount(2);
+        l.checkEvent(AbstractConfiguration.EVENT_SET_PROPERTY, EXIST_PROPERTY,
+                TEST_PROPVALUE, true);
+        l.skipToLast(AbstractConfiguration.EVENT_SET_PROPERTY);
+        l.checkEvent(AbstractConfiguration.EVENT_SET_PROPERTY, EXIST_PROPERTY,
+                TEST_PROPVALUE, false);
+        l.done();
+    }
+
+    /**
+     * Tests the events generated by the clear() method.
+     */
+    @Test
+    public void testClearEvent()
+    {
+        config.clear();
+        l.checkEvent(AbstractConfiguration.EVENT_CLEAR, null, null, true);
+        l.checkEvent(AbstractConfiguration.EVENT_CLEAR, null, null, false);
+        l.done();
+    }
+
+    /**
+     * Tests the events generated by the clear method when detail events are
+     * enabled.
+     */
+    @Test
+    public void testClearEventWithDetails()
+    {
+        config.setDetailEvents(true);
+        config.clear();
+        l.checkEventCount(2);
+        l.checkEvent(AbstractConfiguration.EVENT_CLEAR, null, null, true);
+        l.skipToLast(AbstractConfiguration.EVENT_CLEAR);
+        l.checkEvent(AbstractConfiguration.EVENT_CLEAR, null, null, false);
+        l.done();
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/event/AbstractTestFileConfigurationEvents.java b/src/test/java/org/apache/commons/configuration/event/AbstractTestFileConfigurationEvents.java
new file mode 100644
index 0000000..0d1c77b
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/event/AbstractTestFileConfigurationEvents.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.event;
+
+import java.io.IOException;
+import java.net.URL;
+
+import org.apache.commons.configuration.AbstractFileConfiguration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.FileConfiguration;
+import org.apache.commons.configuration.reloading.ReloadingStrategy;
+import org.junit.Test;
+
+/**
+ * A base test class that can be used for testing file-based configurations.
+ * This class tests reload events, too.
+ *
+ * @version $Id: AbstractTestFileConfigurationEvents.java 1225648 2011-12-29 20:55:07Z oheger $
+ */
+public abstract class AbstractTestFileConfigurationEvents extends
+        AbstractTestConfigurationEvents
+{
+    /**
+     * Initializes the file configuration for the tests.
+     *
+     * @throws ConfigurationException if an error occurs
+     */
+    protected void setUpFileConfiguration() throws ConfigurationException,
+            IOException
+    {
+        FileConfiguration fc = (FileConfiguration) config;
+        fc.setReloadingStrategy(new AlwaysReloadingStrategy());
+        fc.setURL(getSourceURL());
+
+        // deregister event listener before load because load will cause
+        // other events being generated
+        config.removeConfigurationListener(l);
+        fc.load();
+        config.addConfigurationListener(l);
+    }
+
+    /**
+     * Returns the URL of the file to be loaded. Must be implemented in concrete
+     * test classes.
+     *
+     * @return the URL of the file-based configuration
+     * @throws IOException if an error occurs
+     */
+    protected abstract URL getSourceURL() throws IOException;
+
+    /**
+     * Tests events generated by the reload() method.
+     */
+    @Test
+    public void testReloadEvent() throws ConfigurationException, IOException
+    {
+        setUpFileConfiguration();
+        config.isEmpty(); // This should cause a reload
+        l.checkEvent(AbstractFileConfiguration.EVENT_RELOAD, null,
+                getSourceURL(), true);
+        l.checkEvent(AbstractFileConfiguration.EVENT_RELOAD, null,
+                getSourceURL(), false);
+        l.done();
+    }
+
+    /**
+     * Tests events generated by the reload() method when detail events are
+     * enabled.
+     */
+    @Test
+    public void testReloadEventWithDetails() throws ConfigurationException,
+            IOException
+    {
+        setUpFileConfiguration();
+        config.setDetailEvents(true);
+        config.isEmpty(); // This should cause a reload
+        l.checkEventCount(2);
+        l.checkEvent(AbstractFileConfiguration.EVENT_RELOAD, null,
+                getSourceURL(), true);
+        l.skipToLast(AbstractFileConfiguration.EVENT_RELOAD);
+        l.checkEvent(AbstractFileConfiguration.EVENT_RELOAD, null,
+                getSourceURL(), false);
+        l.done();
+    }
+
+    /**
+     * Tests accessing a property during a reload event to ensure that no
+     * infinite loops are possible.
+     */
+    @Test
+    public void testAccessPropertiesOnReload() throws ConfigurationException,
+            IOException
+    {
+        setUpFileConfiguration();
+        config.addConfigurationListener(new ConfigurationListener()
+        {
+            public void configurationChanged(ConfigurationEvent event)
+            {
+                config.getString("test");
+            }
+        });
+        config.isEmpty();
+        l.checkEvent(AbstractFileConfiguration.EVENT_RELOAD, null,
+                getSourceURL(), true);
+        l.checkEvent(AbstractFileConfiguration.EVENT_RELOAD, null,
+                getSourceURL(), false);
+        l.done();
+    }
+
+    /**
+     * A dummy implementation of the ReloadingStrategy interface. This
+     * implementation will always indicate that a reload should be performed. So
+     * it can be used for testing reloading events.
+     */
+    static class AlwaysReloadingStrategy implements ReloadingStrategy
+    {
+        public void setConfiguration(FileConfiguration configuration)
+        {
+        }
+
+        public void init()
+        {
+        }
+
+        public boolean reloadingRequired()
+        {
+            return true;
+        }
+
+        public void reloadingPerformed()
+        {
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/event/ConfigurationListenerTestImpl.java b/src/test/java/org/apache/commons/configuration/event/ConfigurationListenerTestImpl.java
new file mode 100644
index 0000000..9da6e84
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/event/ConfigurationListenerTestImpl.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.event;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A test event listener class that can be used for testing whether
+ * configurations generated correct events.
+ *
+ * @author <a
+ *         href="http://commons.apache.org/configuration/team-list.html">Commons
+ *         Configuration team</a>
+ * @version $Id: ConfigurationListenerTestImpl.java 1225648 2011-12-29 20:55:07Z oheger $
+ */
+public class ConfigurationListenerTestImpl implements ConfigurationListener
+{
+    /** The expected event source. */
+    private final Object expectedSource;
+
+    /** Stores the received events. */
+    private final List<ConfigurationEvent> events;
+
+    /**
+     * Creates a new instance of {@code ConfigurationListenerTestImpl} and sets
+     * the expected event source.
+     *
+     * @param source the event source (<b>null</b> if the source need not to be
+     *        checked)
+     */
+    public ConfigurationListenerTestImpl(Object source)
+    {
+        expectedSource = source;
+        events = new LinkedList<ConfigurationEvent>();
+    }
+
+    public void configurationChanged(ConfigurationEvent event)
+    {
+        events.add(event);
+    }
+
+    /**
+     * Checks if at least {@code minEvents} events have been received.
+     *
+     * @param minEvents the minimum number of expected events
+     */
+    public void checkEventCount(int minEvents)
+    {
+        assertTrue("Too view events received", events.size() >= minEvents);
+    }
+
+    /**
+     * Checks an expected event.
+     *
+     * @param type the event type
+     * @param propName the expected property name
+     * @param propValue the expected property value
+     * @param before the expected before flag
+     */
+    public void checkEvent(int type, String propName, Object propValue,
+            boolean before)
+    {
+        ConfigurationEvent e = nextEvent(type);
+        assertEquals("Wrong property name", propName, e.getPropertyName());
+        assertEquals("Wrong property value", propValue, e.getPropertyValue());
+        assertEquals("Wrong before flag", before, e.isBeforeUpdate());
+    }
+
+    /**
+     * Returns the next received event and checks for the expected type. This
+     * method can be used instead of {@code checkEvent()} for comparing
+     * complex event values.
+     *
+     * @param expectedType the expected type of the event
+     * @return the event object
+     */
+    public ConfigurationEvent nextEvent(int expectedType)
+    {
+        assertFalse("Too few events received", events.isEmpty());
+        ConfigurationEvent e = events.remove(0);
+        if (expectedSource != null)
+        {
+            assertEquals("Wrong event source", expectedSource, e.getSource());
+        }
+        assertEquals("Wrong event type", expectedType, e.getType());
+        return e;
+    }
+
+    /**
+     * Skips to the last received event and checks that no events of the given
+     * type have been received. This method is used by checks for detail events
+     * to ignore the detail events.
+     *
+     * @param type the event type
+     */
+    public void skipToLast(int type)
+    {
+        while (events.size() > 1)
+        {
+            ConfigurationEvent e = events.remove(0);
+            assertTrue("Found end event in details", type != e.getType());
+        }
+    }
+
+    /**
+     * Checks if all events has been processed.
+     */
+    public void done()
+    {
+        assertTrue("Too many events received", events.isEmpty());
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/event/TestDatabaseConfigurationEvents.java b/src/test/java/org/apache/commons/configuration/event/TestDatabaseConfigurationEvents.java
new file mode 100644
index 0000000..5f8053d
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/event/TestDatabaseConfigurationEvents.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.event;
+
+import org.apache.commons.configuration.AbstractConfiguration;
+import org.apache.commons.configuration.DatabaseConfigurationTestHelper;
+import org.junit.After;
+import org.junit.Before;
+
+/**
+ * A test class for the events generated by DatabaseConfiguration.
+ *
+ * @version $Id: TestDatabaseConfigurationEvents.java 1225648 2011-12-29 20:55:07Z oheger $
+ */
+public class TestDatabaseConfigurationEvents extends
+        AbstractTestConfigurationEvents
+{
+    /** The test helper. */
+    private DatabaseConfigurationTestHelper helper;
+
+    @Override
+    @Before
+    public void setUp() throws Exception
+    {
+        helper = new DatabaseConfigurationTestHelper();
+        helper.setUp();
+
+        super.setUp();
+    }
+
+    @After
+    public void tearDown() throws Exception
+    {
+        helper.tearDown();
+    }
+
+    @Override
+    protected AbstractConfiguration createConfiguration()
+    {
+        return helper.setUpConfig();
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/event/TestEventSource.java b/src/test/java/org/apache/commons/configuration/event/TestEventSource.java
new file mode 100644
index 0000000..622fe20
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/event/TestEventSource.java
@@ -0,0 +1,394 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.event;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collection;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for EventSource.
+ *
+ * @version $Id: TestEventSource.java 1225652 2011-12-29 21:00:57Z oheger $
+ */
+public class TestEventSource
+{
+    /** Constant for the event type used for testing. */
+    static final int TEST_TYPE = 42;
+
+    /** Constant for the event property name. */
+    static final String TEST_PROPNAME = "test.property.name";
+
+    /** Constant for the event property value. */
+    static final Object TEST_PROPVALUE = "a test property value";
+
+    /** The object under test. */
+    CountingEventSource source;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        source = new CountingEventSource();
+    }
+
+    /**
+     * Tests a newly created source object.
+     */
+    @Test
+    public void testInit()
+    {
+        assertTrue("Listeners list is not empty", source
+                .getConfigurationListeners().isEmpty());
+        assertFalse("Removing listener", source
+                .removeConfigurationListener(new TestListener()));
+        assertFalse("Detail events are enabled", source.isDetailEvents());
+        assertTrue("Error listeners list is not empty", source
+                .getErrorListeners().isEmpty());
+    }
+
+    /**
+     * Tests registering a new listener.
+     */
+    @Test
+    public void testAddConfigurationListener()
+    {
+        TestListener l = new TestListener();
+        source.addConfigurationListener(l);
+        Collection<ConfigurationListener> listeners = source.getConfigurationListeners();
+        assertEquals("Wrong number of listeners", 1, listeners.size());
+        assertTrue("Listener not in list", listeners.contains(l));
+    }
+
+    /**
+     * Tests adding an undefined configuration listener. This should cause an
+     * exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testAddNullConfigurationListener()
+    {
+        source.addConfigurationListener(null);
+    }
+
+    /**
+     * Tests removing a listener.
+     */
+    @Test
+    public void testRemoveConfigurationListener()
+    {
+        TestListener l = new TestListener();
+        assertFalse("Listener can be removed?", source
+                .removeConfigurationListener(l));
+        source.addConfigurationListener(l);
+        source.addConfigurationListener(new TestListener());
+        assertFalse("Unknown listener can be removed", source
+                .removeConfigurationListener(new TestListener()));
+        assertTrue("Could not remove listener", source
+                .removeConfigurationListener(l));
+        assertFalse("Listener still in list", source
+                .getConfigurationListeners().contains(l));
+    }
+
+    /**
+     * Tests if a null listener can be removed. This should be a no-op.
+     */
+    @Test
+    public void testRemoveNullConfigurationListener()
+    {
+        source.addConfigurationListener(new TestListener());
+        assertFalse("Null listener can be removed", source
+                .removeConfigurationListener(null));
+        assertEquals("Listener list was modified", 1, source
+                .getConfigurationListeners().size());
+    }
+
+    /**
+     * Tests whether the listeners list is read only.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testGetConfigurationListenersUpdate()
+    {
+        source.addConfigurationListener(new TestListener());
+        Collection<ConfigurationListener> list = source.getConfigurationListeners();
+        list.clear();
+    }
+
+    /**
+     * Tests that the collection returned by getConfigurationListeners() is
+     * really a snapshot. A later added listener must not be visible.
+     */
+    @Test
+    public void testGetConfigurationListenersAddNew()
+    {
+        Collection<ConfigurationListener> list = source.getConfigurationListeners();
+        source.addConfigurationListener(new TestListener());
+        assertTrue("Listener snapshot not empty", list.isEmpty());
+    }
+
+    /**
+     * Tests enabling and disabling the detail events flag.
+     */
+    @Test
+    public void testSetDetailEvents()
+    {
+        source.setDetailEvents(true);
+        assertTrue("Detail events are disabled", source.isDetailEvents());
+        source.setDetailEvents(true);
+        source.setDetailEvents(false);
+        assertTrue("Detail events are disabled again", source.isDetailEvents());
+        source.setDetailEvents(false);
+        assertFalse("Detail events are still enabled", source.isDetailEvents());
+    }
+
+    /**
+     * Tests delivering an event to a listener.
+     */
+    @Test
+    public void testFireEvent()
+    {
+        TestListener l = new TestListener();
+        source.addConfigurationListener(l);
+        source.fireEvent(TEST_TYPE, TEST_PROPNAME, TEST_PROPVALUE, true);
+        assertEquals("Not 1 event created", 1, source.eventCount);
+        assertEquals("Listener not called once", 1, l.numberOfCalls);
+        assertEquals("Wrong event type", TEST_TYPE, l.lastEvent.getType());
+        assertEquals("Wrong property name", TEST_PROPNAME, l.lastEvent
+                .getPropertyName());
+        assertEquals("Wrong property value", TEST_PROPVALUE, l.lastEvent
+                .getPropertyValue());
+        assertTrue("Wrong before event flag", l.lastEvent.isBeforeUpdate());
+    }
+
+    /**
+     * Tests firing an event if there are no listeners.
+     */
+    @Test
+    public void testFireEventNoListeners()
+    {
+        source.fireEvent(TEST_TYPE, TEST_PROPNAME, TEST_PROPVALUE, false);
+        assertEquals("An event object was created", 0, source.eventCount);
+    }
+
+    /**
+     * Tests generating a detail event if detail events are not allowed.
+     */
+    @Test
+    public void testFireEventNoDetails()
+    {
+        TestListener l = new TestListener();
+        source.addConfigurationListener(l);
+        source.setDetailEvents(false);
+        source.fireEvent(TEST_TYPE, TEST_PROPNAME, TEST_PROPVALUE, false);
+        assertEquals("Event object was created", 0, source.eventCount);
+        assertEquals("Listener was called", 0, l.numberOfCalls);
+    }
+
+    /**
+     * Tests whether an event listener can deregister itself in reaction of a
+     * delivered event.
+     */
+    @Test
+    public void testRemoveListenerInFireEvent()
+    {
+        ConfigurationListener lstRemove = new ConfigurationListener()
+        {
+            public void configurationChanged(ConfigurationEvent event)
+            {
+                source.removeConfigurationListener(this);
+            }
+        };
+
+        source.addConfigurationListener(lstRemove);
+        TestListener l = new TestListener();
+        source.addConfigurationListener(l);
+        source.fireEvent(TEST_TYPE, TEST_PROPNAME, TEST_PROPVALUE, false);
+        assertEquals("Listener was not called", 1, l.numberOfCalls);
+        assertEquals("Listener was not removed", 1, source
+                .getConfigurationListeners().size());
+    }
+
+    /**
+     * Tests registering a new error listener.
+     */
+    @Test
+    public void testAddErrorListener()
+    {
+        TestListener l = new TestListener();
+        source.addErrorListener(l);
+        Collection<ConfigurationErrorListener> listeners = source.getErrorListeners();
+        assertEquals("Wrong number of listeners", 1, listeners.size());
+        assertTrue("Listener not in list", listeners.contains(l));
+    }
+
+    /**
+     * Tests adding an undefined error listener. This should cause an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testAddNullErrorListener()
+    {
+        source.addErrorListener(null);
+    }
+
+    /**
+     * Tests removing an error listener.
+     */
+    @Test
+    public void testRemoveErrorListener()
+    {
+        TestListener l = new TestListener();
+        assertFalse("Listener can be removed?", source.removeErrorListener(l));
+        source.addErrorListener(l);
+        source.addErrorListener(new TestListener());
+        assertFalse("Unknown listener can be removed", source
+                .removeErrorListener(new TestListener()));
+        assertTrue("Could not remove listener", source.removeErrorListener(l));
+        assertFalse("Listener still in list", source.getErrorListeners()
+                .contains(l));
+    }
+
+    /**
+     * Tests if a null error listener can be removed. This should be a no-op.
+     */
+    @Test
+    public void testRemoveNullErrorListener()
+    {
+        source.addErrorListener(new TestListener());
+        assertFalse("Null listener can be removed", source
+                .removeErrorListener(null));
+        assertEquals("Listener list was modified", 1, source
+                .getErrorListeners().size());
+    }
+
+    /**
+     * Tests whether the listeners list is read only.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testGetErrorListenersUpdate()
+    {
+        source.addErrorListener(new TestListener());
+        Collection<ConfigurationErrorListener> list = source.getErrorListeners();
+        list.clear();
+    }
+
+    /**
+     * Tests delivering an error event to a listener.
+     */
+    @Test
+    public void testFireError()
+    {
+        TestListener l = new TestListener();
+        source.addErrorListener(l);
+        Exception testException = new Exception("A test");
+        source.fireError(TEST_TYPE, TEST_PROPNAME, TEST_PROPVALUE,
+                testException);
+        assertEquals("Not 1 event created", 1, source.errorCount);
+        assertEquals("Error listener not called once", 1, l.numberOfErrors);
+        assertEquals("Normal event was generated", 0, l.numberOfCalls);
+        assertEquals("Wrong event type", TEST_TYPE, l.lastEvent.getType());
+        assertEquals("Wrong property name", TEST_PROPNAME, l.lastEvent
+                .getPropertyName());
+        assertEquals("Wrong property value", TEST_PROPVALUE, l.lastEvent
+                .getPropertyValue());
+        assertEquals("Wrong Throwable object", testException,
+                ((ConfigurationErrorEvent) l.lastEvent).getCause());
+    }
+
+    /**
+     * Tests firing an error event if there are no error listeners.
+     */
+    @Test
+    public void testFireErrorNoListeners()
+    {
+        source.fireError(TEST_TYPE, TEST_PROPNAME, TEST_PROPVALUE,
+                new Exception());
+        assertEquals("An error event object was created", 0, source.errorCount);
+    }
+
+    /**
+     * Tests cloning an event source object. The registered listeners should not
+     * be registered at the clone.
+     */
+    @Test
+    public void testClone() throws CloneNotSupportedException
+    {
+        source.addConfigurationListener(new TestListener());
+        source.addErrorListener(new TestListener());
+        EventSource copy = (EventSource) source.clone();
+        assertTrue("Configuration listeners registered for clone", copy
+                .getConfigurationListeners().isEmpty());
+        assertTrue("Error listeners registered for clone", copy
+                .getErrorListeners().isEmpty());
+    }
+
+    /**
+     * A test event listener implementation.
+     */
+    static class TestListener implements ConfigurationListener,
+            ConfigurationErrorListener
+    {
+        ConfigurationEvent lastEvent;
+
+        int numberOfCalls;
+
+        int numberOfErrors;
+
+        public void configurationChanged(ConfigurationEvent event)
+        {
+            lastEvent = event;
+            numberOfCalls++;
+        }
+
+        public void configurationError(ConfigurationErrorEvent event)
+        {
+            lastEvent = event;
+            numberOfErrors++;
+        }
+    }
+
+    /**
+     * A specialized event source implementation that counts the number of
+     * created event objects. It is used to test whether the
+     * {@code fireEvent()} methods only creates event objects if
+     * necessary. It also allows testing the clone() operation.
+     */
+    static class CountingEventSource extends EventSource implements Cloneable
+    {
+        int eventCount;
+
+        int errorCount;
+
+        @Override
+        protected ConfigurationEvent createEvent(int type, String propName,
+                Object propValue, boolean before)
+        {
+            eventCount++;
+            return super.createEvent(type, propName, propValue, before);
+        }
+
+        @Override
+        protected ConfigurationErrorEvent createErrorEvent(int type,
+                String propName, Object value, Throwable ex)
+        {
+            errorCount++;
+            return super.createErrorEvent(type, propName, value, ex);
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/event/TestHierarchicalConfigurationEvents.java b/src/test/java/org/apache/commons/configuration/event/TestHierarchicalConfigurationEvents.java
new file mode 100644
index 0000000..ef3bbf2
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/event/TestHierarchicalConfigurationEvents.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.event;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.commons.configuration.AbstractConfiguration;
+import org.apache.commons.configuration.HierarchicalConfiguration;
+import org.apache.commons.configuration.SubnodeConfiguration;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.DefaultConfigurationNode;
+import org.junit.Test;
+
+/**
+ * Test class for the events generated by hierarchical configurations.
+ *
+ * @version $Id: TestHierarchicalConfigurationEvents.java 1225648 2011-12-29 20:55:07Z oheger $
+ */
+public class TestHierarchicalConfigurationEvents extends
+        AbstractTestConfigurationEvents
+{
+    @Override
+    protected AbstractConfiguration createConfiguration()
+    {
+        return new HierarchicalConfiguration();
+    }
+
+    /**
+     * Tests events generated by the clearTree() method.
+     */
+    @Test
+    public void testClearTreeEvent()
+    {
+        HierarchicalConfiguration hc = (HierarchicalConfiguration) config;
+        String key = EXIST_PROPERTY.substring(0, EXIST_PROPERTY.indexOf('.'));
+        Collection<ConfigurationNode> nodes = hc.getExpressionEngine()
+                .query(hc.getRootNode(), key);
+        hc.clearTree(key);
+        l.checkEvent(HierarchicalConfiguration.EVENT_CLEAR_TREE, key, null,
+                true);
+        l.checkEvent(HierarchicalConfiguration.EVENT_CLEAR_TREE, key, nodes,
+                false);
+        l.done();
+    }
+
+    /**
+     * Tests events generated by the addNodes() method.
+     */
+    @Test
+    public void testAddNodesEvent()
+    {
+        HierarchicalConfiguration hc = (HierarchicalConfiguration) config;
+        Collection<ConfigurationNode> nodes = new ArrayList<ConfigurationNode>(1);
+        nodes.add(new DefaultConfigurationNode("a_key", TEST_PROPVALUE));
+        hc.addNodes(TEST_PROPNAME, nodes);
+        l.checkEvent(HierarchicalConfiguration.EVENT_ADD_NODES, TEST_PROPNAME,
+                nodes, true);
+        l.checkEvent(HierarchicalConfiguration.EVENT_ADD_NODES, TEST_PROPNAME,
+                nodes, false);
+        l.done();
+    }
+
+    /**
+     * Tests events generated by addNodes() when the list of nodes is empty. In
+     * this case no events should be generated.
+     */
+    @Test
+    public void testAddNodesEmptyEvent()
+    {
+        ((HierarchicalConfiguration) config).addNodes(TEST_PROPNAME,
+                new ArrayList<ConfigurationNode>());
+        l.done();
+    }
+
+    /**
+     * Tests whether manipulations of a subnode configuration trigger correct
+     * events.
+     */
+    @Test
+    public void testSubnodeChangedEvent()
+    {
+        SubnodeConfiguration sub = ((HierarchicalConfiguration) config)
+                .configurationAt(EXIST_PROPERTY);
+        sub.addProperty("newProp", "newValue");
+        checkSubnodeEvent(l
+                .nextEvent(HierarchicalConfiguration.EVENT_SUBNODE_CHANGED),
+                true);
+        checkSubnodeEvent(l
+                .nextEvent(HierarchicalConfiguration.EVENT_SUBNODE_CHANGED),
+                false);
+        l.done();
+    }
+
+    /**
+     * Tests whether a received event contains a correct subnode event.
+     *
+     * @param event the event object
+     * @param before the expected before flag
+     */
+    private void checkSubnodeEvent(ConfigurationEvent event, boolean before)
+    {
+        assertEquals("Wrong before flag of nesting event", before, event
+                .isBeforeUpdate());
+        assertTrue("No subnode event found in value",
+                event.getPropertyValue() instanceof ConfigurationEvent);
+        ConfigurationEvent evSub = (ConfigurationEvent) event
+                .getPropertyValue();
+        assertEquals("Wrong event type",
+                HierarchicalConfiguration.EVENT_ADD_PROPERTY, evSub.getType());
+        assertEquals("Wrong property name", "newProp", evSub.getPropertyName());
+        assertEquals("Wrong property value", "newValue", evSub
+                .getPropertyValue());
+        assertEquals("Wrong before flag", before, evSub.isBeforeUpdate());
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/event/TestMapConfigurationEvents.java b/src/test/java/org/apache/commons/configuration/event/TestMapConfigurationEvents.java
new file mode 100644
index 0000000..08ab7ee
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/event/TestMapConfigurationEvents.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.event;
+
+import java.util.HashMap;
+
+import org.apache.commons.configuration.AbstractConfiguration;
+import org.apache.commons.configuration.MapConfiguration;
+
+/**
+ * Test class for the events generated by MapConfiguration.
+ *
+ * @version $Id: TestMapConfigurationEvents.java 1225648 2011-12-29 20:55:07Z oheger $
+ */
+public class TestMapConfigurationEvents extends AbstractTestConfigurationEvents
+{
+    @Override
+    protected AbstractConfiguration createConfiguration()
+    {
+        return new MapConfiguration(new HashMap<String, Object>());
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/event/TestPropertiesConfigurationEvents.java b/src/test/java/org/apache/commons/configuration/event/TestPropertiesConfigurationEvents.java
new file mode 100644
index 0000000..74de18c
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/event/TestPropertiesConfigurationEvents.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.event;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+
+import org.apache.commons.configuration.AbstractConfiguration;
+import org.apache.commons.configuration.ConfigurationAssert;
+import org.apache.commons.configuration.PropertiesConfiguration;
+
+/**
+ * Test class for the events generated by properties configurations. This class
+ * also tests reload events.
+ *
+ * @version $Id: TestPropertiesConfigurationEvents.java 1225648 2011-12-29 20:55:07Z oheger $
+ */
+public class TestPropertiesConfigurationEvents extends
+        AbstractTestFileConfigurationEvents
+{
+    /** The file to be loaded.*/
+    static final File TEST_FILE = ConfigurationAssert.getTestFile("test.properties");
+
+    @Override
+    protected AbstractConfiguration createConfiguration()
+    {
+        return new PropertiesConfiguration();
+    }
+
+    @Override
+    protected URL getSourceURL() throws IOException
+    {
+        return TEST_FILE.toURI().toURL();
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/event/TestSubsetConfigurationEvents.java b/src/test/java/org/apache/commons/configuration/event/TestSubsetConfigurationEvents.java
new file mode 100644
index 0000000..758c223
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/event/TestSubsetConfigurationEvents.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.event;
+
+import java.util.HashMap;
+
+import org.apache.commons.configuration.AbstractConfiguration;
+import org.apache.commons.configuration.MapConfiguration;
+import org.apache.commons.configuration.SubsetConfiguration;
+
+/**
+ * Test class for the events generated by SubsetConfiguration.
+ *
+ * @version $Id: TestSubsetConfigurationEvents.java 1225648 2011-12-29 20:55:07Z oheger $
+ */
+public class TestSubsetConfigurationEvents extends AbstractTestConfigurationEvents
+{
+    @Override
+    protected AbstractConfiguration createConfiguration()
+    {
+        return (SubsetConfiguration)new MapConfiguration(new HashMap<String, Object>()).subset("test");
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/event/TestXMLConfigurationEvents.java b/src/test/java/org/apache/commons/configuration/event/TestXMLConfigurationEvents.java
new file mode 100644
index 0000000..b02f071
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/event/TestXMLConfigurationEvents.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.event;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+
+import org.apache.commons.configuration.AbstractConfiguration;
+import org.apache.commons.configuration.ConfigurationAssert;
+import org.apache.commons.configuration.XMLConfiguration;
+
+/**
+ * Test class for events generated by XMLConfiguration.
+ *
+ * @version $Id: TestXMLConfigurationEvents.java 1225648 2011-12-29 20:55:07Z oheger $
+ */
+public class TestXMLConfigurationEvents extends
+        AbstractTestFileConfigurationEvents
+{
+    static final File TEST_FILE = ConfigurationAssert.getTestFile("test.xml");
+
+    @Override
+    protected URL getSourceURL() throws IOException
+    {
+        return TEST_FILE.toURI().toURL();
+    }
+
+    @Override
+    protected AbstractConfiguration createConfiguration()
+    {
+        return new XMLConfiguration();
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/interpol/TestConfigurationInterpolator.java b/src/test/java/org/apache/commons/configuration/interpol/TestConfigurationInterpolator.java
new file mode 100644
index 0000000..cb0ab2f
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/interpol/TestConfigurationInterpolator.java
@@ -0,0 +1,351 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.interpol;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.commons.lang.text.StrLookup;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for ConfigurationInterpolator.
+ *
+ * @version $Id: TestConfigurationInterpolator.java 1225653 2011-12-29 21:06:26Z oheger $
+ */
+public class TestConfigurationInterpolator
+{
+    /** Constant for a test variable prefix. */
+    private static final String TEST_PREFIX = "prefix";
+
+    /** Constant for a test variable name. */
+    private static final String TEST_NAME = "varname";
+
+    /** Constant for the value of the test variable. */
+    private static final String TEST_VALUE = "TestVariableValue";
+
+    /** Stores the object to be tested. */
+    private ConfigurationInterpolator interpolator;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        interpolator = new ConfigurationInterpolator();
+    }
+
+    /**
+     * Cleans the test environment. Deregisters the test lookup object if
+     * necessary.
+     */
+    @After
+    public void tearDown() throws Exception
+    {
+        ConfigurationInterpolator.deregisterGlobalLookup(TEST_PREFIX);
+    }
+
+    /**
+     * Tests creating an instance. Does it contain some predefined lookups?
+     */
+    @Test
+    public void testInit()
+    {
+        assertNull("A default lookup is set", interpolator.getDefaultLookup());
+        assertFalse("No predefined lookups", interpolator.prefixSet().isEmpty());
+    }
+
+    /**
+     * Tries to register a global lookup for a null prefix. This should cause an
+     * exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testRegisterGlobalLookupNullPrefix()
+    {
+        ConfigurationInterpolator.registerGlobalLookup(null, StrLookup
+                .noneLookup());
+    }
+
+    /**
+     * Tries to register a global null lookup. This should cause an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testRegisterGlobalLookupNull()
+    {
+        ConfigurationInterpolator.registerGlobalLookup(TEST_PREFIX, null);
+    }
+
+    /**
+     * Tests registering a global lookup object. This lookup object should then
+     * be available for instances created later on.
+     */
+    @Test
+    public void testRegisterGlobalLookup()
+    {
+        ConfigurationInterpolator.registerGlobalLookup(TEST_PREFIX, StrLookup
+                .noneLookup());
+        ConfigurationInterpolator int2 = new ConfigurationInterpolator();
+        assertTrue("No lookup registered for test prefix", int2.prefixSet()
+                .contains(TEST_PREFIX));
+        assertFalse("Existing instance was modified", interpolator.prefixSet()
+                .contains(TEST_PREFIX));
+    }
+
+    /**
+     * Tests deregistering a global lookup object.
+     */
+    @Test
+    public void testDeregisterGlobalLookup()
+    {
+        ConfigurationInterpolator.registerGlobalLookup(TEST_PREFIX, StrLookup
+                .noneLookup());
+        assertTrue("Lookup could not be deregistered",
+                ConfigurationInterpolator.deregisterGlobalLookup(TEST_PREFIX));
+        ConfigurationInterpolator int2 = new ConfigurationInterpolator();
+        assertFalse("Deregistered lookup still available", int2.prefixSet()
+                .contains(TEST_PREFIX));
+    }
+
+    /**
+     * Tests deregistering an unknown lookup.
+     */
+    @Test
+    public void testDeregisterGlobalLookupNonExisting()
+    {
+        assertFalse("Could deregister unknown global lookup",
+                ConfigurationInterpolator.deregisterGlobalLookup(TEST_PREFIX));
+    }
+
+    /**
+     * Tests registering a lookup object at an instance.
+     */
+    @Test
+    public void testRegisterLookup()
+    {
+        int cnt = interpolator.prefixSet().size();
+        interpolator.registerLookup(TEST_PREFIX, StrLookup.noneLookup());
+        assertTrue("New lookup not registered", interpolator.prefixSet()
+                .contains(TEST_PREFIX));
+        assertEquals("Wrong number of registered lookups", cnt + 1,
+                interpolator.prefixSet().size());
+        ConfigurationInterpolator int2 = new ConfigurationInterpolator();
+        assertFalse("Local registration has global impact", int2.prefixSet()
+                .contains(TEST_PREFIX));
+    }
+
+    /**
+     * Tests registering a null lookup object. This should cause an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testRegisterLookupNull()
+    {
+        interpolator.registerLookup(TEST_PREFIX, null);
+    }
+
+    /**
+     * Tests registering a lookup object for an undefined prefix. This should
+     * cause an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testRegisterLookupNullPrefix()
+    {
+        interpolator.registerLookup(null, StrLookup.noneLookup());
+    }
+
+    /**
+     * Tests deregistering a local lookup object.
+     */
+    @Test
+    public void testDeregisterLookup()
+    {
+        interpolator.registerLookup(TEST_PREFIX, StrLookup.noneLookup());
+        assertTrue("Derigstration not successfull", interpolator
+                .deregisterLookup(TEST_PREFIX));
+        assertFalse("Deregistered prefix still contained", interpolator
+                .prefixSet().contains(TEST_PREFIX));
+    }
+
+    /**
+     * Tests deregistering an unknown lookup object.
+     */
+    @Test
+    public void testDeregisterLookupNonExisting()
+    {
+        assertFalse("Could deregister unknown lookup", interpolator
+                .deregisterLookup(TEST_PREFIX));
+    }
+
+    /**
+     * Tests whether a variable can be resolved using the associated lookup
+     * object. The lookup is identified by the variable's prefix.
+     */
+    @Test
+    public void testLookupWithPrefix()
+    {
+        interpolator.registerLookup(TEST_PREFIX, setUpTestLookup());
+        assertEquals("Wrong variable value", TEST_VALUE, interpolator
+                .lookup(TEST_PREFIX + ':' + TEST_NAME));
+    }
+
+    /**
+     * Tests the behavior of the lookup method for variables with an unknown
+     * prefix. These variables should not be resolved.
+     */
+    @Test
+    public void testLookupWithUnknownPrefix()
+    {
+        interpolator.registerLookup(TEST_PREFIX, setUpTestLookup());
+        assertNull("Variable could be resolved", interpolator
+                .lookup("UnknownPrefix:" + TEST_NAME));
+        assertNull("Variable with empty prefix could be resolved", interpolator
+                .lookup(":" + TEST_NAME));
+    }
+
+    /**
+     * Tests looking up a variable without a prefix. This should trigger the
+     * default lookup object.
+     */
+    @Test
+    public void testLookupDefault()
+    {
+        interpolator.setDefaultLookup(setUpTestLookup());
+        assertEquals("Wrong variable value", TEST_VALUE, interpolator
+                .lookup(TEST_NAME));
+    }
+
+    /**
+     * Tests looking up a variable without a prefix when no default lookup is
+     * specified. Result should be null in this case.
+     */
+    @Test
+    public void testLookupNoDefault()
+    {
+        assertNull("Variable could be resolved", interpolator.lookup(TEST_NAME));
+    }
+
+    /**
+     * Tests the empty variable prefix. This is a special case, but legal.
+     */
+    @Test
+    public void testLookupEmptyPrefix()
+    {
+        interpolator.registerLookup("", setUpTestLookup());
+        assertEquals("Wrong variable value", TEST_VALUE, interpolator
+                .lookup(":" + TEST_NAME));
+    }
+
+    /**
+     * Tests an empty variable name.
+     */
+    @Test
+    public void testLookupEmptyVarName()
+    {
+        Map<String, String> map = new HashMap<String, String>();
+        map.put("", TEST_VALUE);
+        interpolator.registerLookup(TEST_PREFIX, StrLookup.mapLookup(map));
+        assertEquals("Wrong variable value", TEST_VALUE, interpolator
+                .lookup(TEST_PREFIX + ":"));
+    }
+
+    /**
+     * Tests an empty variable name without a prefix.
+     */
+    @Test
+    public void testLookupDefaultEmptyVarName()
+    {
+        Map<String, String> map = new HashMap<String, String>();
+        map.put("", TEST_VALUE);
+        interpolator.setDefaultLookup(StrLookup.mapLookup(map));
+        assertEquals("Wrong variable value", TEST_VALUE, interpolator
+                .lookup(""));
+    }
+
+    /**
+     * Tests looking up a null variable. Result shoult be null, too.
+     */
+    @Test
+    public void testLookupNull()
+    {
+        assertNull("Could resolve null variable", interpolator.lookup(null));
+    }
+
+    /**
+     * Creates a lookup object that can resolve the test variable.
+     *
+     * @return the test lookup object
+     */
+    private StrLookup setUpTestLookup()
+    {
+        Map<String, String> map = new HashMap<String, String>();
+        map.put(TEST_NAME, TEST_VALUE);
+        return StrLookup.mapLookup(map);
+    }
+
+    /**
+     * Tests whether system properties can be correctly resolved.
+     */
+    @Test
+    public void testLookupSysProperties()
+    {
+        Properties sysProps = System.getProperties();
+        for (Object prop : sysProps.keySet())
+        {
+            String key = (String) prop;
+            assertEquals("Wrong value for system property " + key, sysProps
+                    .getProperty(key), interpolator
+                    .lookup(ConfigurationInterpolator.PREFIX_SYSPROPERTIES
+                            + ":" + key));
+        }
+    }
+
+    /**
+     * Tests whether constants can be correctly resolved.
+     */
+    @Test
+    public void testLookupConstants()
+    {
+        String varName = ConfigurationInterpolator.class.getName()
+                + ".PREFIX_CONSTANTS";
+        assertEquals("Wrong constant value",
+                ConfigurationInterpolator.PREFIX_CONSTANTS, interpolator
+                        .lookup(ConfigurationInterpolator.PREFIX_CONSTANTS
+                                + ":" + varName));
+    }
+
+    /**
+     * Tests whether the default lookup is called for variables with a prefix
+     * when the lookup that was registered for this prefix is not able to
+     * resolve the variable.
+     */
+    @Test
+    public void testLookupDefaultAfterPrefixFails()
+    {
+        final String varName = TEST_PREFIX + ':' + TEST_NAME + "2";
+        interpolator.registerLookup(TEST_PREFIX, setUpTestLookup());
+        Map<String, Object> map = new HashMap<String, Object>();
+        map.put(varName, TEST_VALUE);
+        interpolator.setDefaultLookup(StrLookup.mapLookup(map));
+        assertEquals("Variable is not resolved by default lookup", TEST_VALUE,
+                interpolator.lookup(varName));
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/interpol/TestConstantLookup.java b/src/test/java/org/apache/commons/configuration/interpol/TestConstantLookup.java
new file mode 100644
index 0000000..3d46885
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/interpol/TestConstantLookup.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.interpol;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.awt.event.KeyEvent;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for ConstantLookup.
+ *
+ * @version $Id: TestConstantLookup.java 1225656 2011-12-29 21:09:11Z oheger $
+ */
+public class TestConstantLookup
+{
+    /** Constant for the name of the test class. */
+    private static final String CLS_NAME = ConfigurationInterpolator.class
+            .getName() + '.';
+
+    /** Constant for the name of the test field. */
+    private static final String FIELD = "PREFIX_CONSTANTS";
+
+    /** Constant for the test variable name. */
+    private static final String VARNAME = CLS_NAME + FIELD;
+
+    /** The lookup object to be tested. */
+    private ConstantLookup lookup;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        lookup = new ConstantLookup();
+    }
+
+    /**
+     * Clears the test environment. Here the static cache of the constant lookup
+     * class is wiped out.
+     */
+    @After
+    public void tearDown() throws Exception
+    {
+        ConstantLookup.clear();
+    }
+
+    /**
+     * Tests resolving a valid constant.
+     */
+    @Test
+    public void testLookupConstant()
+    {
+        assertEquals("Wrong value of constant",
+                ConfigurationInterpolator.PREFIX_CONSTANTS, lookup
+                        .lookup(VARNAME));
+    }
+
+    /**
+     * Tests resolving a non existing constant. Result should be null.
+     */
+    @Test
+    public void testLookupNonExisting()
+    {
+        assertNull("Non null return value for non existing constant", lookup
+                .lookup(CLS_NAME + "NO_FIELD"));
+    }
+
+    /**
+     * Tests resolving a private constant. Because a private field cannot be
+     * accessed this should again yield null.
+     */
+    @Test
+    public void testLookupPrivate()
+    {
+        assertNull("Non null return value for non accessable field", lookup
+                .lookup(CLS_NAME + "PREFIX_SEPARATOR"));
+    }
+
+    /**
+     * Tests resolving a field from an unknown class.
+     */
+    @Test
+    public void testLookupUnknownClass()
+    {
+        assertNull("Non null return value for unknown class", lookup
+                .lookup("org.apache.commons.configuration.NonExistingConfig."
+                        + FIELD));
+    }
+
+    /**
+     * Tries to resolve a variable with an invalid syntax: The name does not
+     * contain a dot as a field separator.
+     */
+    @Test
+    public void testLookupInvalidSyntax()
+    {
+        assertNull("Non null return value for invalid variable name", lookup
+                .lookup("InvalidVariableName"));
+    }
+
+    /**
+     * Tests looking up a null variable.
+     */
+    @Test
+    public void testLookupNull()
+    {
+        assertNull("Non null return value for null variable", lookup
+                .lookup(null));
+    }
+
+    /**
+     * Tests accessing the cache by querying a variable twice.
+     */
+    @Test
+    public void testLookupCache()
+    {
+        testLookupConstant();
+        testLookupConstant();
+    }
+
+    /**
+     * Tests resolving a non string constant. Then looks the same variable up
+     * from the cache.
+     */
+    @Test
+    public void testLookupNonStringFromCache()
+    {
+        final String var = KeyEvent.class.getName() + ".VK_ESCAPE";
+        final String expected = String.valueOf(KeyEvent.VK_ESCAPE);
+        assertEquals("Wrong result of first lookup", expected, lookup
+                .lookup(var));
+        assertEquals("Wrong result of 2nd lookup", expected, lookup.lookup(var));
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/interpol/TestEnvironmentLookup.java b/src/test/java/org/apache/commons/configuration/interpol/TestEnvironmentLookup.java
new file mode 100644
index 0000000..478d312
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/interpol/TestEnvironmentLookup.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.interpol;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.util.Iterator;
+
+import org.apache.commons.configuration.EnvironmentConfiguration;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for EnvironmentLookup.
+ *
+ * @author <a
+ *         href="http://commons.apache.org/configuration/team-list.html">Commons
+ *         Configuration team</a>
+ * @version $Id: TestEnvironmentLookup.java 1225658 2011-12-29 21:11:03Z oheger $
+ */
+public class TestEnvironmentLookup
+{
+    /** The lookup to be tested. */
+    private EnvironmentLookup lookup;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        lookup = new EnvironmentLookup();
+    }
+
+    /**
+     * Tests whether environment variables can be queried.
+     */
+    @Test
+    public void testLookup()
+    {
+        EnvironmentConfiguration envConf = new EnvironmentConfiguration();
+        for (Iterator<String> it = envConf.getKeys(); it.hasNext();)
+        {
+            String var = it.next();
+            assertEquals("Wrong value for " + var, envConf.getString(var),
+                    lookup.lookup(var));
+        }
+    }
+
+    /**
+     * Tries to lookup a non existing property.
+     */
+    @Test
+    public void testLookupNonExisting()
+    {
+        assertNull("Got result for non existing environment variable", lookup
+                .lookup("a non existing variable!"));
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/interpol/TestExprLookup.java b/src/test/java/org/apache/commons/configuration/interpol/TestExprLookup.java
new file mode 100644
index 0000000..2b1dc3e
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/interpol/TestExprLookup.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.interpol;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+
+import org.apache.commons.configuration.ConfigurationAssert;
+import org.apache.commons.configuration.XMLConfiguration;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.logging.impl.Log4JLogger;
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.SimpleLayout;
+import org.junit.Test;
+
+/**
+ * Test class for ExprLookup.
+ *
+ * @version $Id: TestExprLookup.java 1225659 2011-12-29 21:12:54Z oheger $
+ */
+public class TestExprLookup
+{
+    private static File TEST_FILE = ConfigurationAssert.getTestFile("test.xml");
+
+    private static String PATTERN1 =
+        "String.replace(Util.message, 'Hello', 'Goodbye') + System.getProperty('user.name')";
+    private static String PATTERN2 =
+        "'$[element] ' + String.trimToEmpty('$[space.description]')";
+
+    @Test
+    public void testLookup() throws Exception
+    {
+        ConsoleAppender app = new ConsoleAppender(new SimpleLayout());
+        Log log = LogFactory.getLog("TestLogger");
+        Logger logger = ((Log4JLogger)log).getLogger();
+        logger.addAppender(app);
+        logger.setLevel(Level.DEBUG);
+        logger.setAdditivity(false);
+        ExprLookup.Variables vars = new ExprLookup.Variables();
+        vars.add(new ExprLookup.Variable("String", org.apache.commons.lang.StringUtils.class));
+        vars.add(new ExprLookup.Variable("Util", new Utility("Hello")));
+        vars.add(new ExprLookup.Variable("System", "Class:java.lang.System"));
+        XMLConfiguration config = new XMLConfiguration(TEST_FILE);
+        config.setLogger(log);
+        ExprLookup lookup = new ExprLookup(vars);
+        lookup.setConfiguration(config);
+        String str = lookup.lookup(PATTERN1);
+        assertTrue(str.startsWith("Goodbye"));
+        str = lookup.lookup(PATTERN2);
+        assertTrue("Incorrect value: " + str, str.equals("value Some text"));
+        logger.removeAppender(app);
+
+    }
+
+    public static class Utility
+    {
+        String message;
+
+        public Utility(String msg)
+        {
+            this.message = msg;
+        }
+
+        public String getMessage()
+        {
+            return message;
+        }
+
+        public String str(String str)
+        {
+            return str;
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/plist/AbstractTestPListEvents.java b/src/test/java/org/apache/commons/configuration/plist/AbstractTestPListEvents.java
new file mode 100644
index 0000000..af4981d
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/plist/AbstractTestPListEvents.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.plist;
+
+import org.apache.commons.configuration.AbstractConfiguration;
+import org.apache.commons.configuration.event.AbstractTestConfigurationEvents;
+import org.junit.Test;
+
+/**
+ * A base test class for testing the events generated by the plist
+ * configurations. This class especially checks events related to the special
+ * handling of byte arrays.
+ *
+ * @version $Id: AbstractTestPListEvents.java 1225901 2011-12-30 19:37:46Z oheger $
+ */
+public abstract class AbstractTestPListEvents extends
+        AbstractTestConfigurationEvents
+{
+    /** Constant for the name of the byte array property. */
+    private static final String TEST_PROPBYTE = "byteData";
+
+    /** Constant for the test byte array used for testing. */
+    private static final byte[] TEST_DATA =
+    { 1, 2, 3 };
+
+    /**
+     * Tests the events generated by an added byte array property.
+     */
+    @Test
+    public void testAddByteArrayPropertyEvent()
+    {
+        config.addProperty(TEST_PROPBYTE, TEST_DATA);
+        l.checkEvent(AbstractConfiguration.EVENT_ADD_PROPERTY, TEST_PROPBYTE,
+                TEST_DATA, true);
+        l.checkEvent(AbstractConfiguration.EVENT_ADD_PROPERTY, TEST_PROPBYTE,
+                TEST_DATA, false);
+        l.done();
+    }
+
+    /**
+     * Tests the events generated by setting a byte array property.
+     */
+    @Test
+    public void testSetByteArrayPropertyEvent()
+    {
+        config.setProperty(TEST_PROPBYTE, TEST_DATA);
+        l.checkEvent(AbstractConfiguration.EVENT_SET_PROPERTY, TEST_PROPBYTE,
+                TEST_DATA, true);
+        l.checkEvent(AbstractConfiguration.EVENT_SET_PROPERTY, TEST_PROPBYTE,
+                TEST_DATA, false);
+        l.done();
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/plist/TestPropertyListConfiguration.java b/src/test/java/org/apache/commons/configuration/plist/TestPropertyListConfiguration.java
new file mode 100644
index 0000000..7c0b3d7
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/plist/TestPropertyListConfiguration.java
@@ -0,0 +1,414 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.plist;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.StringReader;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TimeZone;
+
+import junitx.framework.ArrayAssert;
+import junitx.framework.ListAssert;
+import junitx.framework.ObjectAssert;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationAssert;
+import org.apache.commons.configuration.ConfigurationComparator;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.StrictConfigurationComparator;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Emmanuel Bourg
+ * @version $Id: TestPropertyListConfiguration.java 1225902 2011-12-30 19:46:24Z oheger $
+ */
+public class TestPropertyListConfiguration
+{
+    private PropertyListConfiguration config;
+
+    private String testProperties = ConfigurationAssert.getTestFile("test.plist").getAbsolutePath();
+
+    @Before
+    public void setUp() throws Exception
+    {
+        config = new PropertyListConfiguration();
+        config.setFileName(testProperties);
+        config.load();
+    }
+
+    @Test
+    public void testLoad()
+    {
+        assertFalse("the configuration is empty", config.isEmpty());
+    }
+
+    @Test
+    public void testLoadWithError()
+    {
+        config = new PropertyListConfiguration();
+        try {
+            config.load(new StringReader(""));
+            fail("No exception thrown on loading an empty file");
+        } catch (ConfigurationException e) {
+            // expected
+            assertNotNull(e.getMessage());
+        }
+    }
+
+    @Test
+    public void testString()
+    {
+        assertEquals("simple-string", "string1", config.getProperty("simple-string"));
+    }
+
+    @Test
+    public void testQuotedString()
+    {
+        assertEquals("quoted-string", "string2", config.getProperty("quoted-string"));
+        assertEquals("quoted-string2", "this is a string", config.getProperty("quoted-string2"));
+        assertEquals("complex-string", "this is a \"complex\" string {(=,;)}", config.getProperty("complex-string"));
+    }
+
+    @Test
+    public void testEmptyArray()
+    {
+        String key = "empty-array";
+        assertNotNull("array null", config.getProperty(key));
+
+        List<?> list = (List<?>) config.getProperty(key);
+        assertTrue("array is not empty", list.isEmpty());
+    }
+
+    @Test
+    public void testArray()
+    {
+        String key = "array";
+        assertNotNull("array null", config.getProperty(key));
+
+        List<?> list = (List<?>) config.getProperty(key);
+        assertFalse("array is empty", list.isEmpty());
+
+        assertEquals("1st value", "value1", list.get(0));
+        assertEquals("2nd value", "value2", list.get(1));
+        assertEquals("3rd value", "value3", list.get(2));
+    }
+
+    @Test
+    public void testNestedArrays()
+    {
+        String key = "nested-arrays";
+
+        Object array = config.getProperty(key);
+
+        // root array
+        assertNotNull("array not found", array);
+        ObjectAssert.assertInstanceOf("the array element is not parsed as a List", List.class, array);
+        List<?> list = config.getList(key);
+
+        assertFalse("empty array", list.isEmpty());
+        assertEquals("size", 2, list.size());
+
+        // 1st array
+        ObjectAssert.assertInstanceOf("the array element is not parsed as a List", List.class, list.get(0));
+        List<?> list1 = (List<?>) list.get(0);
+        assertFalse("nested array 1 is empty", list1.isEmpty());
+        assertEquals("size", 2, list1.size());
+        assertEquals("1st element", "a", list1.get(0));
+        assertEquals("2nd element", "b", list1.get(1));
+
+        // 2nd array
+        ObjectAssert.assertInstanceOf("the array element is not parsed as a List", List.class, list.get(1));
+        List<?> list2 = (List<?>) list.get(1);
+        assertFalse("nested array 2 is empty", list2.isEmpty());
+        assertEquals("size", 2, list2.size());
+        assertEquals("1st element", "c", list2.get(0));
+        assertEquals("2nd element", "d", list2.get(1));
+    }
+
+    @Test
+    public void testDictionary()
+    {
+        assertEquals("1st element in dictionary", "bar1", config.getProperty("dictionary.foo1"));
+        assertEquals("2nd element in dictionary", "bar2", config.getProperty("dictionary.foo2"));
+    }
+
+    @Test
+    public void testDictionaryArray()
+    {
+        String key = "dictionary-array";
+
+        Object array = config.getProperty(key);
+
+        // root array
+        assertNotNull("array not found", array);
+        ObjectAssert.assertInstanceOf("the array element is not parsed as a List", List.class, array);
+        List<?> list = config.getList(key);
+
+        assertFalse("empty array", list.isEmpty());
+        assertEquals("size", 2, list.size());
+
+        // 1st dictionary
+        ObjectAssert.assertInstanceOf("the dict element is not parsed as a Configuration", Configuration.class, list.get(0));
+        Configuration conf1 = (Configuration) list.get(0);
+        assertFalse("configuration 1 is empty", conf1.isEmpty());
+        assertEquals("configuration element", "bar", conf1.getProperty("foo"));
+
+        // 2nd dictionary
+        ObjectAssert.assertInstanceOf("the dict element is not parsed as a Configuration", Configuration.class, list.get(1));
+        Configuration conf2 = (Configuration) list.get(1);
+        assertFalse("configuration 2 is empty", conf2.isEmpty());
+        assertEquals("configuration element", "value", conf2.getProperty("key"));
+    }
+
+    @Test
+    public void testNestedDictionaries()
+    {
+        assertEquals("nested property", "value", config.getString("nested-dictionaries.foo.bar.key"));
+    }
+
+    @Test
+    public void testData()
+    {
+        ObjectAssert.assertInstanceOf("data", (new byte[0]).getClass(), config.getProperty("data"));
+        ArrayAssert.assertEquals("data", "foo bar".getBytes(), (byte[]) config.getProperty("data"));
+    }
+
+    @Test
+    public void testDate() throws Exception
+    {
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(2002, 2, 22, 11, 30, 0);
+        cal.setTimeZone(TimeZone.getTimeZone("GMT+0100"));
+        Date date = cal.getTime();
+
+        assertEquals("date", date, config.getProperty("date"));
+    }
+
+    @Test
+    public void testSave() throws Exception
+    {
+        File savedFile = new File("target/testsave.plist");
+
+        // remove the file previously saved if necessary
+        if (savedFile.exists())
+        {
+            assertTrue(savedFile.delete());
+        }
+
+        // save the configuration
+        String filename = savedFile.getAbsolutePath();
+        config.save(filename);
+
+        assertTrue("The saved file doesn't exist", savedFile.exists());
+
+        // read the configuration and compare the properties
+        Configuration checkConfig = new PropertyListConfiguration(new File(filename));
+
+        Iterator<String> it = config.getKeys();
+        while (it.hasNext())
+        {
+            String key = it.next();
+            assertTrue("The saved configuration doesn't contain the key '" + key + "'", checkConfig.containsKey(key));
+
+            Object value = checkConfig.getProperty(key);
+            if (value instanceof byte[])
+            {
+                byte[] array = (byte[]) value;
+                ArrayAssert.assertEquals("Value of the '" + key + "' property", (byte[]) config.getProperty(key), array);
+            }
+            else if (value instanceof List)
+            {
+                List<?> list1 = (List<?>) config.getProperty(key);
+                List<?> list2 = (List<?>) value;
+
+                assertEquals("The size of the list for the key '" + key + "' doesn't match", list1.size(), list2.size());
+
+                for (int i = 0; i < list2.size(); i++)
+                {
+                    Object value1 = list1.get(i);
+                    Object value2 = list2.get(i);
+
+                    if (value1 instanceof Configuration)
+                    {
+                        ConfigurationComparator comparator = new StrictConfigurationComparator();
+                        assertTrue("The dictionnary at index " + i + " for the key '" + key + "' doesn't match", comparator.compare((Configuration) value1, (Configuration) value2));
+                    }
+                    else
+                    {
+                        assertEquals("Element at index " + i + " for the key '" + key + "'", value1, value2);
+                    }
+                }
+
+                ListAssert.assertEquals("Value of the '" + key + "' property", (List<?>) config.getProperty(key), list1);
+            }
+            else
+            {
+                assertEquals("Value of the '" + key + "' property", config.getProperty(key), checkConfig.getProperty(key));
+            }
+
+        }
+    }
+
+    @Test
+    public void testSaveEmptyDictionary() throws Exception
+    {
+        File savedFile = new File("target/testsave.plist");
+
+        // remove the file previously saved if necessary
+        if (savedFile.exists())
+        {
+            assertTrue(savedFile.delete());
+        }
+
+        // save the configuration
+        String filename = savedFile.getAbsolutePath();
+        config.save(filename);
+
+        assertTrue("The saved file doesn't exist", savedFile.exists());
+
+        // read the configuration and compare the properties
+        PropertyListConfiguration checkConfig = new PropertyListConfiguration(new File(filename));
+
+        assertFalse(config.getRootNode().getChildren("empty-dictionary").isEmpty());
+        assertFalse(checkConfig.getRootNode().getChildren("empty-dictionary").isEmpty());
+    }
+
+    @Test
+    public void testQuoteString()
+    {
+        assertEquals("null string", null, config.quoteString(null));
+        assertEquals("simple string", "abcd", config.quoteString("abcd"));
+        assertEquals("string with a space", "\"ab cd\"", config.quoteString("ab cd"));
+        assertEquals("string with a quote", "\"foo\\\"bar\"", config.quoteString("foo\"bar"));
+        assertEquals("string with a special char", "\"foo;bar\"", config.quoteString("foo;bar"));
+    }
+
+    /**
+     * Ensure that setProperty doesn't alter an array of byte
+     * since it's a first class type in plist file
+     */
+    @Test
+    public void testSetDataProperty() throws Exception
+    {
+        byte[] expected = new byte[]{1, 2, 3, 4};
+        PropertyListConfiguration config = new PropertyListConfiguration();
+        config.setProperty("foo", expected);
+        config.save("target/testdata.plist");
+
+        PropertyListConfiguration config2 = new PropertyListConfiguration("target/testdata.plist");
+        Object array = config2.getProperty("foo");
+
+        assertNotNull("data not found", array);
+        assertEquals("property type", byte[].class, array.getClass());
+        ArrayAssert.assertEquals(expected, (byte[]) array);
+    }
+
+    /**
+     * Ensure that addProperty doesn't alter an array of byte
+     */
+    @Test
+    public void testAddDataProperty() throws Exception
+    {
+        byte[] expected = new byte[]{1, 2, 3, 4};
+        PropertyListConfiguration config = new PropertyListConfiguration();
+        config.addProperty("foo", expected);
+        config.save("target/testdata.plist");
+
+        PropertyListConfiguration config2 = new PropertyListConfiguration("target/testdata.plist");
+        Object array = config2.getProperty("foo");
+
+        assertNotNull("data not found", array);
+        assertEquals("property type", byte[].class, array.getClass());
+        ArrayAssert.assertEquals(expected, (byte[]) array);
+    }
+
+    @Test
+    public void testInitCopy()
+    {
+        PropertyListConfiguration copy = new PropertyListConfiguration(config);
+        assertFalse("Nothing was copied", copy.isEmpty());
+    }
+
+    /**
+     * Tests parsing a date with an invalid numeric value.
+     */
+    @Test(expected = ParseException.class)
+    public void testParseDateNoNumber() throws ParseException
+    {
+        PropertyListConfiguration
+                .parseDate("<*D2002-03-22 1c:30:00 +0100>");
+    }
+
+    /**
+     * Tests parsing a date that is not long enough.
+     */
+    @Test(expected = ParseException.class)
+    public void testParseDateTooShort() throws ParseException
+    {
+        PropertyListConfiguration.parseDate("<*D2002-03-22 11:3>");
+    }
+
+    /**
+     * Tests parsing a date that contains an invalid separator character.
+     */
+    @Test(expected = ParseException.class)
+    public void testParseDateInvalidChar() throws ParseException
+    {
+        PropertyListConfiguration
+                .parseDate("<*D2002+03-22 11:30:00 +0100>");
+    }
+
+    /**
+     * Tries parsing a null date. This should cause an exception.n
+     */
+    @Test(expected = ParseException.class)
+    public void testParseDateNull() throws ParseException
+    {
+        PropertyListConfiguration.parseDate(null);
+    }
+
+    /**
+     * Tests formatting a date.
+     */
+    @Test
+    public void testFormatDate()
+    {
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(2007, 9, 29, 23, 4, 30);
+        cal.setTimeZone(TimeZone.getTimeZone("GMT-0230"));
+        assertEquals("Wrong date literal (1)", "<*D2007-10-29 23:04:30 -0230>",
+                PropertyListConfiguration.formatDate(cal));
+        cal.clear();
+        cal.set(2007, 9, 30, 22, 2, 15);
+        cal.setTimeZone(TimeZone.getTimeZone("GMT+1111"));
+        assertEquals("Wrong date literal (2)", "<*D2007-10-30 22:02:15 +1111>",
+                PropertyListConfiguration.formatDate(cal));
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/plist/TestPropertyListConfigurationEvents.java b/src/test/java/org/apache/commons/configuration/plist/TestPropertyListConfigurationEvents.java
new file mode 100644
index 0000000..427b9be
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/plist/TestPropertyListConfigurationEvents.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.plist;
+
+import java.io.File;
+
+import org.apache.commons.configuration.AbstractConfiguration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.ConfigurationRuntimeException;
+
+/**
+ * Test class for the events generated by PropertyListConfiguration.
+ *
+ * @version $Id: TestPropertyListConfigurationEvents.java 1301994 2012-03-17 20:21:23Z sebb $
+ */
+public class TestPropertyListConfigurationEvents extends
+        AbstractTestPListEvents
+{
+    /** Constant for the test file that will be loaded. */
+    private static final File TEST_FILE = new File("conf/test.plist");
+
+    @Override
+    protected AbstractConfiguration createConfiguration()
+    {
+        try
+        {
+            return new PropertyListConfiguration(TEST_FILE);
+        }
+        catch (ConfigurationException cex)
+        {
+            throw new ConfigurationRuntimeException(cex);
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/configuration/plist/TestPropertyListParser.java b/src/test/java/org/apache/commons/configuration/plist/TestPropertyListParser.java
new file mode 100644
index 0000000..a90f85a
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/plist/TestPropertyListParser.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.plist;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.Reader;
+import java.util.Calendar;
+import java.util.SimpleTimeZone;
+
+import junitx.framework.ArrayAssert;
+
+import org.junit.Test;
+
+/**
+ * @author Emmanuel Bourg
+ * @version $Id: TestPropertyListParser.java 1225903 2011-12-30 19:49:15Z oheger $
+ */
+public class TestPropertyListParser
+{
+    private PropertyListParser parser = new PropertyListParser((Reader) null);
+
+    @Test
+    public void testRemoveQuotes()
+    {
+        assertEquals("unquoted string", "abc", parser.removeQuotes("abc"));
+        assertEquals("quoted string", "abc", parser.removeQuotes("\"abc\""));
+        assertEquals("empty quotes", "", parser.removeQuotes("\"\""));
+        assertEquals("empty string", "", parser.removeQuotes(""));
+        assertEquals("null string", null, parser.removeQuotes(null));
+    }
+
+    @Test
+    public void testUnescapeQuotes()
+    {
+        assertEquals("non escaped quotes", "aaa\"bbb\"ccc", parser.unescapeQuotes("aaa\"bbb\"ccc"));
+        assertEquals("escaped quotes", "aaa\"bbb\"ccc", parser.unescapeQuotes("aaa\\\"bbb\\\"ccc"));
+    }
+
+    @Test
+    public void testParseDate() throws Exception
+    {
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(Calendar.YEAR, 2002);
+        calendar.set(Calendar.MONTH, Calendar.MARCH);
+        calendar.set(Calendar.DAY_OF_MONTH, 22);
+        calendar.set(Calendar.HOUR_OF_DAY, 11);
+        calendar.set(Calendar.MINUTE, 30);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+        calendar.setTimeZone(new SimpleTimeZone(60 * 60 * 1000, "Apache/Jakarta"));
+
+        assertEquals("parsed date", calendar.getTime(), parser.parseDate("<*D2002-03-22 11:30:00 +0100>"));
+    }
+
+    @Test
+    public void testFilterData() throws Exception
+    {
+        byte[] expected = new byte[] {0x20, 0x20};
+        ArrayAssert.assertEquals("null string", null, parser.filterData(null));
+        ArrayAssert.assertEquals("data with < >", expected, parser.filterData("<2020>"));
+        ArrayAssert.assertEquals("data without < >", expected, parser.filterData("2020"));
+        ArrayAssert.assertEquals("data with space", expected, parser.filterData("20 20"));
+        ArrayAssert.assertEquals("odd length", new byte[]{9, 0x20}, parser.filterData("920"));
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/plist/TestXMLPropertyListConfiguration.java b/src/test/java/org/apache/commons/configuration/plist/TestXMLPropertyListConfiguration.java
new file mode 100644
index 0000000..cd026c6
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/plist/TestXMLPropertyListConfiguration.java
@@ -0,0 +1,409 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.plist;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.util.Calendar;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TimeZone;
+
+import junitx.framework.ArrayAssert;
+import junitx.framework.ListAssert;
+import junitx.framework.ObjectAssert;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationAssert;
+import org.apache.commons.configuration.ConfigurationComparator;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.FileConfiguration;
+import org.apache.commons.configuration.HierarchicalConfiguration;
+import org.apache.commons.configuration.StrictConfigurationComparator;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Emmanuel Bourg
+ * @version $Id: TestXMLPropertyListConfiguration.java 1367253 2012-07-30 19:59:36Z oheger $
+ */
+public class TestXMLPropertyListConfiguration
+{
+    private FileConfiguration config;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        config = new XMLPropertyListConfiguration();
+        config.setFile(ConfigurationAssert.getTestFile("test.plist.xml"));
+        config.load();
+    }
+
+    @Test
+    public void testString() throws Exception
+    {
+        assertEquals("'string' property", "value1", config.getString("string"));
+    }
+
+    @Test
+    public void testInteger() throws Exception
+    {
+        assertEquals("'integer' property", 12345678900L, config.getLong("integer"));
+    }
+
+    @Test
+    public void testReal() throws Exception
+    {
+        assertEquals("'real' property", -12.345, config.getDouble("real"), 0);
+    }
+
+    @Test
+    public void testBoolean() throws Exception
+    {
+        assertEquals("'boolean1' property", true, config.getBoolean("boolean1"));
+        assertEquals("'boolean2' property", false, config.getBoolean("boolean2"));
+    }
+
+    @Test
+    public void testDictionary()
+    {
+        assertEquals("1st element", "value1", config.getProperty("dictionary.key1"));
+        assertEquals("2nd element", "value2", config.getProperty("dictionary.key2"));
+        assertEquals("3rd element", "value3", config.getProperty("dictionary.key3"));
+    }
+
+    @Test
+    public void testDate() throws Exception
+    {
+        Calendar calendar = Calendar.getInstance();
+        calendar.clear();
+        calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
+        calendar.set(2005, Calendar.JANUARY, 1, 12, 0, 0);
+
+        assertEquals("'date' property", calendar.getTime(), config.getProperty("date"));
+
+        calendar.setTimeZone(TimeZone.getTimeZone("CET"));
+        calendar.set(2002, Calendar.MARCH, 22, 11, 30, 0);
+
+        assertEquals("'date-gnustep' property", calendar.getTime(), config.getProperty("date-gnustep"));
+    }
+
+    @Test
+    public void testSubset()
+    {
+        Configuration subset = config.subset("dictionary");
+        Iterator<String> keys = subset.getKeys();
+
+        String key = keys.next();
+        assertEquals("1st key", "key1", key);
+        assertEquals("1st value", "value1", subset.getString(key));
+
+        key = keys.next();
+        assertEquals("2nd key", "key2", key);
+        assertEquals("2nd value", "value2", subset.getString(key));
+
+        key = keys.next();
+        assertEquals("3rd key", "key3", key);
+        assertEquals("3rd value", "value3", subset.getString(key));
+
+        assertFalse("more than 3 properties founds", keys.hasNext());
+    }
+
+    @Test
+    public void testArray()
+    {
+        Object array = config.getProperty("array");
+
+        assertNotNull("array not found", array);
+        ObjectAssert.assertInstanceOf("the array element is not parsed as a List", List.class, array);
+        List<?> list = config.getList("array");
+
+        assertFalse("empty array", list.isEmpty());
+        assertEquals("size", 3, list.size());
+        assertEquals("1st element", "value1", list.get(0));
+        assertEquals("2nd element", "value2", list.get(1));
+        assertEquals("3rd element", "value3", list.get(2));
+    }
+
+    @Test
+    public void testNestedArray()
+    {
+        String key = "nested-array";
+
+        Object array = config.getProperty(key);
+
+        // root array
+        assertNotNull("array not found", array);
+        ObjectAssert.assertInstanceOf("the array element is not parsed as a List", List.class, array);
+        List<?> list = config.getList(key);
+
+        assertFalse("empty array", list.isEmpty());
+        assertEquals("size", 2, list.size());
+
+        // 1st array
+        ObjectAssert.assertInstanceOf("the array element is not parsed as a List", List.class, list.get(0));
+        List<?> list1 = (List<?>) list.get(0);
+        assertFalse("nested array 1 is empty", list1.isEmpty());
+        assertEquals("size", 2, list1.size());
+        assertEquals("1st element", "a", list1.get(0));
+        assertEquals("2nd element", "b", list1.get(1));
+
+        // 2nd array
+        ObjectAssert.assertInstanceOf("the array element is not parsed as a List", List.class, list.get(1));
+        List<?> list2 = (List<?>) list.get(1);
+        assertFalse("nested array 2 is empty", list2.isEmpty());
+        assertEquals("size", 2, list2.size());
+        assertEquals("1st element", "c", list2.get(0));
+        assertEquals("2nd element", "d", list2.get(1));
+    }
+
+    @Test
+    public void testDictionaryArray()
+    {
+        String key = "dictionary-array";
+
+        Object array = config.getProperty(key);
+
+        // root array
+        assertNotNull("array not found", array);
+        ObjectAssert.assertInstanceOf("the array element is not parsed as a List", List.class, array);
+        List<?> list = config.getList(key);
+
+        assertFalse("empty array", list.isEmpty());
+        assertEquals("size", 2, list.size());
+
+        // 1st dictionary
+        ObjectAssert.assertInstanceOf("the dict element is not parsed as a Configuration", Configuration.class, list.get(0));
+        Configuration conf1 = (Configuration) list.get(0);
+        assertFalse("configuration 1 is empty", conf1.isEmpty());
+        assertEquals("configuration element", "bar", conf1.getProperty("foo"));
+
+        // 2nd dictionary
+        ObjectAssert.assertInstanceOf("the dict element is not parsed as a Configuration", Configuration.class, list.get(1));
+        Configuration conf2 = (Configuration) list.get(1);
+        assertFalse("configuration 2 is empty", conf2.isEmpty());
+        assertEquals("configuration element", "value", conf2.getProperty("key"));
+    }
+
+    @Test
+    public void testNested()
+    {
+        assertEquals("nested property", "value", config.getString("nested.node1.node2.node3"));
+    }
+
+    @Test
+    public void testSave() throws Exception
+    {
+        File savedFile = new File("target/testsave.plist.xml");
+
+        // remove the file previously saved if necessary
+        if (savedFile.exists())
+        {
+            assertTrue(savedFile.delete());
+        }
+
+        // add an array of strings to the configuration
+        /*
+        config.addProperty("string", "value1");
+        List list = new ArrayList();
+        for (int i = 1; i < 5; i++)
+        {
+            list.add("value" + i);
+        }
+        config.addProperty("newarray", list);*/
+        // todo : investigate why the array structure of 'newarray' is lost in the saved file
+
+        // add a map of strings
+        /*
+        Map map = new HashMap();
+        map.put("foo", "bar");
+        map.put("int", new Integer(123));
+        config.addProperty("newmap", map);
+        */
+        // todo : a Map added to a HierarchicalConfiguration should be decomposed as list of nodes
+
+        // save the configuration
+        String filename = savedFile.getAbsolutePath();
+        config.save(filename);
+
+        assertTrue("The saved file doesn't exist", savedFile.exists());
+
+        // read the configuration and compare the properties
+        Configuration checkConfig = new XMLPropertyListConfiguration(new File(filename));
+
+        Iterator<String> it = config.getKeys();
+        while (it.hasNext())
+        {
+            String key = it.next();
+            assertTrue("The saved configuration doesn't contain the key '" + key + "'", checkConfig.containsKey(key));
+
+            Object value = checkConfig.getProperty(key);
+            if (value instanceof byte[])
+            {
+                byte[] array = (byte[]) value;
+                ArrayAssert.assertEquals("Value of the '" + key + "' property", (byte[]) config.getProperty(key), array);
+            }
+            else if (value instanceof List)
+            {
+                List<?> list1 = (List<?>) config.getProperty(key);
+                List<?> list2 = (List<?>) value;
+
+                assertEquals("The size of the list for the key '" + key + "' doesn't match", list1.size(), list2.size());
+
+                for (int i = 0; i < list2.size(); i++)
+                {
+                    Object value1 = list1.get(i);
+                    Object value2 = list2.get(i);
+
+                    if (value1 instanceof Configuration)
+                    {
+                        ConfigurationComparator comparator = new StrictConfigurationComparator();
+                        assertTrue("The dictionnary at index " + i + " for the key '" + key + "' doesn't match", comparator.compare((Configuration) value1, (Configuration) value2));
+                    }
+                    else
+                    {
+                        assertEquals("Element at index " + i + " for the key '" + key + "'", value1, value2);
+                    }
+                }
+
+                ListAssert.assertEquals("Value of the '" + key + "' property", (List<?>) config.getProperty(key), list1);
+            }
+            else
+            {
+                assertEquals("Value of the '" + key + "' property", config.getProperty(key), checkConfig.getProperty(key));
+            }
+
+        }
+    }
+
+    @Test
+    public void testSaveEmptyDictionary() throws Exception
+    {
+        File savedFile = new File("target/testsave.plist.xml");
+
+        // remove the file previously saved if necessary
+        if (savedFile.exists())
+        {
+            assertTrue(savedFile.delete());
+        }
+
+        // save the configuration
+        String filename = savedFile.getAbsolutePath();
+        config.save(filename);
+
+        assertTrue("The saved file doesn't exist", savedFile.exists());
+
+        // read the configuration and compare the properties
+        Configuration checkConfig = new XMLPropertyListConfiguration(new File(filename));
+
+        assertEquals(null, config.getProperty("empty-dictionary"));
+        assertEquals(null, checkConfig.getProperty("empty-dictionary"));
+    }
+
+    /**
+     * Ensure that setProperty doesn't alter an array of byte
+     * since it's a first class type in plist file
+     */
+    @Test
+    public void testSetDataProperty() throws Exception
+    {
+        byte[] expected = new byte[]{1, 2, 3, 4};
+        XMLPropertyListConfiguration config = new XMLPropertyListConfiguration();
+        config.setProperty("foo", expected);
+        config.save("target/testdata.plist.xml");
+
+        XMLPropertyListConfiguration config2 = new XMLPropertyListConfiguration("target/testdata.plist.xml");
+        Object array = config2.getProperty("foo");
+
+        assertNotNull("data not found", array);
+        assertEquals("property type", byte[].class, array.getClass());
+        ArrayAssert.assertEquals(expected, (byte[]) array);
+    }
+
+    /**
+     * Ensure that addProperty doesn't alter an array of byte
+     */
+    @Test
+    public void testAddDataProperty() throws Exception
+    {
+        byte[] expected = new byte[]{1, 2, 3, 4};
+        XMLPropertyListConfiguration config = new XMLPropertyListConfiguration();
+        config.addProperty("foo", expected);
+        config.save("target/testdata.plist.xml");
+
+        XMLPropertyListConfiguration config2 = new XMLPropertyListConfiguration("target/testdata.plist.xml");
+        Object array = config2.getProperty("foo");
+
+        assertNotNull("data not found", array);
+        assertEquals("property type", byte[].class, array.getClass());
+        ArrayAssert.assertEquals(expected, (byte[]) array);
+    }
+
+    @Test
+    public void testInitCopy()
+    {
+        XMLPropertyListConfiguration copy = new XMLPropertyListConfiguration((HierarchicalConfiguration) config);
+        StrictConfigurationComparator comp = new StrictConfigurationComparator();
+        assertTrue("Configurations are not equal", comp.compare(config, copy));
+    }
+
+    /**
+     * Tests whether a configuration can be loaded that does not start with a
+     * {@code dict} element. This test case is related to
+     * CONFIGURATION-405.
+     */
+    @Test
+    public void testLoadNoDict() throws ConfigurationException
+    {
+        XMLPropertyListConfiguration plist = new XMLPropertyListConfiguration();
+        plist.setFile(ConfigurationAssert.getTestFile("test2.plist.xml"));
+        plist.load();
+        assertFalse("Configuration is empty", plist.isEmpty());
+    }
+
+    /**
+     * Tests whether a configuration that does not start with a
+     * {@code dict} element can be loaded from a constructor. This test
+     * case is related to CONFIGURATION-405.
+     */
+    @Test
+    public void testLoadNoDictConstr() throws ConfigurationException
+    {
+        XMLPropertyListConfiguration plist = new XMLPropertyListConfiguration(
+                ConfigurationAssert.getTestFile("test2.plist.xml"));
+        assertFalse("Configuration is empty", plist.isEmpty());
+    }
+
+    /**
+     * Tests a configuration file which contains an invalid date property value.
+     * This test is related to CONFIGURATION-501.
+     */
+    @Test
+    public void testSetDatePropertyInvalid() throws ConfigurationException
+    {
+        config.clear();
+        config.setFile(ConfigurationAssert.getTestFile("test_invalid_date.plist.xml"));
+        config.load();
+        assertEquals("'string' property", "value1", config.getString("string"));
+        assertFalse("Date property was loaded", config.containsKey("date"));
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/plist/TestXMLPropertyListConfigurationEvents.java b/src/test/java/org/apache/commons/configuration/plist/TestXMLPropertyListConfigurationEvents.java
new file mode 100644
index 0000000..c569a91
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/plist/TestXMLPropertyListConfigurationEvents.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.plist;
+
+import java.io.File;
+
+import org.apache.commons.configuration.AbstractConfiguration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.ConfigurationRuntimeException;
+
+/**
+ * Test class for the events generated by XMLPropertyListConfiguration.
+ *
+ * @version $Id: TestXMLPropertyListConfigurationEvents.java 1301994 2012-03-17 20:21:23Z sebb $
+ */
+public class TestXMLPropertyListConfigurationEvents extends
+        AbstractTestPListEvents
+{
+    /** Constant for the test file that will be loaded. */
+    private static final File TEST_FILE = new File("conf/test.plist.xml");
+
+    @Override
+    protected AbstractConfiguration createConfiguration()
+    {
+        try
+        {
+            return new XMLPropertyListConfiguration(TEST_FILE);
+        }
+        catch (ConfigurationException cex)
+        {
+            throw new ConfigurationRuntimeException(cex);
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/reloading/FileAlwaysReloadingStrategy.java b/src/test/java/org/apache/commons/configuration/reloading/FileAlwaysReloadingStrategy.java
new file mode 100644
index 0000000..703a95d
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/reloading/FileAlwaysReloadingStrategy.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.reloading;
+
+import java.io.File;
+
+/**
+ * A specialized reloading strategy for files that will always report a change
+ * of the monitored file. Thus it is well suited for testing reloading
+ * operations on file-based configurations.
+ *
+ * @version $Id: FileAlwaysReloadingStrategy.java 1301995 2012-03-17 20:24:16Z sebb $
+ */
+public class FileAlwaysReloadingStrategy extends FileChangedReloadingStrategy
+{
+    /**
+     * Checks whether a reload is necessary. This implementation returns always
+     * <b>true</b>.
+     *
+     * @return a flag whether a reload is required
+     */
+    @Override
+    public boolean reloadingRequired()
+    {
+        return true;
+    }
+
+    /**
+     * Returns the file that is watched by this strategy.
+     *
+     * @return the monitored file
+     */
+    public File getMonitoredFile()
+    {
+        return getFile();
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/reloading/FileRandomReloadingStrategy.java b/src/test/java/org/apache/commons/configuration/reloading/FileRandomReloadingStrategy.java
new file mode 100644
index 0000000..0694df4
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/reloading/FileRandomReloadingStrategy.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.reloading;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.File;
+import java.util.Random;
+
+/**
+ * A ReloadingStrategy that randomly returns true or false;
+ */
+public class FileRandomReloadingStrategy extends FileChangedReloadingStrategy
+{
+    Random random = new Random();
+
+      /** The Log to use for diagnostic messages */
+    private Log logger = LogFactory.getLog(FileRandomReloadingStrategy.class);
+
+    /**
+     * Checks whether a reload is necessary.
+     *
+     * @return a flag whether a reload is required
+     */
+    @Override
+    public boolean reloadingRequired()
+    {
+        boolean result = random.nextBoolean();
+        if (result)
+        {
+            if (logger.isDebugEnabled())
+            {
+                logger.debug("File change detected: " + getName());
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Returns the file that is watched by this strategy.
+     *
+     * @return the monitored file
+     */
+    public File getMonitoredFile()
+    {
+        return getFile();
+    }
+
+    private String getName()
+    {
+        return getName(getFile());
+    }
+
+    private String getName(File file)
+    {
+        String name = configuration.getURL().toString();
+        if (name == null)
+        {
+            if (file != null)
+            {
+                name = file.getAbsolutePath();
+            }
+            else
+            {
+                name = "base: " + configuration.getBasePath()
+                       + "file: " + configuration.getFileName();
+            }
+        }
+        return name;
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/reloading/TestFileChangedReloadingStrategy.java b/src/test/java/org/apache/commons/configuration/reloading/TestFileChangedReloadingStrategy.java
new file mode 100644
index 0000000..f765e5b
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/reloading/TestFileChangedReloadingStrategy.java
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.reloading;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileWriter;
+import java.net.URL;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.commons.configuration.XMLConfiguration;
+import org.apache.log4j.Appender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.PatternLayout;
+import org.apache.log4j.WriterAppender;
+import org.junit.Test;
+
+/**
+ * Test case for the ReloadableConfiguration class.
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: TestFileChangedReloadingStrategy.java 1225906 2011-12-30 20:01:37Z oheger $
+ */
+public class TestFileChangedReloadingStrategy
+{
+    /** Constant for the name of a test properties file.*/
+    private static final String TEST_FILE = "test.properties";
+
+    @Test
+    public void testAutomaticReloading() throws Exception
+    {
+        // create a new configuration
+        File file = new File("target/testReload.properties");
+
+        if (file.exists())
+        {
+            file.delete();
+        }
+
+        // create the configuration file
+        FileWriter out = new FileWriter(file);
+        out.write("string=value1");
+        out.flush();
+        out.close();
+
+        // load the configuration
+        PropertiesConfiguration config = new PropertiesConfiguration("target/testReload.properties");
+        FileChangedReloadingStrategy strategy = new FileChangedReloadingStrategy();
+        strategy.setRefreshDelay(500);
+        config.setReloadingStrategy(strategy);
+        assertEquals("Initial value", "value1", config.getString("string"));
+
+        Thread.sleep(2000);
+
+        // change the file
+        out = new FileWriter(file);
+        out.write("string=value2");
+        out.flush();
+        out.close();
+
+        // test the automatic reloading
+        assertEquals("Modified value with enabled reloading", "value2", config.getString("string"));
+    }
+
+    @Test
+    public void testNewFileReloading() throws Exception
+    {
+        // create a new configuration
+        File file = new File("target/testReload.properties");
+
+        if (file.exists())
+        {
+            file.delete();
+        }
+
+        PropertiesConfiguration config = new PropertiesConfiguration();
+        config.setFile(file);
+        FileChangedReloadingStrategy strategy = new FileChangedReloadingStrategy();
+        strategy.setRefreshDelay(500);
+        config.setReloadingStrategy(strategy);
+
+        assertNull("Initial value", config.getString("string"));
+
+        // change the file
+        FileWriter out = new FileWriter(file);
+        out.write("string=value1");
+        out.flush();
+        out.close();
+
+        Thread.sleep(2000);
+
+        // test the automatic reloading
+        assertEquals("Modified value with enabled reloading", "value1", config.getString("string"));
+    }
+
+    @Test
+    public void testGetRefreshDelay()
+    {
+        FileChangedReloadingStrategy strategy = new FileChangedReloadingStrategy();
+        strategy.setRefreshDelay(500);
+        assertEquals("refresh delay", 500, strategy.getRefreshDelay());
+    }
+
+    /**
+     * Tests if a file from the classpath can be monitored.
+     */
+    @Test
+    public void testFromClassPath() throws Exception
+    {
+        PropertiesConfiguration config = new PropertiesConfiguration();
+        config.setFileName(TEST_FILE);
+        config.load();
+        assertTrue(config.getBoolean("configuration.loaded"));
+        FileChangedReloadingStrategy strategy = new FileChangedReloadingStrategy();
+        config.setReloadingStrategy(strategy);
+        assertEquals(config.getURL().toExternalForm(), strategy.getFile().toURI().toURL().toExternalForm());
+    }
+
+    /**
+     * Tests to watch a configuration file in a jar. In this case the jar file
+     * itself should be monitored.
+     */
+    @Test
+    public void testFromJar() throws Exception
+    {
+        XMLConfiguration config = new XMLConfiguration();
+        // use some jar: URL
+        config.setURL(new URL("jar:" + new File("conf/resources.jar").getAbsoluteFile().toURI().toURL() + "!/test-jar.xml"));
+        FileChangedReloadingStrategy strategy = new FileChangedReloadingStrategy();
+        config.setReloadingStrategy(strategy);
+        File file = strategy.getFile();
+        assertNotNull("Strategy's file is null", file);
+        assertEquals("Strategy does not monitor the jar file", "resources.jar", file.getName());
+    }
+
+    /**
+     * Tests calling reloadingRequired() multiple times before a reload actually
+     * happens. This test is related to CONFIGURATION-302.
+     */
+    @Test
+    public void testReloadingRequiredMultipleTimes()
+            throws ConfigurationException
+    {
+        FileChangedReloadingStrategy strategy = new FileChangedReloadingStrategy()
+        {
+            @Override
+            protected boolean hasChanged()
+            {
+                // signal always a change
+                return true;
+            }
+        };
+        strategy.setRefreshDelay(100000);
+        PropertiesConfiguration config = new PropertiesConfiguration(TEST_FILE);
+        config.setReloadingStrategy(strategy);
+        assertTrue("Reloading not required", strategy.reloadingRequired());
+        assertTrue("Reloading no more required", strategy.reloadingRequired());
+        strategy.reloadingPerformed();
+        assertFalse("Reloading still required", strategy.reloadingRequired());
+    }
+
+    @Test
+    public void testFileDeletion() throws Exception
+    {
+        Logger logger = Logger.getLogger(FileChangedReloadingStrategy.class.getName());
+        Layout layout = new PatternLayout("%p - %m%n");
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        Appender appender = new WriterAppender(layout, os);
+        logger.addAppender(appender);
+        logger.setLevel(Level.WARN);
+        logger.setAdditivity(false);
+        // create a new configuration
+        File file = new File("target/testReload.properties");
+
+        if (file.exists())
+        {
+            file.delete();
+        }
+
+        // create the configuration file
+        FileWriter out = new FileWriter(file);
+        out.write("string=value1");
+        out.flush();
+        out.close();
+
+        // load the configuration
+        PropertiesConfiguration config = new PropertiesConfiguration("target/testReload.properties");
+        FileChangedReloadingStrategy strategy = new FileChangedReloadingStrategy();
+        strategy.setRefreshDelay(500);
+        config.setReloadingStrategy(strategy);
+        assertEquals("Initial value", "value1", config.getString("string"));
+
+        Thread.sleep(2000);
+
+        // Delete the file.
+        file.delete();
+        //Old value should still be returned.
+        assertEquals("Initial value", "value1", config.getString("string"));
+        logger.removeAppender(appender);
+        String str = os.toString();
+        //System.out.println(str);
+        assertTrue("No error was logged", str != null && str.length() > 0);
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/reloading/TestManagedReloadingStrategy.java b/src/test/java/org/apache/commons/configuration/reloading/TestManagedReloadingStrategy.java
new file mode 100644
index 0000000..3411c3b
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/reloading/TestManagedReloadingStrategy.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.reloading;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.FileWriter;
+
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.junit.Test;
+
+/**
+ * Test case for the ManagedReloadingStrategy class.
+ *
+ * @author Nicolas De loof
+ * @version $Id: TestManagedReloadingStrategy.java 1225908 2011-12-30 20:04:48Z oheger $
+ */
+public class TestManagedReloadingStrategy
+{
+    @Test
+    public void testManagedRefresh() throws Exception
+    {
+        File file = new File("target/testReload.properties");
+        if (file.exists())
+        {
+            file.delete();
+        }
+        // create the configuration file
+        FileWriter out = new FileWriter(file);
+        out.write("string=value1");
+        out.flush();
+        out.close();
+
+        // load the configuration
+        PropertiesConfiguration config = new PropertiesConfiguration("target/testReload.properties");
+        ManagedReloadingStrategy strategy = new ManagedReloadingStrategy();
+        config.setReloadingStrategy(strategy);
+        assertEquals("Initial value", "value1", config.getString("string"));
+
+        // change the file
+        out = new FileWriter(file);
+        out.write("string=value2");
+        out.flush();
+        out.close();
+
+        // test the automatic reloading
+        assertEquals("No automatic reloading", "value1", config.getString("string"));
+        strategy.refresh();
+        assertEquals("Modified value with enabled reloading", "value2", config.getString("string"));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/configuration/reloading/TestVFSFileChangedReloadingStrategy.java b/src/test/java/org/apache/commons/configuration/reloading/TestVFSFileChangedReloadingStrategy.java
new file mode 100644
index 0000000..df227b7
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/reloading/TestVFSFileChangedReloadingStrategy.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.reloading;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileWriter;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.FileSystem;
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.commons.configuration.VFSFileSystem;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test case for the VFSFileMonitorReloadingStrategy class.
+ *
+ * @author Ralph Goers
+ * @version $Id: TestVFSFileChangedReloadingStrategy.java 1225909 2011-12-30 20:09:00Z oheger $
+ */
+public class TestVFSFileChangedReloadingStrategy
+{
+    /** Constant for the name of a test properties file.*/
+    private static final String TEST_FILE = "test.properties";
+
+    @Before
+    public void setUp() throws Exception
+    {
+        FileSystem.setDefaultFileSystem(new VFSFileSystem());
+    }
+
+    @After
+    public void tearDown() throws Exception
+    {
+        FileSystem.resetDefaultFileSystem();
+    }
+
+    @Test
+    public void testAutomaticReloading() throws Exception
+    {
+        // create a new configuration
+        File file = new File("target/testReload.properties");
+
+        if (file.exists())
+        {
+            file.delete();
+        }
+
+        // create the configuration file
+        FileWriter out = new FileWriter(file);
+        out.write("string=value1");
+        out.flush();
+        out.close();
+
+        // load the configuration
+        PropertiesConfiguration config = new PropertiesConfiguration("target/testReload.properties");
+        VFSFileChangedReloadingStrategy strategy = new VFSFileChangedReloadingStrategy();
+        strategy.setRefreshDelay(500);
+        config.setReloadingStrategy(strategy);
+        assertEquals("Initial value", "value1", config.getString("string"));
+
+        Thread.sleep(2000);
+
+        // change the file
+        out = new FileWriter(file);
+        out.write("string=value2");
+        out.flush();
+        out.close();
+
+        // test the automatic reloading
+        assertEquals("Modified value with enabled reloading", "value2", config.getString("string"));
+    }
+
+    @Test
+    public void testNewFileReloading() throws Exception
+    {
+        // create a new configuration
+        File file = new File("target/testReload.properties");
+
+        if (file.exists())
+        {
+            file.delete();
+        }
+
+        PropertiesConfiguration config = new PropertiesConfiguration();
+        config.setFile(file);
+        VFSFileChangedReloadingStrategy strategy = new VFSFileChangedReloadingStrategy();
+        strategy.setRefreshDelay(500);
+        config.setReloadingStrategy(strategy);
+
+        assertNull("Initial value", config.getString("string"));
+
+        // change the file
+        FileWriter out = new FileWriter(file);
+        out.write("string=value1");
+        out.flush();
+        out.close();
+
+        Thread.sleep(2000);
+
+        // test the automatic reloading
+        assertEquals("Modified value with enabled reloading", "value1", config.getString("string"));
+    }
+
+    @Test
+    public void testGetRefreshDelay() throws Exception
+    {
+        VFSFileChangedReloadingStrategy strategy = new VFSFileChangedReloadingStrategy();
+        strategy.setRefreshDelay(500);
+        assertEquals("refresh delay", 500, strategy.getRefreshDelay());
+    }
+
+    /**
+     * Tests calling reloadingRequired() multiple times before a reload actually
+     * happens. This test is related to CONFIGURATION-302.
+     */
+    @Test
+    public void testReloadingRequiredMultipleTimes()
+            throws ConfigurationException
+    {
+        VFSFileChangedReloadingStrategy strategy = new VFSFileChangedReloadingStrategy()
+        {
+            @Override
+            protected boolean hasChanged()
+            {
+                // signal always a change
+                return true;
+            }
+        };
+        strategy.setRefreshDelay(100000);
+        PropertiesConfiguration config = new PropertiesConfiguration(TEST_FILE);
+        config.setReloadingStrategy(strategy);
+        assertTrue("Reloading not required", strategy.reloadingRequired());
+        assertTrue("Reloading no more required", strategy.reloadingRequired());
+        strategy.reloadingPerformed();
+        assertFalse("Reloading still required", strategy.reloadingRequired());
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/commons/configuration/test/HsqlDB.java b/src/test/java/org/apache/commons/configuration/test/HsqlDB.java
new file mode 100644
index 0000000..9e37ab3
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/test/HsqlDB.java
@@ -0,0 +1,123 @@
+package org.apache.commons.configuration.test;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.FileReader;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Stolen from Turbine
+ *
+ * @author <a href="mailto:hps at intermeta.de">Henning P. Schmiedehausen</a>
+ * @version $Id: HsqlDB.java 439648 2006-09-02 20:42:10Z oheger $
+ */
+
+public class HsqlDB
+{
+    private Connection connection = null;
+    private static Log log = LogFactory.getLog(HsqlDB.class);
+
+    public HsqlDB(String uri, String databaseDriver, String loadFile)
+            throws Exception
+    {
+        Class.forName(databaseDriver);
+
+        this.connection = DriverManager.getConnection(uri, "sa", "");
+
+            if (StringUtils.isNotEmpty(loadFile))
+            {
+                loadSqlFile(loadFile);
+            }
+        this.connection.commit();
+    }
+
+    public Connection getConnection()
+    {
+        return connection;
+    }
+
+    public void close()
+    {
+        try
+        {
+            connection.close();
+        }
+        catch (Exception e)
+        {
+        }
+    }
+
+    private void loadSqlFile(String fileName)
+            throws Exception
+    {
+        Statement statement = null;
+        try
+        {
+            statement = connection.createStatement();
+            String commands = getFileContents(fileName);
+
+            for (int targetPos = commands.indexOf(';'); targetPos > -1; targetPos = commands.indexOf(';'))
+            {
+                String cmd = commands.substring(0, targetPos + 1);
+                try
+                {
+                    statement.execute(cmd);
+                }
+                catch (SQLException sqle)
+                {
+                    log.warn("Statement: " + cmd + ": " + sqle.getMessage());
+                }
+                
+                commands = commands.substring(targetPos + 2);
+            }
+        }
+        finally
+        {
+            if (statement != null)
+            {
+                statement.close();
+            }
+        }
+    }
+
+    private String getFileContents(String fileName)
+            throws Exception
+    {
+        FileReader fr = new FileReader(fileName);
+
+        char fileBuf[]  = new char[1024];
+        StringBuffer sb = new StringBuffer(1000);
+        int res = -1;
+
+        while ((res = fr.read(fileBuf, 0, 1024)) > -1)
+        {
+            sb.append(fileBuf, 0, res);
+        }
+        fr.close();
+        return sb.toString();
+    }
+}
+
diff --git a/src/test/java/org/apache/commons/configuration/tree/AbstractCombinerTest.java b/src/test/java/org/apache/commons/configuration/tree/AbstractCombinerTest.java
new file mode 100644
index 0000000..c5291e4
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/tree/AbstractCombinerTest.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+
+import org.apache.commons.configuration.ConfigurationAssert;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.HierarchicalConfiguration;
+import org.apache.commons.configuration.XMLConfiguration;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * A base class for testing combiner implementations. This base class provides
+ * some functionality for loading the test configurations, which are to be
+ * combined. Concrete sub classes only need to create the correct combiner
+ * object.
+ *
+ * @version $Id: AbstractCombinerTest.java 1225911 2011-12-30 20:19:10Z oheger $
+ */
+public abstract class AbstractCombinerTest
+{
+    /** Constant for the first test configuration. */
+    static File CONF1 = ConfigurationAssert.getTestFile("testcombine1.xml");
+
+    /** Constant for the second test configuration. */
+    static File CONF2 = ConfigurationAssert.getTestFile("testcombine2.xml");
+
+    /** The combiner to be tested. */
+    protected NodeCombiner combiner;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        combiner = createCombiner();
+    }
+
+    /**
+     * Creates the combiner to be tested. This method is called by
+     * <code>setUp()</code>. It must be implemented in concrete sub classes.
+     *
+     * @return the combiner to be tested
+     */
+    protected abstract NodeCombiner createCombiner();
+
+    /**
+     * Constructs a union configuration based on the source configurations.
+     *
+     * @return the union configuration
+     * @throws ConfigurationException if an error occurs
+     */
+    protected HierarchicalConfiguration createCombinedConfiguration()
+            throws ConfigurationException
+    {
+        XMLConfiguration conf1 = new XMLConfiguration(CONF1);
+        XMLConfiguration conf2 = new XMLConfiguration(CONF2);
+        ConfigurationNode cn = combiner.combine(conf1.getRootNode(), conf2
+                .getRootNode());
+
+        HierarchicalConfiguration result = new HierarchicalConfiguration();
+        result.setRootNode(cn);
+
+        return result;
+    }
+
+    /**
+     * Tests a newly created combiner.
+     */
+    @Test
+    public void testInit()
+    {
+        assertTrue("Combiner has list nodes", combiner.getListNodes().isEmpty());
+        assertFalse("Node is list node", combiner
+                .isListNode(new DefaultConfigurationNode("test")));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/configuration/tree/TestDefaultConfigurationKey.java b/src/test/java/org/apache/commons/configuration/tree/TestDefaultConfigurationKey.java
new file mode 100644
index 0000000..85dcdaf
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/tree/TestDefaultConfigurationKey.java
@@ -0,0 +1,516 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.NoSuchElementException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for DefaultConfigurationKey.
+ *
+ * @author Oliver Heger
+ * @version $Id: TestDefaultConfigurationKey.java 1225914 2011-12-30 20:26:36Z oheger $
+ */
+public class TestDefaultConfigurationKey
+{
+    /** Constant for a test key. */
+    private static final String TESTPROPS = "tables.table(0).fields.field(1)";
+
+    /** Constant for a test attribute key. */
+    private static final String TESTATTR = "[@dataType]";
+
+    /** Constant for a complex attribute key. */
+    private static final String TESTKEY = TESTPROPS + TESTATTR;
+
+    /** Stores the expression engine of the key to test. */
+    DefaultExpressionEngine expressionEngine;
+
+    /** Stores the object to be tested. */
+    DefaultConfigurationKey key;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        expressionEngine = new DefaultExpressionEngine();
+        key = new DefaultConfigurationKey(expressionEngine);
+    }
+
+    /**
+     * Tests setting the expression engine to null. This should not be allowed.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetNullExpressionEngine()
+    {
+        key.setExpressionEngine(null);
+    }
+
+    /**
+     * Tests the isAttributeKey() method with several keys.
+     */
+    @Test
+    public void testIsAttributeKey()
+    {
+        assertTrue("Attribute key not detected", key.isAttributeKey(TESTATTR));
+        assertFalse("Property key considered as attribute", key
+                .isAttributeKey(TESTPROPS));
+        assertFalse("Null key considered as attribute", key
+                .isAttributeKey(null));
+    }
+
+    /**
+     * Tests if attribute keys are correctly detected if no end markers are set.
+     * (In this test case we use the same delimiter for attributes as for simple
+     * properties.)
+     */
+    @Test
+    public void testIsAttributeKeyWithoutEndMarkers()
+    {
+        expressionEngine.setAttributeEnd(null);
+        expressionEngine
+                .setAttributeStart(DefaultExpressionEngine.DEFAULT_PROPERTY_DELIMITER);
+        assertTrue(
+                "Attribute key not detected",
+                key
+                        .isAttributeKey(DefaultExpressionEngine.DEFAULT_PROPERTY_DELIMITER
+                                + "test"));
+        assertFalse("Property key considered as attribute key", key
+                .isAttributeKey(TESTATTR));
+    }
+
+    /**
+     * Tests removing leading delimiters.
+     */
+    @Test
+    public void testTrimLeft()
+    {
+        assertEquals("Key was not left trimmed", "test.", key
+                .trimLeft(".test."));
+        assertEquals("Too much left trimming", "..test.", key
+                .trimLeft("..test."));
+    }
+
+    /**
+     * Tests removing trailing delimiters.
+     */
+    @Test
+    public void testTrimRight()
+    {
+        assertEquals("Key was not right trimmed", ".test", key
+                .trimRight(".test."));
+        assertEquals("Too much right trimming", ".test..", key
+                .trimRight(".test.."));
+    }
+
+    /**
+     * Tests removing delimiters.
+     */
+    @Test
+    public void testTrim()
+    {
+        assertEquals("Key was not trimmed", "test", key.trim(".test."));
+        assertEquals("Null key could not be processed", "", key.trim(null));
+        assertEquals("Delimiter could not be processed", "", key
+                .trim(DefaultExpressionEngine.DEFAULT_PROPERTY_DELIMITER));
+    }
+
+    /**
+     * Tests appending keys.
+     */
+    @Test
+    public void testAppend()
+    {
+        key.append("tables").append("table(0).");
+        key.append("fields.").append("field(1)");
+        key.append(null).append(TESTATTR);
+        assertEquals("Wrong key", TESTKEY, key.toString());
+    }
+
+    /**
+     * Tests appending keys that contain delimiters.
+     */
+    @Test
+    public void testAppendDelimiters()
+    {
+        key.append("key..").append("test").append(".");
+        key.append(".more").append("..tests");
+        assertEquals("Wrong key", "key...test.more...tests", key.toString());
+    }
+
+    /**
+     * Tests appending keys that contain delimiters when no escpaped delimiter
+     * is defined.
+     */
+    @Test
+    public void testAppendDelimitersWithoutEscaping()
+    {
+        expressionEngine.setEscapedDelimiter(null);
+        key.append("key.......").append("test").append(".");
+        key.append(".more").append("..tests");
+        assertEquals("Wrong constructed key", "key.test.more.tests", key
+                .toString());
+    }
+
+    /**
+     * Tests calling append with the escape flag.
+     */
+    @Test
+    public void testAppendWithEscapeFlag()
+    {
+        key.append(".key.test.", true);
+        key.append(".more").append(".tests", true);
+        assertEquals("Wrong constructed key", "..key..test...more...tests", key
+                .toString());
+    }
+
+    /**
+     * Tests constructing keys for attributes.
+     */
+    @Test
+    public void testConstructAttributeKey()
+    {
+        assertEquals("Wrong attribute key", TESTATTR, key
+                .constructAttributeKey("dataType"));
+        assertEquals("Attribute key was incorrectly converted", TESTATTR, key
+                .constructAttributeKey(TESTATTR));
+        assertEquals("Null key could not be processed", "", key
+                .constructAttributeKey(null));
+    }
+
+    /**
+     * Tests constructing attribute keys when no end markers are defined. In
+     * this test case we use the property delimiter as attribute prefix.
+     */
+    @Test
+    public void testConstructAttributeKeyWithoutEndMarkers()
+    {
+        expressionEngine.setAttributeEnd(null);
+        expressionEngine.setAttributeStart(expressionEngine
+                .getPropertyDelimiter());
+        assertEquals("Wrong attribute key", ".test", key
+                .constructAttributeKey("test"));
+        assertEquals("Attribute key was incorrectly converted", ".test", key
+                .constructAttributeKey(".test"));
+    }
+
+    /**
+     * Tests appending attribute keys.
+     */
+    @Test
+    public void testAppendAttribute()
+    {
+        key.appendAttribute("dataType");
+        assertEquals("Attribute key not correctly appended", TESTATTR, key
+                .toString());
+    }
+
+    /**
+     * Tests appending an attribute key that is already decorated-
+     */
+    @Test
+    public void testAppendDecoratedAttributeKey()
+    {
+        key.appendAttribute(TESTATTR);
+        assertEquals("Decorated attribute key not correctly appended",
+                TESTATTR, key.toString());
+    }
+
+    /**
+     * Tests appending a null attribute key.
+     */
+    @Test
+    public void testAppendNullAttributeKey()
+    {
+        key.appendAttribute(null);
+        assertEquals("Null attribute key not correctly appended", "", key
+                .toString());
+    }
+
+    /**
+     * Tests appending an index to a key.
+     */
+    @Test
+    public void testAppendIndex()
+    {
+        key.append("test").appendIndex(42);
+        assertEquals("Index was not correctly appended", "test(42)", key
+                .toString());
+    }
+
+    /**
+     * Tests constructing a complex key by chaining multiple append operations.
+     */
+    @Test
+    public void testAppendComplexKey()
+    {
+        key.append("tables").append("table.").appendIndex(0);
+        key.append("fields.").append("field").appendIndex(1);
+        key.appendAttribute("dataType");
+        assertEquals("Wrong complex key", TESTKEY, key.toString());
+    }
+
+    /**
+     * Tests getting and setting the key's length.
+     */
+    @Test
+    public void testLength()
+    {
+        key.append(TESTPROPS);
+        assertEquals("Wrong length", TESTPROPS.length(), key.length());
+        key.appendAttribute("dataType");
+        assertEquals("Wrong length", TESTKEY.length(), key.length());
+        key.setLength(TESTPROPS.length());
+        assertEquals("Wrong length after shortening", TESTPROPS.length(), key
+                .length());
+        assertEquals("Wrong resulting key", TESTPROPS, key.toString());
+    }
+
+    /**
+     * Tests comparing configuration keys.
+     */
+    @Test
+    public void testEquals()
+    {
+        DefaultConfigurationKey k1 = new DefaultConfigurationKey(
+                expressionEngine, TESTKEY);
+        DefaultConfigurationKey k2 = new DefaultConfigurationKey(
+                expressionEngine, TESTKEY);
+        assertTrue("Keys are not equal", k1.equals(k2));
+        assertTrue("Not reflexiv", k2.equals(k1));
+        assertEquals("Hash codes not equal", k1.hashCode(), k2.hashCode());
+        k2.append("anotherPart");
+        assertFalse("Keys considered equal", k1.equals(k2));
+        assertFalse("Keys considered equal", k2.equals(k1));
+        assertFalse("Key equals null key", k1.equals(null));
+        assertTrue("Faild comparison with string", k1.equals(TESTKEY));
+    }
+
+    /**
+     * Tests determining an attribute key's name.
+     */
+    @Test
+    public void testAttributeName()
+    {
+        assertEquals("Plain key not detected", "test", key
+                .attributeName("test"));
+        assertEquals("Attribute markers not stripped", "dataType", key
+                .attributeName(TESTATTR));
+        assertNull("Null key not processed", key.attributeName(null));
+    }
+
+    /**
+     * Tests to iterate over a simple key.
+     */
+    @Test
+    public void testIterate()
+    {
+        key.append(TESTKEY);
+        DefaultConfigurationKey.KeyIterator it = key.iterator();
+        assertTrue("No key parts", it.hasNext());
+        assertEquals("Wrong key part", "tables", it.nextKey());
+        assertEquals("Wrong key part", "table", it.nextKey());
+        assertTrue("No index found", it.hasIndex());
+        assertEquals("Wrong index", 0, it.getIndex());
+        assertEquals("Wrong key part", "fields", it.nextKey());
+        assertFalse("Found an index", it.hasIndex());
+        assertEquals("Wrong key part", "field", it.nextKey(true));
+        assertEquals("Wrong index", 1, it.getIndex());
+        assertFalse("Found an attribute", it.isAttribute());
+        assertEquals("Wrong current key", "field", it.currentKey(true));
+        assertEquals("Wrong key part", "dataType", it.nextKey());
+        assertEquals("Wrong decorated key part", "[@dataType]", it
+                .currentKey(true));
+        assertTrue("Attribute not found", it.isAttribute());
+        assertFalse("Too many key parts", it.hasNext());
+        try
+        {
+            it.next();
+            fail("Could iterate over the iteration's end!");
+        }
+        catch (NoSuchElementException nex)
+        {
+            // ok
+        }
+    }
+
+    /**
+     * Tests an iteration where the remove() method is called. This is not
+     * supported.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testIterateWithRemove()
+    {
+        assertFalse(key.iterator().hasNext());
+        key.append("simple");
+        DefaultConfigurationKey.KeyIterator it = key.iterator();
+        assertTrue(it.hasNext());
+        assertEquals("simple", it.next());
+        it.remove();
+    }
+
+    /**
+     * Tests iterating over some funny keys.
+     */
+    @Test
+    public void testIterateStrangeKeys()
+    {
+        key = new DefaultConfigurationKey(expressionEngine, "key.");
+        DefaultConfigurationKey.KeyIterator it = key.iterator();
+        assertTrue("Too few key parts", it.hasNext());
+        assertEquals("Wrong key part", "key", it.next());
+        assertFalse("Too many key parts", it.hasNext());
+
+        key = new DefaultConfigurationKey(expressionEngine, ".");
+        it = key.iterator();
+        assertFalse("Simple delimiter key has more parts", it.hasNext());
+
+        key = new DefaultConfigurationKey(expressionEngine,
+                "key().index()undefined(0).test");
+        it = key.iterator();
+        assertEquals("Wrong first part", "key()", it.next());
+        assertFalse("Index detected in first part", it.hasIndex());
+        assertEquals("Wrong second part", "index()undefined", it.nextKey(false));
+        assertTrue("No index detected in second part", it.hasIndex());
+        assertEquals("Wrong index value", 0, it.getIndex());
+    }
+
+    /**
+     * Tests iterating over keys with escaped delimiters.
+     */
+    @Test
+    public void testIterateEscapedDelimiters()
+    {
+        key.append("my..elem");
+        key.append("trailing..dot..");
+        key.append(".strange");
+        assertEquals("my..elem.trailing..dot...strange", key.toString());
+        DefaultConfigurationKey.KeyIterator kit = key.iterator();
+        assertEquals("Wrong first part", "my.elem", kit.nextKey());
+        assertEquals("Wrong second part", "trailing.dot.", kit.nextKey());
+        assertEquals("Wrong third part", "strange", kit.nextKey());
+        assertFalse("Too many parts", kit.hasNext());
+    }
+
+    /**
+     * Tests iterating over keys when a different escaped delimiter is used.
+     */
+    @Test
+    public void testIterateAlternativeEscapeDelimiter()
+    {
+        expressionEngine.setEscapedDelimiter("\\.");
+        key.append("\\.my\\.elem");
+        key.append("trailing\\.dot\\.");
+        key.append(".strange");
+        assertEquals("\\.my\\.elem.trailing\\.dot\\..strange", key.toString());
+        DefaultConfigurationKey.KeyIterator kit = key.iterator();
+        assertEquals("Wrong first part", ".my.elem", kit.nextKey());
+        assertEquals("Wrong second part", "trailing.dot.", kit.nextKey());
+        assertEquals("Wrong third part", "strange", kit.nextKey());
+        assertFalse("Too many parts", kit.hasNext());
+    }
+
+    /**
+     * Tests iterating when no escape delimiter is defined.
+     */
+    @Test
+    public void testIterateWithoutEscapeDelimiter()
+    {
+        expressionEngine.setEscapedDelimiter(null);
+        key.append("..my..elem.trailing..dot...strange");
+        assertEquals("Wrong key", "my..elem.trailing..dot...strange", key
+                .toString());
+        DefaultConfigurationKey.KeyIterator kit = key.iterator();
+        final String[] parts =
+        { "my", "elem", "trailing", "dot", "strange"};
+        for (int i = 0; i < parts.length; i++)
+        {
+            assertEquals("Wrong key part " + i, parts[i], kit.next());
+        }
+        assertFalse("Too many parts", kit.hasNext());
+    }
+
+    /**
+     * Tests whether a key with brackets in it can be iterated over.
+     */
+    @Test
+    public void testIterateWithBrackets()
+    {
+        key.append("directory.platform(x86).path");
+        DefaultConfigurationKey.KeyIterator kit = key.iterator();
+        String part = kit.nextKey();
+        assertEquals("Wrong part 1", "directory", part);
+        assertFalse("Has index 1", kit.hasIndex());
+        part = kit.nextKey();
+        assertEquals("Wrong part 2", "platform(x86)", part);
+        assertFalse("Has index 2", kit.hasIndex());
+        part = kit.nextKey();
+        assertEquals("Wrong part 3", "path", part);
+        assertFalse("Has index 3", kit.hasIndex());
+        assertFalse("Too many elements", kit.hasNext());
+    }
+
+    /**
+     * Tests iterating over an attribute key that has an index.
+     */
+    @Test
+    public void testAttributeKeyWithIndex()
+    {
+        key.append(TESTATTR);
+        key.appendIndex(0);
+        assertEquals("Wrong attribute key with index", TESTATTR + "(0)", key
+                .toString());
+
+        DefaultConfigurationKey.KeyIterator it = key.iterator();
+        assertTrue("No first element", it.hasNext());
+        it.next();
+        assertTrue("Index not found", it.hasIndex());
+        assertEquals("Incorrect index", 0, it.getIndex());
+        assertTrue("Attribute not found", it.isAttribute());
+        assertEquals("Wrong plain key", "dataType", it.currentKey(false));
+        assertEquals("Wrong decorated key", TESTATTR, it.currentKey(true));
+    }
+
+    /**
+     * Tests iteration when the attribute markers equals the property delimiter.
+     */
+    @Test
+    public void testIterateAttributeEqualsPropertyDelimiter()
+    {
+        expressionEngine.setAttributeEnd(null);
+        expressionEngine.setAttributeStart(expressionEngine
+                .getPropertyDelimiter());
+        key.append("this.isa.key");
+        DefaultConfigurationKey.KeyIterator kit = key.iterator();
+        assertEquals("Wrong first key part", "this", kit.next());
+        assertFalse("First part is an attribute", kit.isAttribute());
+        assertTrue("First part is not a property key", kit.isPropertyKey());
+        assertEquals("Wrong second key part", "isa", kit.next());
+        assertFalse("Second part is an attribute", kit.isAttribute());
+        assertTrue("Second part is not a property key", kit.isPropertyKey());
+        assertEquals("Wrong third key part", "key", kit.next());
+        assertTrue("Third part is not an attribute", kit.isAttribute());
+        assertTrue("Third part is not a property key", kit.isPropertyKey());
+        assertEquals("Wrong decorated key part", "key", kit.currentKey(true));
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/tree/TestDefaultConfigurationNode.java b/src/test/java/org/apache/commons/configuration/tree/TestDefaultConfigurationNode.java
new file mode 100644
index 0000000..b801566
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/tree/TestDefaultConfigurationNode.java
@@ -0,0 +1,507 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for DefaultConfigurationNode.
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: TestDefaultConfigurationNode.java 1225917 2011-12-30 20:42:09Z oheger $
+ */
+public class TestDefaultConfigurationNode
+{
+    /** Constant array for the field names. */
+    private static final String[] FIELD_NAMES =
+    { "UID", "NAME", "FIRSTNAME", "LASTLOGIN"};
+
+    /** Constant array for the field data types. */
+    private static final String[] FIELD_TYPES =
+    { "long", "string", "string", "date"};
+
+    /** Constant array for additional field attributes. */
+    private static final String[] FIELD_ATTRS =
+    { "primarykey,unique", "notnull", "notnull", null};
+
+    /** The node to be tested. */
+    DefaultConfigurationNode node;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        node = new DefaultConfigurationNode();
+        node.setName("table");
+        node.setReference("TestReference");
+        node.addAttribute(new DefaultConfigurationNode("type", "system"));
+        node.addChild(new DefaultConfigurationNode("name", "users"));
+
+        // Add nodes for the table's fields
+        for (int i = 0; i < FIELD_NAMES.length; i++)
+        {
+            DefaultConfigurationNode field = new DefaultConfigurationNode(
+                    "field");
+            field
+                    .addChild(new DefaultConfigurationNode("name",
+                            FIELD_NAMES[i]));
+            field.addAttribute(new DefaultConfigurationNode("type",
+                    FIELD_TYPES[i]));
+            if (FIELD_ATTRS[i] != null)
+            {
+                StringTokenizer tok = new StringTokenizer(FIELD_ATTRS[i], ", ");
+                while (tok.hasMoreTokens())
+                {
+                    field.addAttribute(new DefaultConfigurationNode(
+                            "attribute", tok.nextToken()));
+                }
+            }
+            node.addChild(field);
+        }
+    }
+
+    /**
+     * Tests a newly created, uninitialized node.
+     */
+    @Test
+    public void testNewNode()
+    {
+        node = new DefaultConfigurationNode();
+        assertNull("name is not null", node.getName());
+        assertNull("value is not null", node.getValue());
+        assertNull("reference is not null", node.getReference());
+        assertTrue("Children are not empty", node.getChildren().isEmpty());
+        assertTrue("Named children are not empty", node.getChildren("test")
+                .isEmpty());
+        assertEquals("Children cound is not 0", 0, node.getChildrenCount());
+        assertEquals("Named children count is not 0", 0, node
+                .getChildrenCount("test"));
+        assertTrue("Attributes are not empty", node.getAttributes().isEmpty());
+        assertTrue("Named attributes are not empty", node.getAttributes("test")
+                .isEmpty());
+        assertNull("Node has a parent", node.getParentNode());
+        assertFalse("Node is defined", node.isDefined());
+    }
+
+    /**
+     * Tries to access an attribute using an invalid index.
+     */
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testGetAttributeNonExisting()
+    {
+        node = new DefaultConfigurationNode();
+        node.getAttribute(0);
+    }
+
+    /**
+     * Tests accessing a node's reference.
+     */
+    @Test
+    public void testGetReference()
+    {
+        assertEquals("Reference was not stored", "TestReference", node
+                .getReference());
+    }
+
+    /**
+     * Tests accessing the node's children.
+     */
+    @Test
+    public void testGetChildren()
+    {
+        assertEquals("Number of children incorrect", FIELD_NAMES.length + 1,
+                node.getChildrenCount());
+        List<ConfigurationNode> children = node.getChildren();
+        Iterator<ConfigurationNode> it = children.iterator();
+        DefaultConfigurationNode child = (DefaultConfigurationNode) it.next();
+        assertEquals("Wrong node", "name", child.getName());
+        checkFieldNodes(it);
+    }
+
+    /**
+     * Tests accessing the node's children by name.
+     */
+    @Test
+    public void testGetChildrenByName()
+    {
+        List<ConfigurationNode> children = node.getChildren("field");
+        assertEquals("Incorrect number of child nodes", FIELD_NAMES.length,
+                children.size());
+        assertEquals("Incorrect result of getChildrenCount()",
+                FIELD_NAMES.length, node.getChildrenCount("field"));
+        checkFieldNodes(children.iterator());
+        assertTrue("Found non existing nodes", node.getChildren("test")
+                .isEmpty());
+        assertEquals("Wrong children list for null", node.getChildren(), node
+                .getChildren(null));
+    }
+
+    /**
+     * Tests adding a new child node.
+     */
+    @Test
+    public void testAddChild()
+    {
+        int cnt = node.getChildrenCount();
+        DefaultConfigurationNode ndNew = new DefaultConfigurationNode("test",
+                "xyz");
+        node.addChild(ndNew);
+        assertEquals("New node was not added", cnt + 1, node.getChildrenCount());
+        List<ConfigurationNode> children = node.getChildren();
+        assertEquals("Incorrect number of children", node.getChildrenCount(),
+                children.size());
+        assertSame("Node was not added to end", ndNew, children.get(cnt));
+        assertEquals("Incorrect number of named children", 1, node
+                .getChildrenCount(ndNew.getName()));
+        assertFalse("Child is an attribute", ndNew.isAttribute());
+        assertSame("Parent was not set", node, ndNew.getParentNode());
+    }
+
+    /**
+     * Tries to add a null child node.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testAddChildNull()
+    {
+        node.addChild(null);
+    }
+
+    /**
+     * Tries to add a node without a name.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testAddUndefinedChild()
+    {
+        node.addChild(new DefaultConfigurationNode());
+    }
+
+    /**
+     * Tests removing a child node.
+     */
+    @Test
+    public void testRemoveChild()
+    {
+        DefaultConfigurationNode child = (DefaultConfigurationNode) node
+                .getChildren().get(3);
+        int cnt = node.getChildrenCount();
+        node.removeChild(child);
+        assertEquals("Child was not removed", cnt - 1, node.getChildrenCount());
+        for (ConfigurationNode nd : node.getChildren())
+        {
+            assertNotSame("Found removed node", child, nd);
+        }
+        assertNull("Parent reference was not removed", child.getParentNode());
+    }
+
+    /**
+     * Tests removing a child node that does not belong to this node.
+     */
+    @Test
+    public void testRemoveNonExistingChild()
+    {
+        int cnt = node.getChildrenCount();
+        node.removeChild(new DefaultConfigurationNode("test"));
+        node.removeChild(new DefaultConfigurationNode());
+        node.removeChild((ConfigurationNode) null);
+        node.removeChild("non existing child node");
+        node.removeChild((String) null);
+        assertEquals("Children were changed", cnt, node.getChildrenCount());
+    }
+
+    /**
+     * Tests removing children by their name.
+     */
+    @Test
+    public void testRemoveChildByName()
+    {
+        int cnt = node.getChildrenCount();
+        node.removeChild("name");
+        assertEquals("Child was not removed", cnt - 1, node.getChildrenCount());
+        assertEquals("Still found name child", 0, node.getChildrenCount("name"));
+        node.removeChild("field");
+        assertEquals("Still remaining nodes", 0, node.getChildrenCount());
+    }
+
+    /**
+     * Tests removing all children at once.
+     */
+    @Test
+    public void testRemoveChildren()
+    {
+        node.removeChildren();
+        assertEquals("Children count is not 0", 0, node.getChildrenCount());
+        assertTrue("Children are not empty", node.getChildren().isEmpty());
+    }
+
+    /**
+     * Tests accessing a child by its index.
+     */
+    @Test
+    public void testGetChild()
+    {
+        ConfigurationNode child = node.getChild(2);
+        assertEquals("Wrong child returned", child, node.getChildren().get(2));
+    }
+
+    /**
+     * Tests accessing child nodes with invalid indices.
+     */
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testGetChildInvalidIndex()
+    {
+        node.getChild(4724);
+    }
+
+    /**
+     * Tests accessing the node's attributes.
+     */
+    @Test
+    public void testGetAttributes()
+    {
+        assertEquals("Number of attributes incorrect", 1, node
+                .getAttributeCount());
+        List<ConfigurationNode> attributes = node.getAttributes();
+        Iterator<ConfigurationNode> it = attributes.iterator();
+        DefaultConfigurationNode attr = (DefaultConfigurationNode) it.next();
+        assertEquals("Wrong node", "type", attr.getName());
+        assertFalse("More attributes", it.hasNext());
+    }
+
+    /**
+     * Tests accessing the node's attributes by name.
+     */
+    @Test
+    public void testGetAttributesByName()
+    {
+        assertEquals("Incorrect number of attributes", 1, node
+                .getAttributeCount("type"));
+        DefaultConfigurationNode field = (DefaultConfigurationNode) node
+                .getChildren().get(1);
+        assertEquals("Incorrect number of attributes", 2, field
+                .getAttributeCount("attribute"));
+        List<ConfigurationNode> attrs = field.getAttributes("attribute");
+        assertEquals("Wrong value", "primarykey",
+                ((DefaultConfigurationNode) attrs.get(0)).getValue());
+        assertEquals("Wrong value", "unique", ((DefaultConfigurationNode) attrs
+                .get(1)).getValue());
+    }
+
+    /**
+     * Tests adding a new attribute node.
+     */
+    @Test
+    public void testAddAttribute()
+    {
+        int cnt = node.getAttributeCount();
+        DefaultConfigurationNode ndNew = new DefaultConfigurationNode("test",
+                "xyz");
+        node.addAttribute(ndNew);
+        assertEquals("New node was not added", cnt + 1, node
+                .getAttributeCount());
+        List<ConfigurationNode> attrs = node.getAttributes();
+        assertEquals("Incorrect number of attributes",
+                node.getAttributeCount(), attrs.size());
+        assertSame("Node was not added to end", ndNew, attrs.get(cnt));
+        assertEquals("Incorrect number of named attributes", 1, node
+                .getAttributeCount(ndNew.getName()));
+        assertTrue("Child is no attribute", ndNew.isAttribute());
+        assertSame("Parent was not set", node, ndNew.getParentNode());
+    }
+
+    /**
+     * Tests removing an attribute node.
+     */
+    @Test
+    public void testRemoveAttribute()
+    {
+        DefaultConfigurationNode attr = (DefaultConfigurationNode) node
+                .getAttributes().get(0);
+        int cnt = node.getAttributeCount();
+        node.removeAttribute(attr);
+        assertEquals("Attribute was not removed", cnt - 1, node
+                .getAttributeCount());
+        for (ConfigurationNode nd : node.getAttributes())
+        {
+            assertNotSame("Found removed node", attr, nd);
+        }
+        assertNull("Parent reference was not removed", attr.getParentNode());
+    }
+
+    /**
+     * Tests removing attributes by their names.
+     */
+    @Test
+    public void testRemoveAttributeByName()
+    {
+        ConfigurationNode field = node.getChild(1);
+        assertEquals("Incorrect number of attributes", 3, field
+                .getAttributeCount());
+        field.removeAttribute("attribute");
+        assertEquals("Not all nodes removed", 1, field.getAttributeCount());
+        assertTrue("Remaining attributes", field.getAttributes("attribute")
+                .isEmpty());
+        field.removeAttribute("type");
+        assertEquals("Remaining attributes", 0, field.getAttributeCount());
+    }
+
+    /**
+     * Tests removing all attributes.
+     */
+    @Test
+    public void testRemoveAttributes()
+    {
+        node.removeAttributes();
+        assertEquals("Not all attributes removed", 0, node.getAttributeCount());
+        assertTrue("Attributes not empty", node.getAttributes().isEmpty());
+    }
+
+    /**
+     * Tests changing a node's attribute state.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testChangeAttributeState()
+    {
+        ConfigurationNode attr = node.getAttribute(0);
+        attr.setAttribute(false);
+    }
+
+    /**
+     * Tests the visit() method using a simple visitor.
+     */
+    @Test
+    public void testVisit()
+    {
+        CountNodeVisitor visitor = new CountNodeVisitor();
+        node.visit(visitor);
+        assertEquals("Not all nodes visited", 19, visitor.beforeCalls);
+        assertEquals("Different number of before and after calls",
+                visitor.beforeCalls, visitor.afterCalls);
+    }
+
+    /**
+     * Tests the visit() method with a visitor that terminates the visit
+     * process.
+     */
+    @Test
+    public void testVisitWithTerminate()
+    {
+        CountNodeVisitor visitor = new CountNodeVisitor(10);
+        node.visit(visitor);
+        assertEquals("Incorrect number of nodes visited", visitor.maxCalls,
+                visitor.beforeCalls);
+        assertEquals("Different number of before and after calls",
+                visitor.beforeCalls, visitor.afterCalls);
+    }
+
+    /**
+     * Tests the visit() method when null is passed in. This should throw an
+     * exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testVisitWithNullVisitor()
+    {
+        node.visit(null);
+    }
+
+    /**
+     * Tests cloning a node.
+     */
+    @Test
+    public void testClone()
+    {
+        node.setValue("TestValue");
+        DefaultConfigurationNode clone = (DefaultConfigurationNode) node.clone();
+        assertEquals("Value not cloned", "TestValue", clone.getValue());
+        assertEquals("Name not cloned", "table", clone.getName());
+        assertEquals("Reference not cloned", "TestReference", clone.getReference());
+        assertEquals("Children were cloned", 0, clone.getChildrenCount());
+        assertEquals("Attributes were cloned", 0, clone.getAttributeCount());
+    }
+
+    /**
+     * Helper method for checking the child nodes of type "field".
+     *
+     * @param itFields the iterator with the child nodes
+     */
+    private void checkFieldNodes(Iterator<ConfigurationNode> itFields)
+    {
+        for (int i = 0; i < FIELD_NAMES.length; i++)
+        {
+            DefaultConfigurationNode child = (DefaultConfigurationNode) itFields
+                    .next();
+            assertEquals("Wrong node", "field", child.getName());
+            List<ConfigurationNode> nameNodes = child.getChildren("name");
+            assertEquals("Wrong number of name nodes", 1, nameNodes.size());
+            DefaultConfigurationNode nameNode = (DefaultConfigurationNode) nameNodes
+                    .get(0);
+            assertEquals("Wrong field name", FIELD_NAMES[i], nameNode
+                    .getValue());
+        }
+    }
+
+    /**
+     * A test visitor implementation that is able to count the number of visits.
+     * It also supports a maximum number of visits to be set; if this number is
+     * reached, the <code>terminate()</code> method returns <b>true</b>.
+     */
+    public static class CountNodeVisitor implements ConfigurationNodeVisitor
+    {
+        public int beforeCalls;
+
+        public int afterCalls;
+
+        public int maxCalls;
+
+        public CountNodeVisitor()
+        {
+            this(Integer.MAX_VALUE);
+        }
+
+        public CountNodeVisitor(int maxNumberOfVisits)
+        {
+            maxCalls = maxNumberOfVisits;
+        }
+
+        public void visitBeforeChildren(ConfigurationNode node)
+        {
+            beforeCalls++;
+        }
+
+        public void visitAfterChildren(ConfigurationNode node)
+        {
+            afterCalls++;
+        }
+
+        public boolean terminate()
+        {
+            return beforeCalls >= maxCalls;
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/tree/TestDefaultExpressionEngine.java b/src/test/java/org/apache/commons/configuration/tree/TestDefaultExpressionEngine.java
new file mode 100644
index 0000000..33efd92
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/tree/TestDefaultExpressionEngine.java
@@ -0,0 +1,539 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for DefaultExpressionEngine.
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: TestDefaultExpressionEngine.java 1225918 2011-12-30 20:54:47Z oheger $
+ */
+public class TestDefaultExpressionEngine
+{
+    /** Stores the names of the test nodes representing tables. */
+    private static String[] tables =
+    { "users", "documents"};
+
+    /** Stores the types of the test table nodes. */
+    private static String[] tabTypes =
+    { "system", "application"};
+
+    /** Test data fields for the node hierarchy. */
+    private static String[][] fields =
+    {
+    { "uid", "uname", "firstName", "lastName", "email"},
+    { "docid", "name", "creationDate", "authorID", "version"}};
+
+    /** The object to be tested. */
+    DefaultExpressionEngine engine;
+
+    /** The root of a hierarchy with configuration nodes. */
+    ConfigurationNode root;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        root = setUpNodes();
+        engine = new DefaultExpressionEngine();
+    }
+
+    /**
+     * Tests some simple queries.
+     */
+    @Test
+    public void testQueryKeys()
+    {
+        checkKey("tables.table.name", "name", 2);
+        checkKey("tables.table.fields.field.name", "name", 10);
+        checkKey("tables.table[@type]", "type", 2);
+        checkKey("tables.table(0).fields.field.name", "name", 5);
+        checkKey("tables.table(1).fields.field.name", "name", 5);
+        checkKey("tables.table.fields.field(1).name", "name", 2);
+    }
+
+    /**
+     * Performs some queries and evaluates the values of the result nodes.
+     */
+    @Test
+    public void testQueryNodes()
+    {
+        for (int i = 0; i < tables.length; i++)
+        {
+            checkKeyValue("tables.table(" + i + ").name", "name", tables[i]);
+            checkKeyValue("tables.table(" + i + ")[@type]", "type", tabTypes[i]);
+
+            for (int j = 0; j < fields[i].length; j++)
+            {
+                checkKeyValue("tables.table(" + i + ").fields.field(" + j
+                        + ").name", "name", fields[i][j]);
+            }
+        }
+    }
+
+    /**
+     * Tests querying keys that do not exist.
+     */
+    @Test
+    public void testQueryNonExistingKeys()
+    {
+        checkKey("tables.tablespace.name", null, 0);
+        checkKey("tables.table(2).name", null, 0);
+        checkKey("a complete unknown key", null, 0);
+        checkKey("tables.table(0).fields.field(-1).name", null, 0);
+        checkKey("tables.table(0).fields.field(28).name", null, 0);
+        checkKey("tables.table(0).fields.field().name", null, 0);
+        checkKey("connection.settings.usr.name", null, 0);
+    }
+
+    /**
+     * Tests querying nodes whose names contain a delimiter.
+     */
+    @Test
+    public void testQueryEscapedKeys()
+    {
+        checkKeyValue("connection..settings.usr..name", "usr.name", "scott");
+        checkKeyValue("connection..settings.usr..pwd", "usr.pwd", "tiger");
+    }
+
+    /**
+     * Tests some queries when the same delimiter is used for properties and
+     * attributes.
+     */
+    @Test
+    public void testQueryAttributeEmulation()
+    {
+        engine.setAttributeEnd(null);
+        engine.setAttributeStart(engine.getPropertyDelimiter());
+        checkKeyValue("tables.table(0).name", "name", tables[0]);
+        checkKeyValue("tables.table(0).type", "type", tabTypes[0]);
+        checkKey("tables.table.type", "type", 2);
+    }
+
+    /**
+     * Tests accessing the root node.
+     */
+    @Test
+    public void testQueryRootNode()
+    {
+        List<ConfigurationNode> nodes = checkKey(null, null, 1);
+        assertSame("Root node not found", root, nodes.get(0));
+        nodes = checkKey("", null, 1);
+        assertSame("Root node not found", root, nodes.get(0));
+        checkKeyValue("[@test]", "test", "true");
+    }
+
+    /**
+     * Tests a different query syntax. Sets other strings for the typical tokens
+     * used by the expression engine.
+     */
+    @Test
+    public void testQueryAlternativeSyntax()
+    {
+        setUpAlternativeSyntax();
+        checkKeyValue("tables/table[1]/name", "name", tables[1]);
+        checkKeyValue("tables/table[0]@type", "type", tabTypes[0]);
+        checkKeyValue("@test", "test", "true");
+        checkKeyValue("connection.settings/usr.name", "usr.name", "scott");
+    }
+
+    /**
+     * Tests obtaining keys for nodes.
+     */
+    @Test
+    public void testNodeKey()
+    {
+        ConfigurationNode node = root.getChild(0);
+        assertEquals("Invalid name for descendant of root", "tables", engine
+                .nodeKey(node, ""));
+        assertEquals("Parent key not respected", "test.tables", engine.nodeKey(
+                node, "test"));
+        assertEquals("Full parent key not taken into account",
+                "a.full.parent.key.tables", engine.nodeKey(node,
+                        "a.full.parent.key"));
+    }
+
+    /**
+     * Tests obtaining keys when the root node is involved.
+     */
+    @Test
+    public void testNodeKeyWithRoot()
+    {
+        assertEquals("Wrong name for root noot", "", engine.nodeKey(root, null));
+        assertEquals("Null name not detected", "test", engine.nodeKey(root,
+                "test"));
+    }
+
+    /**
+     * Tests obtaining keys for attribute nodes.
+     */
+    @Test
+    public void testNodeKeyWithAttribute()
+    {
+        ConfigurationNode node = root.getChild(0).getChild(0).getAttribute(0);
+        assertEquals("Wrong attribute node", "type", node.getName());
+        assertEquals("Wrong attribute key", "tables.table[@type]", engine
+                .nodeKey(node, "tables.table"));
+        assertEquals("Wrong key for root attribute", "[@test]", engine.nodeKey(
+                root.getAttribute(0), ""));
+    }
+
+    /**
+     * Tests obtaining keys for nodes that contain the delimiter character.
+     */
+    @Test
+    public void testNodeKeyWithEscapedDelimiters()
+    {
+        ConfigurationNode node = root.getChild(1);
+        assertEquals("Wrong escaped key", "connection..settings", engine
+                .nodeKey(node, ""));
+        assertEquals("Wrong complex escaped key",
+                "connection..settings.usr..name", engine.nodeKey(node
+                        .getChild(0), engine.nodeKey(node, "")));
+    }
+
+    /**
+     * Tests obtaining node keys when a different syntax is set.
+     */
+    @Test
+    public void testNodeKeyWithAlternativeSyntax()
+    {
+        setUpAlternativeSyntax();
+        assertEquals("Wrong child key", "tables/table", engine.nodeKey(root
+                .getChild(0).getChild(0), "tables"));
+        assertEquals("Wrong attribute key", "@test", engine.nodeKey(root
+                .getAttribute(0), ""));
+
+        engine.setAttributeStart(engine.getPropertyDelimiter());
+        assertEquals("Wrong attribute key", "/test", engine.nodeKey(root
+                .getAttribute(0), ""));
+    }
+
+    /**
+     * Tests adding direct child nodes to the existing hierarchy.
+     */
+    @Test
+    public void testPrepareAddDirectly()
+    {
+        NodeAddData data = engine.prepareAdd(root, "newNode");
+        assertSame("Wrong parent node", root, data.getParent());
+        assertTrue("Path nodes available", data.getPathNodes().isEmpty());
+        assertEquals("Wrong name of new node", "newNode", data.getNewNodeName());
+        assertFalse("New node is an attribute", data.isAttribute());
+
+        data = engine.prepareAdd(root, "tables.table.fields.field.name");
+        assertEquals("Wrong name of new node", "name", data.getNewNodeName());
+        assertTrue("Path nodes available", data.getPathNodes().isEmpty());
+        assertEquals("Wrong parent node", "field", data.getParent().getName());
+        ConfigurationNode nd = data.getParent().getChild(0);
+        assertEquals("Field has no name node", "name", nd.getName());
+        assertEquals("Incorrect name", "version", nd.getValue());
+    }
+
+    /**
+     * Tests adding when indices are involved.
+     */
+    @Test
+    public void testPrepareAddWithIndex()
+    {
+        NodeAddData data = engine
+                .prepareAdd(root, "tables.table(0).tableSpace");
+        assertEquals("Wrong name of new node", "tableSpace", data
+                .getNewNodeName());
+        assertTrue("Path nodes available", data.getPathNodes().isEmpty());
+        assertEquals("Wrong type of parent node", "table", data.getParent()
+                .getName());
+        ConfigurationNode node = data.getParent().getChild(0);
+        assertEquals("Wrong table", tables[0], node.getValue());
+
+        data = engine.prepareAdd(root, "tables.table(1).fields.field(2).alias");
+        assertEquals("Wrong name of new node", "alias", data.getNewNodeName());
+        assertEquals("Wrong type of parent node", "field", data.getParent()
+                .getName());
+        assertEquals("Wrong field node", "creationDate", data.getParent()
+                .getChild(0).getValue());
+    }
+
+    /**
+     * Tests adding new attributes.
+     */
+    @Test
+    public void testPrepareAddAttribute()
+    {
+        NodeAddData data = engine.prepareAdd(root,
+                "tables.table(0)[@tableSpace]");
+        assertEquals("Wrong table node", tables[0], data.getParent()
+                .getChild(0).getValue());
+        assertEquals("Wrong name of new node", "tableSpace", data
+                .getNewNodeName());
+        assertTrue("Attribute not detected", data.isAttribute());
+        assertTrue("Path nodes available", data.getPathNodes().isEmpty());
+
+        data = engine.prepareAdd(root, "[@newAttr]");
+        assertSame("Root node is not parent", root, data.getParent());
+        assertEquals("Wrong name of new node", "newAttr", data.getNewNodeName());
+        assertTrue("Attribute not detected", data.isAttribute());
+    }
+
+    /**
+     * Tests add operations where complete paths are added.
+     */
+    @Test
+    public void testPrepareAddWithPath()
+    {
+        NodeAddData data = engine.prepareAdd(root,
+                "tables.table(1).fields.field(-1).name");
+        assertEquals("Wrong name of new node", "name", data.getNewNodeName());
+        checkNodePath(data, new String[]
+        { "field"});
+        assertEquals("Wrong type of parent node", "fields", data.getParent()
+                .getName());
+
+        data = engine.prepareAdd(root, "tables.table(-1).name");
+        assertEquals("Wrong name of new node", "name", data.getNewNodeName());
+        checkNodePath(data, new String[]
+        { "table"});
+        assertEquals("Wrong type of parent node", "tables", data.getParent()
+                .getName());
+
+        data = engine.prepareAdd(root, "a.complete.new.path");
+        assertEquals("Wrong name of new node", "path", data.getNewNodeName());
+        checkNodePath(data, new String[]
+        { "a", "complete", "new"});
+        assertSame("Root is not parent", root, data.getParent());
+    }
+
+    /**
+     * Tests add operations when property and attribute delimiters are equal.
+     * Then it is not possible to add new attribute nodes.
+     */
+    @Test
+    public void testPrepareAddWithSameAttributeDelimiter()
+    {
+        engine.setAttributeEnd(null);
+        engine.setAttributeStart(engine.getPropertyDelimiter());
+
+        NodeAddData data = engine.prepareAdd(root, "tables.table(0).test");
+        assertEquals("Wrong name of new node", "test", data.getNewNodeName());
+        assertFalse("New node is an attribute", data.isAttribute());
+        assertEquals("Wrong type of parent node", "table", data.getParent()
+                .getName());
+
+        data = engine.prepareAdd(root, "a.complete.new.path");
+        assertFalse("New node is an attribute", data.isAttribute());
+        checkNodePath(data, new String[]
+        { "a", "complete", "new"});
+    }
+
+    /**
+     * Tests add operations when an alternative syntax is set.
+     */
+    @Test
+    public void testPrepareAddWithAlternativeSyntax()
+    {
+        setUpAlternativeSyntax();
+        NodeAddData data = engine.prepareAdd(root, "tables/table[0]/test");
+        assertEquals("Wrong name of new node", "test", data.getNewNodeName());
+        assertFalse("New node is attribute", data.isAttribute());
+        assertEquals("Wrong parent node", tables[0], data.getParent().getChild(
+                0).getValue());
+
+        data = engine.prepareAdd(root, "a/complete/new/path at attr");
+        assertEquals("Wrong name of new attribute", "attr", data
+                .getNewNodeName());
+        checkNodePath(data, new String[]
+        { "a", "complete", "new", "path"});
+        assertSame("Root is not parent", root, data.getParent());
+    }
+
+    /**
+     * Tests using invalid keys, e.g. if something should be added to
+     * attributes.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testPrepareAddInvalidKey()
+    {
+        engine.prepareAdd(root, "tables.table(0)[@type].new");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testPrepareAddInvalidKeyAttribute()
+    {
+        engine
+        .prepareAdd(root,
+                "a.complete.new.path.with.an[@attribute].at.a.non.allowed[@position]");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testPrepareAddNullKey()
+    {
+        engine.prepareAdd(root, null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testPrepareAddEmptyKey()
+    {
+        engine.prepareAdd(root, "");
+    }
+
+    /**
+     * Creates a node hierarchy for testing that consists of tables, their
+     * fields, and some additional data:
+     *
+     * <pre>
+     *  tables
+     *       table
+     *          name
+     *          fields
+     *              field
+     *                  name
+     *              field
+     *                  name
+     * </pre>
+     *
+     * @return the root of the test node hierarchy
+     */
+    protected ConfigurationNode setUpNodes()
+    {
+        DefaultConfigurationNode rootNode = new DefaultConfigurationNode();
+
+        DefaultConfigurationNode nodeTables = new DefaultConfigurationNode(
+                "tables");
+        rootNode.addChild(nodeTables);
+        for (int i = 0; i < tables.length; i++)
+        {
+            DefaultConfigurationNode nodeTable = new DefaultConfigurationNode(
+                    "table");
+            nodeTables.addChild(nodeTable);
+            nodeTable.addChild(new DefaultConfigurationNode("name", tables[i]));
+            nodeTable.addAttribute(new DefaultConfigurationNode("type",
+                    tabTypes[i]));
+            DefaultConfigurationNode nodeFields = new DefaultConfigurationNode(
+                    "fields");
+            nodeTable.addChild(nodeFields);
+
+            for (int j = 0; j < fields[i].length; j++)
+            {
+                nodeFields.addChild(createFieldNode(fields[i][j]));
+            }
+        }
+
+        DefaultConfigurationNode nodeConn = new DefaultConfigurationNode(
+                "connection.settings");
+        rootNode.addChild(nodeConn);
+        nodeConn.addChild(new DefaultConfigurationNode("usr.name", "scott"));
+        nodeConn.addChild(new DefaultConfigurationNode("usr.pwd", "tiger"));
+        rootNode.addAttribute(new DefaultConfigurationNode("test", "true"));
+
+        return rootNode;
+    }
+
+    /**
+     * Configures the expression engine to use a different syntax.
+     */
+    private void setUpAlternativeSyntax()
+    {
+        engine.setAttributeEnd(null);
+        engine.setAttributeStart("@");
+        engine.setPropertyDelimiter("/");
+        engine.setEscapedDelimiter(null);
+        engine.setIndexStart("[");
+        engine.setIndexEnd("]");
+    }
+
+    /**
+     * Helper method for checking the evaluation of a key. Queries the
+     * expression engine and tests if the expected results are returned.
+     *
+     * @param key the key
+     * @param name the name of the nodes to be returned
+     * @param count the number of expected result nodes
+     * @return the list with the results of the query
+     */
+    private List<ConfigurationNode> checkKey(String key, String name, int count)
+    {
+        List<ConfigurationNode> nodes = engine.query(root, key);
+        assertEquals("Wrong number of result nodes for key " + key, count,
+                nodes.size());
+        for (Iterator<ConfigurationNode> it = nodes.iterator(); it.hasNext();)
+        {
+            assertEquals("Wrong result node for key " + key, name,
+                    it.next().getName());
+        }
+        return nodes;
+    }
+
+    /**
+     * Helper method for checking the value of a node specified by the given
+     * key. This method evaluates the key and checks whether the resulting node
+     * has the expected value.
+     *
+     * @param key the key
+     * @param name the expected name of the result node
+     * @param value the expected value of the result node
+     */
+    private void checkKeyValue(String key, String name, String value)
+    {
+        List<ConfigurationNode> nodes = checkKey(key, name, 1);
+        assertEquals("Wrong value for key " + key, value,
+                nodes.get(0).getValue());
+    }
+
+    /**
+     * Helper method for checking the path of an add operation.
+     *
+     * @param data the add data object
+     * @param expected the expected path nodes
+     */
+    private void checkNodePath(NodeAddData data, String[] expected)
+    {
+        assertEquals("Wrong number of path nodes", expected.length, data
+                .getPathNodes().size());
+        Iterator<String> it = data.getPathNodes().iterator();
+        for (int i = 0; i < expected.length; i++)
+        {
+            assertEquals("Wrong path node " + i, expected[i], it.next());
+        }
+    }
+
+    /**
+     * Helper method for creating a field node with its children for the test
+     * node hierarchy.
+     *
+     * @param name the name of the field
+     * @return the field node
+     */
+    private static ConfigurationNode createFieldNode(String name)
+    {
+        DefaultConfigurationNode nodeField = new DefaultConfigurationNode(
+                "field");
+        nodeField.addChild(new DefaultConfigurationNode("name", name));
+        return nodeField;
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/tree/TestMergeCombiner.java b/src/test/java/org/apache/commons/configuration/tree/TestMergeCombiner.java
new file mode 100644
index 0000000..9e2d9de
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/tree/TestMergeCombiner.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+
+import java.util.List;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.HierarchicalConfiguration;
+import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
+import org.junit.Test;
+
+/**
+ * Test class for MergeCombiner.
+ *
+ * @version $Id: TestMergeCombiner.java 1225911 2011-12-30 20:19:10Z oheger $
+ */
+public class TestMergeCombiner extends AbstractCombinerTest
+{
+    /**
+     * Creates the combiner.
+     *
+     * @return the combiner
+     */
+    @Override
+    protected NodeCombiner createCombiner()
+    {
+        return new MergeCombiner();
+    }
+
+    /**
+     * Tests combination of simple elements.
+     */
+    @Test
+    public void testSimpleValues() throws ConfigurationException
+    {
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        assertEquals("Wrong number of bgcolors", 0, config
+                .getMaxIndex("gui.bgcolor"));
+        assertEquals("Wrong bgcolor", "green", config.getString("gui.bgcolor"));
+        assertEquals("Wrong selcolor", "yellow", config
+                .getString("gui.selcolor"));
+        assertEquals("Wrong fgcolor", "blue", config.getString("gui.fgcolor"));
+        assertEquals("Wrong level", 1, config.getInt("gui.level"));
+    }
+
+    /**
+     * Tests combination of attributes.
+     */
+    @Test
+    public void testAttributes() throws ConfigurationException
+    {
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        assertEquals("Wrong value of min attribute", 1, config
+                .getInt("gui.level[@min]"));
+        assertEquals("Wrong value of default attribute", 2, config
+                .getInt("gui.level[@default]"));
+        assertEquals("Wrong number of id attributes", 0, config
+                .getMaxIndex("database.tables.table(0)[@id]"));
+        assertEquals("Wrong value of table id", 1, config
+                .getInt("database.tables.table(0)[@id]"));
+    }
+
+    /**
+     * Tests whether property values are correctly overridden.
+     */
+    @Test
+    public void testOverrideValues() throws ConfigurationException
+    {
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        assertEquals("Wrong user", "Admin", config
+                .getString("base.services.security.login.user"));
+        assertEquals("Wrong user type", "default", config
+                .getString("base.services.security.login.user[@type]"));
+        assertNull("Wrong password", config.getString("base.services.security.login.passwd"));
+        assertEquals("Wrong password type", "secret", config
+                .getString("base.services.security.login.passwd[@type]"));
+    }
+
+    /**
+     * Tests if a list from the first node structure overrides a list in the
+     * second structure.
+     */
+    @Test
+    public void testListFromFirstStructure() throws ConfigurationException
+    {
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        assertEquals("Wrong number of services", 0, config
+                .getMaxIndex("net.service.url"));
+        assertEquals("Wrong service", "http://service1.org", config
+                .getString("net.service.url"));
+        assertFalse("Type attribute available", config
+                .containsKey("net.service.url[@type]"));
+    }
+
+    /**
+     * Tests if a list from the second structure is added if it is not defined
+     * in the first structure.
+     */
+    @Test
+    public void testListFromSecondStructure() throws ConfigurationException
+    {
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        assertEquals("Wrong number of servers", 3, config
+                .getMaxIndex("net.server.url"));
+        assertEquals("Wrong server", "http://testsvr.com", config
+                .getString("net.server.url(2)"));
+    }
+
+    /**
+     * Tests the combination of the table structure. With the merge combiner
+     * both table 1 and table 2 should be present.
+     */
+    @Test
+    public void testCombinedTable() throws ConfigurationException
+    {
+        checkTable(createCombinedConfiguration());
+    }
+
+    @Test
+    public void testMerge() throws ConfigurationException
+    {
+        //combiner.setDebugStream(System.out);
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        config.setExpressionEngine(new XPathExpressionEngine());
+        assertEquals("Wrong number of Channels", 3, config.getMaxIndex("Channels/Channel"));
+        assertEquals("Bad Channel 1 Name", "My Channel",
+                config.getString("Channels/Channel[@id='1']/Name"));
+        assertEquals("Bad Channel Type", "half",
+                config.getString("Channels/Channel[@id='1']/@type"));
+        assertEquals("Bad Channel 2 Name", "Channel 2",
+                config.getString("Channels/Channel[@id='2']/Name"));
+        assertEquals("Bad Channel Type", "full",
+                config.getString("Channels/Channel[@id='2']/@type"));
+        assertEquals("Bad Channel Data", "test 1 data",
+                config.getString("Channels/Channel[@id='1']/ChannelData"));
+        assertEquals("Bad Channel Data", "test 2 data",
+                config.getString("Channels/Channel[@id='2']/ChannelData"));
+        assertEquals("Bad Channel Data", "more test 2 data",
+                config.getString("Channels/Channel[@id='2']/MoreChannelData"));
+
+    }
+
+    /**
+     * Helper method for checking the combined table structure.
+     *
+     * @param config the config
+     * @return the node for the table element
+     */
+    private ConfigurationNode checkTable(HierarchicalConfiguration config)
+    {
+        assertEquals("Wrong number of tables", 1, config
+                .getMaxIndex("database.tables.table"));
+        HierarchicalConfiguration c = config
+                .configurationAt("database.tables.table(0)");
+        assertEquals("Wrong table name", "documents", c.getString("name"));
+        assertEquals("Wrong number of fields", 2, c
+                .getMaxIndex("fields.field.name"));
+        assertEquals("Wrong field", "docname", c
+                .getString("fields.field(1).name"));
+
+        List<ConfigurationNode> nds = config.getExpressionEngine().query(config.getRoot(),
+                "database.tables.table");
+        assertFalse("No node found", nds.isEmpty());
+        return nds.get(0);
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/commons/configuration/tree/TestNodeAddData.java b/src/test/java/org/apache/commons/configuration/tree/TestNodeAddData.java
new file mode 100644
index 0000000..5bec4a0
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/tree/TestNodeAddData.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for NodeAddData.
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: TestNodeAddData.java 1226097 2011-12-31 15:16:02Z oheger $
+ */
+public class TestNodeAddData
+{
+    /** Constant for the default parent node used for testing. */
+    private static final ConfigurationNode TEST_PARENT = new DefaultConfigurationNode(
+            "parent");
+
+    /** Constant for the name of the new node. */
+    private static final String TEST_NODENAME = "testNewNode";
+
+    /** Constant for the name of a path node. */
+    private static final String PATH_NODE_NAME = "PATHNODE";
+
+    /** Constant for the number of path nodes to be added. */
+    private static final int PATH_NODE_COUNT = 10;
+
+    /** The object to be tested. */
+    NodeAddData addData;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        addData = new NodeAddData(TEST_PARENT, TEST_NODENAME);
+    }
+
+    /**
+     * Tests the default values of an uninitialized instance.
+     */
+    @Test
+    public void testUninitialized()
+    {
+        addData = new NodeAddData();
+        assertNull("A parent is set", addData.getParent());
+        assertNull("Node has a name", addData.getNewNodeName());
+        assertFalse("Attribute flag is set", addData.isAttribute());
+        assertTrue("Path nodes are not empty", addData.getPathNodes().isEmpty());
+    }
+
+    /**
+     * Tests the constructor that initializes the most important fields.
+     */
+    @Test
+    public void testInitialized()
+    {
+        assertSame("Wrong parent", TEST_PARENT, addData.getParent());
+        assertEquals("Wrong node name", TEST_NODENAME, addData.getNewNodeName());
+        assertFalse("Attribute flag is set", addData.isAttribute());
+        assertTrue("Path nodes are not empty", addData.getPathNodes().isEmpty());
+    }
+
+    /**
+     * Tests adding path nodes.
+     */
+    @Test
+    public void testAddPathNode()
+    {
+        for (int i = 0; i < PATH_NODE_COUNT; i++)
+        {
+            addData.addPathNode(PATH_NODE_NAME + i);
+        }
+
+        List<String> nodes = addData.getPathNodes();
+        assertEquals("Incorrect number of path nodes", PATH_NODE_COUNT, nodes
+                .size());
+        for (int i = 0; i < PATH_NODE_COUNT; i++)
+        {
+            assertEquals("Wrong path node at position" + i, PATH_NODE_NAME + i,
+                    nodes.get(i));
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/tree/TestOverrideCombiner.java b/src/test/java/org/apache/commons/configuration/tree/TestOverrideCombiner.java
new file mode 100644
index 0000000..6d41ed5
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/tree/TestOverrideCombiner.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.HierarchicalConfiguration;
+import org.junit.Test;
+
+/**
+ * Test class for OverrideCombiner.
+ *
+ * @version $Id: TestOverrideCombiner.java 1225911 2011-12-30 20:19:10Z oheger $
+ */
+public class TestOverrideCombiner extends AbstractCombinerTest
+{
+    /**
+     * Creates the combiner.
+     *
+     * @return the combiner
+     */
+    @Override
+    protected NodeCombiner createCombiner()
+    {
+        return new OverrideCombiner();
+    }
+
+    /**
+     * Tests combination of simple elements.
+     */
+    @Test
+    public void testSimpleValues() throws ConfigurationException
+    {
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        assertEquals("Wrong number of bgcolors", 0, config
+                .getMaxIndex("gui.bgcolor"));
+        assertEquals("Wrong bgcolor", "green", config.getString("gui.bgcolor"));
+        assertEquals("Wrong selcolor", "yellow", config
+                .getString("gui.selcolor"));
+        assertEquals("Wrong fgcolor", "blue", config.getString("gui.fgcolor"));
+        assertEquals("Wrong level", 1, config.getInt("gui.level"));
+    }
+
+    /**
+     * Tests combination of attributes.
+     */
+    @Test
+    public void testAttributes() throws ConfigurationException
+    {
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        assertEquals("Wrong value of min attribute", 1, config
+                .getInt("gui.level[@min]"));
+        assertEquals("Wrong value of default attribute", 2, config
+                .getInt("gui.level[@default]"));
+        assertEquals("Wrong number of id attributes", 0, config
+                .getMaxIndex("database.tables.table(0)[@id]"));
+        assertEquals("Wrong value of table id", 1, config
+                .getInt("database.tables.table(0)[@id]"));
+    }
+
+    /**
+     * Tests whether property values are correctly overridden.
+     */
+    @Test
+    public void testOverrideValues() throws ConfigurationException
+    {
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        assertEquals("Wrong user", "Admin", config
+                .getString("base.services.security.login.user"));
+        assertEquals("Wrong user type", "default", config
+                .getString("base.services.security.login.user[@type]"));
+        assertEquals("Wrong password", "BeamMeUp", config
+                .getString("base.services.security.login.passwd"));
+        assertEquals("Wrong password type", "secret", config
+                .getString("base.services.security.login.passwd[@type]"));
+    }
+
+    /**
+     * Tests if a list from the first node structure overrides a list in the
+     * second structure.
+     */
+    @Test
+    public void testListFromFirstStructure() throws ConfigurationException
+    {
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        assertEquals("Wrong number of services", 0, config
+                .getMaxIndex("net.service.url"));
+        assertEquals("Wrong service", "http://service1.org", config
+                .getString("net.service.url"));
+        assertFalse("Type attribute available", config
+                .containsKey("net.service.url[@type]"));
+    }
+
+    /**
+     * Tests if a list from the second structure is added if it is not defined
+     * in the first structure.
+     */
+    @Test
+    public void testListFromSecondStructure() throws ConfigurationException
+    {
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        assertEquals("Wrong number of servers", 3, config
+                .getMaxIndex("net.server.url"));
+        assertEquals("Wrong server", "http://testsvr.com", config
+                .getString("net.server.url(2)"));
+    }
+
+    /**
+     * Tests the combination of the table structure. Because the table node is
+     * not declared as a list node the structures will be combined. But this
+     * won't make any difference because the values in the first table override
+     * the values in the second table. Only the node for the table element will
+     * be a ViewNode.
+     */
+    @Test
+    public void testCombinedTableNoList() throws ConfigurationException
+    {
+        ConfigurationNode tabNode = checkTable(createCombinedConfiguration());
+        assertTrue("Node is not a view node", tabNode instanceof ViewNode);
+    }
+
+    /**
+     * Tests the combination of the table structure when the table node is
+     * declared as a list node. In this case the first table structure
+     * completely overrides the second and will be directly added to the
+     * resulting structure.
+     */
+    @Test
+    public void testCombinedTableList() throws ConfigurationException
+    {
+        combiner.addListNode("table");
+        ConfigurationNode tabNode = checkTable(createCombinedConfiguration());
+        assertFalse("Node is a view node", tabNode instanceof ViewNode);
+    }
+
+    /**
+     * Helper method for checking the combined table structure.
+     *
+     * @param config the config
+     * @return the node for the table element
+     */
+    private ConfigurationNode checkTable(HierarchicalConfiguration config)
+    {
+        assertEquals("Wrong number of tables", 0, config
+                .getMaxIndex("database.tables.table"));
+        HierarchicalConfiguration c = config
+                .configurationAt("database.tables.table");
+        assertEquals("Wrong table name", "documents", c.getString("name"));
+        assertEquals("Wrong number of fields", 2, c
+                .getMaxIndex("fields.field.name"));
+        assertEquals("Wrong field", "docname", c
+                .getString("fields.field(1).name"));
+
+        List<ConfigurationNode> nds = config.getExpressionEngine().query(config.getRoot(),
+                "database.tables.table");
+        assertFalse("No node found", nds.isEmpty());
+        return nds.get(0);
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/tree/TestUnionCombiner.java b/src/test/java/org/apache/commons/configuration/tree/TestUnionCombiner.java
new file mode 100644
index 0000000..8bf503b
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/tree/TestUnionCombiner.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.HierarchicalConfiguration;
+import org.junit.Test;
+
+/**
+ * Test class for UnionCombiner.
+ *
+ * @version $Id: TestUnionCombiner.java 1225911 2011-12-30 20:19:10Z oheger $
+ */
+public class TestUnionCombiner extends AbstractCombinerTest
+{
+    /**
+     * Creates the combiner.
+     *
+     * @return the combiner
+     */
+    @Override
+    protected NodeCombiner createCombiner()
+    {
+        return new UnionCombiner();
+    }
+
+    /**
+     * Tests combination of simple values (no lists).
+     */
+    @Test
+    public void testSimpleValues() throws ConfigurationException
+    {
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        assertEquals("Too few bgcolors", 1, config.getMaxIndex("gui.bgcolor"));
+        assertEquals("Wrong first color", "green", config
+                .getString("gui.bgcolor(0)"));
+        assertEquals("Wrong second color", "black", config
+                .getString("gui.bgcolor(1)"));
+        assertEquals("Wrong number of selcolors", 0, config
+                .getMaxIndex("gui.selcolor"));
+        assertEquals("Wrong selcolor", "yellow", config
+                .getString("gui.selcolor"));
+    }
+
+    /**
+     * Tests combinations of elements with attributes.
+     */
+    @Test
+    public void testSimpleValuesWithAttributes() throws ConfigurationException
+    {
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        assertEquals("Too few level elements", 1, config
+                .getMaxIndex("gui.level"));
+        assertEquals("Wrong value of first element", 1, config
+                .getInt("gui.level(0)"));
+        assertEquals("Wrong value of second element", 4, config
+                .getInt("gui.level(1)"));
+        assertEquals("Wrong value of first attribute", 2, config
+                .getInt("gui.level(0)[@default]"));
+        assertFalse("Found wrong attribute", config
+                .containsKey("gui.level(0)[@min]"));
+        assertEquals("Wrong value of second attribute", 1, config
+                .getInt("gui.level(1)[@min]"));
+    }
+
+    /**
+     * Tests combination of attributes.
+     */
+    @Test
+    public void testAttributes() throws ConfigurationException
+    {
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        assertEquals("Too few attributes", 1, config
+                .getMaxIndex("database.tables.table(0)[@id]"));
+        assertEquals("Wrong value of first attribute", 1, config
+                .getInt("database.tables.table(0)[@id](0)"));
+        assertEquals("Wrong value of second attribute", 2, config
+                .getInt("database.tables.table(0)[@id](1)"));
+    }
+
+    /**
+     * Tests combination of lists.
+     */
+    @Test
+    public void testLists() throws ConfigurationException
+    {
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        assertEquals("Too few list elements", 2, config
+                .getMaxIndex("net.service.url"));
+        assertEquals("Wrong first service", "http://service1.org", config
+                .getString("net.service.url(0)"));
+        assertEquals("Wrong second service", "http://service2.org", config
+                .getString("net.service.url(1)"));
+        assertEquals("Wrong service attribute", 2, config
+                .getInt("net.service.url(2)[@type]"));
+        assertEquals("Wrong number of server elements", 3, config
+                .getMaxIndex("net.server.url"));
+    }
+
+    /**
+     * Tests combining a list of tables. Per default the table elements will be
+     * combined. But if they are defined as list elements, the resulting tree
+     * should contain two table nodes.
+     */
+    @Test
+    public void testTableList() throws ConfigurationException
+    {
+        combiner.addListNode("table");
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        assertEquals("Wrong name of first table", "documents", config
+                .getString("database.tables.table(0).name"));
+        assertEquals("Wrong id of first table", 1, config
+                .getInt("database.tables.table(0)[@id]"));
+        assertEquals("Wrong name of second table", "tasks", config
+                .getString("database.tables.table(1).name"));
+        assertEquals("Wrong id of second table", 2, config
+                .getInt("database.tables.table(1)[@id]"));
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/tree/TestViewNode.java b/src/test/java/org/apache/commons/configuration/tree/TestViewNode.java
new file mode 100644
index 0000000..4d10e91
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/tree/TestViewNode.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for ViewNode.
+ *
+ * @version $Id: TestViewNode.java 1226098 2011-12-31 15:18:45Z oheger $
+ */
+public class TestViewNode
+{
+    /** Stores the view node to be tested. */
+    ViewNode viewNode;
+
+    /** Stores a regular node. */
+    ConfigurationNode node;
+
+    /** A child node of the regular node. */
+    ConfigurationNode child;
+
+    /** An attribute node of the regular node. */
+    ConfigurationNode attr;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        node = new DefaultConfigurationNode();
+        child = new DefaultConfigurationNode("child");
+        attr = new DefaultConfigurationNode("attr");
+        node.addChild(child);
+        node.addAttribute(attr);
+        viewNode = new ViewNode();
+    }
+
+    /**
+     * Tests adding a child to the view node.
+     */
+    @Test
+    public void testAddChild()
+    {
+        viewNode.addChild(child);
+        assertEquals("Parent was changed", node, child.getParentNode());
+        assertEquals("Child was not added", 1, viewNode.getChildrenCount());
+    }
+
+    /**
+     * Tests adding a null child to the view node. This should throw an
+     * exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testAddNullChild()
+    {
+        viewNode.addChild(null);
+    }
+
+    /**
+     * Tests adding an attribute to the view node.
+     */
+    @Test
+    public void testAddAttribute()
+    {
+        viewNode.addAttribute(attr);
+        assertEquals("Parent was changed", node, attr.getParentNode());
+        assertEquals("Attribute was not added", 1, viewNode.getAttributeCount());
+    }
+
+    /**
+     * Tests adding a null attribute to the view node. This should cause an
+     * exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testAddNullAttribute()
+    {
+        viewNode.addAttribute(null);
+    }
+
+    /**
+     * Tests appending all children to a view node.
+     */
+    @Test
+    public void testAppendChildren()
+    {
+        viewNode.addChild(new DefaultConfigurationNode("testNode"));
+        viewNode.appendChildren(node);
+        assertEquals("Wrong number of children", 2, viewNode.getChildrenCount());
+        assertEquals("Cannot find child", child, viewNode.getChild(1));
+        assertEquals("Parent was changed", node, viewNode
+                .getChild(1).getParentNode());
+    }
+
+    /**
+     * Tests appending children from a null source. This should be a noop.
+     */
+    @Test
+    public void testAppendNullChildren()
+    {
+        viewNode.appendChildren(null);
+        assertEquals("Wrong number of children", 0, viewNode.getChildrenCount());
+    }
+
+    /**
+     * tests appending all attributes to a view node.
+     */
+    @Test
+    public void testAppendAttributes()
+    {
+        viewNode.appendAttributes(node);
+        assertEquals("Wrong number of attributes", 1, viewNode
+                .getAttributeCount());
+        assertEquals("Cannot find attribute", attr, viewNode.getAttribute(0));
+        assertEquals("Parent was changed", node, viewNode
+                .getAttribute(0).getParentNode());
+    }
+
+    /**
+     * Tests appending attributes from a null source. This should be a noop.
+     */
+    @Test
+    public void testAppendNullAttributes()
+    {
+        viewNode.appendAttributes(null);
+        assertEquals("Wrong number of attributes", 0, viewNode
+                .getAttributeCount());
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/tree/xpath/AbstractXPathTest.java b/src/test/java/org/apache/commons/configuration/tree/xpath/AbstractXPathTest.java
new file mode 100644
index 0000000..7516df3
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/tree/xpath/AbstractXPathTest.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree.xpath;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.DefaultConfigurationNode;
+import org.apache.commons.jxpath.ri.model.NodeIterator;
+import org.junit.After;
+import org.junit.Before;
+
+/**
+ * A base class for testing classes of the XPath package. This base class
+ * creates a hierarchy of nodes in its setUp() method that can be used for test
+ * cases.
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: AbstractXPathTest.java 1226104 2011-12-31 15:37:16Z oheger $
+ */
+public abstract class AbstractXPathTest
+{
+    /** Constant for the name of the counter attribute. */
+    protected static final String ATTR_NAME = "counter";
+
+    /** Constant for the name of the first child. */
+    protected static final String CHILD_NAME1 = "subNode";
+
+    /** Constant for the name of the second child. */
+    protected static final String CHILD_NAME2 = "childNode";
+
+    /** Constant for the number of sub nodes. */
+    protected static final int CHILD_COUNT = 5;
+
+    /** Constant for the number of levels in the hierarchy. */
+    protected static final int LEVEL_COUNT = 3;
+
+    /** Stores the root node of the hierarchy. */
+    protected ConfigurationNode root;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        root = constructHierarchy(LEVEL_COUNT);
+    }
+
+    /**
+     * Clears the test environment.
+     */
+    @After
+    public void tearDown() throws Exception
+    {
+        root = null;
+    }
+
+    /**
+     * Builds up a hierarchy of nodes. Each node has {@code CHILD_COUNT}
+     * child nodes having the names {@code CHILD_NAME1} or
+     * {@code CHILD_NAME2}. Their values are named like their parent
+     * node with an additional index. Each node has an attribute with a counter
+     * value.
+     *
+     * @param levels the number of levels in the hierarchy
+     * @return the root node of the hierarchy
+     */
+    protected ConfigurationNode constructHierarchy(int levels)
+    {
+        ConfigurationNode result = new DefaultConfigurationNode();
+        createLevel(result, levels);
+        return result;
+    }
+
+    /**
+     * Determines the number of elements contained in the given iterator.
+     *
+     * @param iterator the iterator
+     * @return the number of elements in this iteration
+     */
+    protected int iteratorSize(NodeIterator iterator)
+    {
+        int cnt = 0;
+        boolean ok;
+
+        do
+        {
+            ok = iterator.setPosition(cnt + 1);
+            if (ok)
+            {
+                cnt++;
+            }
+        } while (ok);
+
+        return cnt;
+    }
+
+    /**
+     * Returns a list with all configuration nodes contained in the specified
+     * iteration. It is assumed that the iteration contains only elements of
+     * this type.
+     *
+     * @param iterator the iterator
+     * @return a list with configuration nodes obtained from the iterator
+     */
+    protected List<ConfigurationNode> iterationElements(NodeIterator iterator)
+    {
+        List<ConfigurationNode> result = new ArrayList<ConfigurationNode>();
+        for (int pos = 1; iterator.setPosition(pos); pos++)
+        {
+            result.add((ConfigurationNode) iterator.getNodePointer().getNode());
+        }
+        return result;
+    }
+
+    /**
+     * Recursive helper method for creating a level of the node hierarchy.
+     *
+     * @param parent the parent node
+     * @param level the level counter
+     */
+    private void createLevel(ConfigurationNode parent, int level)
+    {
+        if (level >= 0)
+        {
+            String prefix = (parent.getValue() == null) ? "" : parent
+                    .getValue()
+                    + ".";
+            for (int i = 1; i <= CHILD_COUNT; i++)
+            {
+                ConfigurationNode child = new DefaultConfigurationNode(
+                        (i % 2 == 0) ? CHILD_NAME1 : CHILD_NAME2, prefix + i);
+                parent.addChild(child);
+                child.addAttribute(new DefaultConfigurationNode(ATTR_NAME,
+                        String.valueOf(i)));
+
+                createLevel(child, level - 1);
+            }
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/tree/xpath/TestConfigurationIteratorAttributes.java b/src/test/java/org/apache/commons/configuration/tree/xpath/TestConfigurationIteratorAttributes.java
new file mode 100644
index 0000000..e20787d
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/tree/xpath/TestConfigurationIteratorAttributes.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree.xpath;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.DefaultConfigurationNode;
+import org.apache.commons.jxpath.ri.QName;
+import org.apache.commons.jxpath.ri.model.NodePointer;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for ConfigurationIteratorAttributes.
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: TestConfigurationIteratorAttributes.java 1226104 2011-12-31 15:37:16Z oheger $
+ */
+public class TestConfigurationIteratorAttributes extends AbstractXPathTest
+{
+    /** Constant for the name of another test attribute.*/
+    private static final String TEST_ATTR = "test";
+
+    /** Stores the node pointer of the test node.*/
+    NodePointer pointer;
+
+    @Override
+    @Before
+    public void setUp() throws Exception
+    {
+        super.setUp();
+
+        // Adds further attributes to the test node
+        ConfigurationNode testNode = root.getChild(1);
+        testNode.addAttribute(new DefaultConfigurationNode(TEST_ATTR, "yes"));
+        pointer = new ConfigurationNodePointer(testNode, Locale.getDefault());
+    }
+
+    /**
+     * Tests to iterate over all attributes.
+     */
+    @Test
+    public void testIterateAllAttributes()
+    {
+        ConfigurationNodeIteratorAttribute it = new ConfigurationNodeIteratorAttribute(pointer, new QName(null, "*"));
+        assertEquals("Wrong number of attributes", 2, iteratorSize(it));
+        List<ConfigurationNode> attrs = iterationElements(it);
+        assertEquals("Wrong first attribute", ATTR_NAME, attrs.get(0).getName());
+        assertEquals("Wrong first attribute", TEST_ATTR, attrs.get(1).getName());
+    }
+
+    /**
+     * Tests to iterate over attributes with a specific name.
+     */
+    @Test
+    public void testIterateSpecificAttribute()
+    {
+        ConfigurationNodeIteratorAttribute it = new ConfigurationNodeIteratorAttribute(pointer, new QName(null, TEST_ATTR));
+        assertEquals("Wrong number of attributes", 1, iteratorSize(it));
+        assertEquals("Wrong attribute", TEST_ATTR, iterationElements(it).get(0).getName());
+    }
+
+    /**
+     * Tests to iterate over non existing attributes.
+     */
+    @Test
+    public void testIterateUnknownAttribute()
+    {
+        ConfigurationNodeIteratorAttribute it = new ConfigurationNodeIteratorAttribute(pointer, new QName(null, "unknown"));
+        assertEquals("Found attributes", 0, iteratorSize(it));
+    }
+
+    /**
+     * Tests iteration when a namespace is specified. This is not supported, so
+     * the iteration should be empty.
+     */
+    @Test
+    public void testIterateNamespace()
+    {
+        ConfigurationNodeIteratorAttribute it = new ConfigurationNodeIteratorAttribute(pointer, new QName("test", "*"));
+        assertEquals("Found attributes", 0, iteratorSize(it));
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/tree/xpath/TestConfigurationNodeIteratorChildren.java b/src/test/java/org/apache/commons/configuration/tree/xpath/TestConfigurationNodeIteratorChildren.java
new file mode 100644
index 0000000..01431a6
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/tree/xpath/TestConfigurationNodeIteratorChildren.java
@@ -0,0 +1,247 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree.xpath;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.DefaultConfigurationNode;
+import org.apache.commons.jxpath.ri.Compiler;
+import org.apache.commons.jxpath.ri.QName;
+import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
+import org.apache.commons.jxpath.ri.compiler.NodeTest;
+import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
+import org.apache.commons.jxpath.ri.compiler.ProcessingInstructionTest;
+import org.apache.commons.jxpath.ri.model.NodeIterator;
+import org.apache.commons.jxpath.ri.model.NodePointer;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for ConfigurationNodeIteratorChildren.
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: TestConfigurationNodeIteratorChildren.java 1226104 2011-12-31 15:37:16Z oheger $
+ */
+public class TestConfigurationNodeIteratorChildren extends AbstractXPathTest
+{
+    /** Stores the node pointer to the root node. */
+    NodePointer rootPointer;
+
+    @Override
+    @Before
+    public void setUp() throws Exception
+    {
+        super.setUp();
+        rootPointer = new ConfigurationNodePointer(root, Locale.getDefault());
+    }
+
+    /**
+     * Tests to iterate over all children of the root node.
+     */
+    @Test
+    public void testIterateAllChildren()
+    {
+        ConfigurationNodeIteratorChildren it = new ConfigurationNodeIteratorChildren(
+                rootPointer, null, false, null);
+        assertEquals("Wrong number of elements", CHILD_COUNT, iteratorSize(it));
+        checkValues(it, new int[]
+        { 1, 2, 3, 4, 5 });
+    }
+
+    /**
+     * Tests a reverse iteration.
+     */
+    @Test
+    public void testIterateReverse()
+    {
+        ConfigurationNodeIteratorChildren it = new ConfigurationNodeIteratorChildren(
+                rootPointer, null, true, null);
+        assertEquals("Wrong number of elements", CHILD_COUNT, iteratorSize(it));
+        checkValues(it, new int[]
+        { 5, 4, 3, 2, 1 });
+    }
+
+    /**
+     * Tests using a node test with a wildcard name.
+     */
+    @Test
+    public void testIterateWithWildcardTest()
+    {
+        NodeNameTest test = new NodeNameTest(new QName(null, "*"));
+        ConfigurationNodeIteratorChildren it = new ConfigurationNodeIteratorChildren(
+                rootPointer, test, false, null);
+        assertEquals("Wrong number of elements", CHILD_COUNT, iteratorSize(it));
+    }
+
+    /**
+     * Tests using a node test that defines a namespace prefix. Because
+     * namespaces are not supported, no elements should be in the iteration.
+     */
+    @Test
+    public void testIterateWithPrefixTest()
+    {
+        NodeNameTest test = new NodeNameTest(new QName("prefix", "*"));
+        ConfigurationNodeIteratorChildren it = new ConfigurationNodeIteratorChildren(
+                rootPointer, test, false, null);
+        assertNull("Undefined node pointer not returned", it.getNodePointer());
+        assertEquals("Prefix was not evaluated", 0, iteratorSize(it));
+    }
+
+    /**
+     * Tests using a node test that selects a certain sub node name.
+     */
+    @Test
+    public void testIterateWithNameTest()
+    {
+        NodeNameTest test = new NodeNameTest(new QName(null, CHILD_NAME2));
+        ConfigurationNodeIteratorChildren it = new ConfigurationNodeIteratorChildren(
+                rootPointer, test, false, null);
+        assertTrue("No children found", iteratorSize(it) > 0);
+        for (ConfigurationNode nd : iterationElements(it))
+        {
+            assertEquals("Wrong child element", CHILD_NAME2, nd.getName());
+        }
+    }
+
+    /**
+     * Tests using a not supported test class. This should yield an empty
+     * iteration.
+     */
+    @Test
+    public void testIterateWithUnknownTest()
+    {
+        NodeTest test = new ProcessingInstructionTest("test");
+        ConfigurationNodeIteratorChildren it = new ConfigurationNodeIteratorChildren(
+                rootPointer, test, false, null);
+        assertEquals("Unknown test was not evaluated", 0, iteratorSize(it));
+    }
+
+    /**
+     * Tests using a type test for nodes. This should return all nodes.
+     */
+    @Test
+    public void testIterateWithNodeType()
+    {
+        NodeTypeTest test = new NodeTypeTest(Compiler.NODE_TYPE_NODE);
+        ConfigurationNodeIteratorChildren it = new ConfigurationNodeIteratorChildren(
+                rootPointer, test, false, null);
+        assertEquals("Node type not evaluated", CHILD_COUNT, iteratorSize(it));
+    }
+
+    /**
+     * Tests using a type test for a non supported type. This should return an
+     * empty iteration.
+     */
+    @Test
+    public void testIterateWithUnknownType()
+    {
+        NodeTypeTest test = new NodeTypeTest(Compiler.NODE_TYPE_COMMENT);
+        ConfigurationNodeIteratorChildren it = new ConfigurationNodeIteratorChildren(
+                rootPointer, test, false, null);
+        assertEquals("Unknown node type not evaluated", 0, iteratorSize(it));
+    }
+
+    /**
+     * Tests defining a start node for the iteration.
+     */
+    @Test
+    public void testIterateStartsWith()
+    {
+        NodePointer childPointer = new ConfigurationNodePointer(rootPointer,
+                root.getChild(2));
+        ConfigurationNodeIteratorChildren it = new ConfigurationNodeIteratorChildren(
+                rootPointer, null, false, childPointer);
+        assertEquals("Wrong start position", 0, it.getPosition());
+        List<ConfigurationNode> nodes = iterationElements(it);
+        assertEquals("Wrong size of iteration", CHILD_COUNT - 3, nodes.size());
+        int index = 4;
+        for (Iterator<ConfigurationNode> it2 = nodes.iterator(); it2.hasNext(); index++)
+        {
+            ConfigurationNode node = it2.next();
+            assertEquals("Wrong node value", String.valueOf(index), node
+                    .getValue());
+        }
+    }
+
+    /**
+     * Tests defining a start node for a reverse iteration.
+     */
+    @Test
+    public void testIterateStartsWithReverse()
+    {
+        NodePointer childPointer = new ConfigurationNodePointer(rootPointer,
+                root.getChild(3));
+        ConfigurationNodeIteratorChildren it = new ConfigurationNodeIteratorChildren(
+                rootPointer, null, true, childPointer);
+        int value = 3;
+        for (int index = 1; it.setPosition(index); index++, value--)
+        {
+            ConfigurationNode node = (ConfigurationNode) it.getNodePointer()
+                    .getNode();
+            assertEquals("Incorrect value at index " + index, String
+                    .valueOf(value), node.getValue());
+        }
+        assertEquals("Iteration ended not at end node", 0, value);
+    }
+
+    /**
+     * Tests iteration with an invalid start node. This should cause the
+     * iteration to start at the first position.
+     */
+    @Test
+    public void testIterateStartsWithInvalid()
+    {
+        NodePointer childPointer = new ConfigurationNodePointer(rootPointer,
+                new DefaultConfigurationNode("newNode"));
+        ConfigurationNodeIteratorChildren it = new ConfigurationNodeIteratorChildren(
+                rootPointer, null, false, childPointer);
+        assertEquals("Wrong size of iteration", CHILD_COUNT, iteratorSize(it));
+        it.setPosition(1);
+        ConfigurationNode node = (ConfigurationNode) it.getNodePointer()
+                .getNode();
+        assertEquals("Wrong start node", "1", node.getValue());
+    }
+
+    /**
+     * Helper method for checking the values of the nodes returned by an
+     * iterator. Because the values indicate the order of the child nodes with
+     * this test it can be checked whether the nodes were returned in the
+     * correct order.
+     *
+     * @param iterator the iterator
+     * @param expectedIndices an array with the expected indices
+     */
+    private void checkValues(NodeIterator iterator, int[] expectedIndices)
+    {
+        List<ConfigurationNode> nodes = iterationElements(iterator);
+        for (int i = 0; i < expectedIndices.length; i++)
+        {
+            ConfigurationNode child = nodes.get(i);
+            assertTrue("Wrong index value for child " + i, child.getValue()
+                    .toString().endsWith(String.valueOf(expectedIndices[i])));
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/tree/xpath/TestConfigurationNodePointer.java b/src/test/java/org/apache/commons/configuration/tree/xpath/TestConfigurationNodePointer.java
new file mode 100644
index 0000000..8b300f4
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/tree/xpath/TestConfigurationNodePointer.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree.xpath;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Locale;
+
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.DefaultConfigurationNode;
+import org.apache.commons.jxpath.ri.QName;
+import org.apache.commons.jxpath.ri.model.NodeIterator;
+import org.apache.commons.jxpath.ri.model.NodePointer;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for ConfigurationNodePointer.
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: TestConfigurationNodePointer.java 1226104 2011-12-31 15:37:16Z oheger $
+ */
+public class TestConfigurationNodePointer extends AbstractXPathTest
+{
+    /** Stores the node pointer to be tested. */
+    NodePointer pointer;
+
+    @Override
+    @Before
+    public void setUp() throws Exception
+    {
+        super.setUp();
+        pointer = new ConfigurationNodePointer(root, Locale.getDefault());
+    }
+
+    /**
+     * Tests comparing child node pointers for child nodes.
+     */
+    @Test
+    public void testCompareChildNodePointersChildren()
+    {
+        NodePointer p1 = new ConfigurationNodePointer(pointer, root.getChild(1));
+        NodePointer p2 = new ConfigurationNodePointer(pointer, root.getChild(3));
+        assertEquals("Incorrect order", -1, pointer.compareChildNodePointers(
+                p1, p2));
+        assertEquals("Incorrect symmetric order", 1, pointer
+                .compareChildNodePointers(p2, p1));
+    }
+
+    /**
+     * Tests comparing child node pointers for attribute nodes.
+     */
+    @Test
+    public void testCompareChildNodePointersAttributes()
+    {
+        root.addAttribute(new DefaultConfigurationNode("attr1", "test1"));
+        root.addAttribute(new DefaultConfigurationNode("attr2", "test2"));
+        NodePointer p1 = new ConfigurationNodePointer(pointer, root
+                .getAttribute(0));
+        NodePointer p2 = new ConfigurationNodePointer(pointer, root
+                .getAttribute(1));
+        assertEquals("Incorrect order", -1, pointer.compareChildNodePointers(
+                p1, p2));
+        assertEquals("Incorrect symmetric order", 1, pointer
+                .compareChildNodePointers(p2, p1));
+    }
+
+    /**
+     * tests comparing child node pointers for both child and attribute nodes.
+     */
+    @Test
+    public void testCompareChildNodePointersChildAndAttribute()
+    {
+        root.addAttribute(new DefaultConfigurationNode("attr1", "test1"));
+        NodePointer p1 = new ConfigurationNodePointer(pointer, root.getChild(2));
+        NodePointer p2 = new ConfigurationNodePointer(pointer, root
+                .getAttribute(0));
+        assertEquals("Incorrect order for attributes", 1, pointer
+                .compareChildNodePointers(p1, p2));
+        assertEquals("Incorrect symmetric order for attributes", -1, pointer
+                .compareChildNodePointers(p2, p1));
+    }
+
+    /**
+     * Tests comparing child node pointers for child nodes that do not belong to
+     * the parent node.
+     */
+    @Test
+    public void testCompareChildNodePointersInvalidChildren()
+    {
+        ConfigurationNode node = root.getChild(1);
+        NodePointer p1 = new ConfigurationNodePointer(pointer, node.getChild(1));
+        NodePointer p2 = new ConfigurationNodePointer(pointer, node.getChild(3));
+        assertEquals("Non child nodes could be sorted", 0, pointer
+                .compareChildNodePointers(p1, p2));
+        assertEquals("Non child nodes could be sorted symmetrically", 0,
+                pointer.compareChildNodePointers(p2, p1));
+    }
+
+    /**
+     * Tests the attribute flag.
+     */
+    @Test
+    public void testIsAttribute()
+    {
+        ConfigurationNode node = new DefaultConfigurationNode("test", "testval");
+        NodePointer p = new ConfigurationNodePointer(pointer, node);
+        assertFalse("Node is an attribute", p.isAttribute());
+        node.setAttribute(true);
+        assertTrue("Node is no attribute", p.isAttribute());
+    }
+
+    /**
+     * Tests if leaves in the tree are correctly detected.
+     */
+    @Test
+    public void testIsLeave()
+    {
+        assertFalse("Root node is leaf", pointer.isLeaf());
+
+        NodePointer p = pointer;
+        while (!p.isLeaf())
+        {
+            ConfigurationNode node = (ConfigurationNode) p.getNode();
+            assertTrue("Node has no children", node.getChildrenCount() > 0);
+            p = new ConfigurationNodePointer(p, node.getChild(0));
+        }
+        assertTrue("Node has children", ((ConfigurationNode) p.getNode())
+                .getChildrenCount() == 0);
+    }
+
+    /**
+     * Tests the iterators returned by the node pointer.
+     */
+    @Test
+    public void testIterators()
+    {
+        checkIterators(pointer);
+    }
+
+    /**
+     * Recursive helper method for testing the returned iterators.
+     *
+     * @param p the node pointer to test
+     */
+    private void checkIterators(NodePointer p)
+    {
+        ConfigurationNode node = (ConfigurationNode) p.getNode();
+        NodeIterator it = p.childIterator(null, false, null);
+        assertEquals("Iterator count differs from children count", node
+                .getChildrenCount(), iteratorSize(it));
+
+        for (int index = 1; it.setPosition(index); index++)
+        {
+            NodePointer pchild = it.getNodePointer();
+            assertEquals("Wrong child", node.getChild(index - 1), pchild
+                    .getNode());
+            checkIterators(pchild);
+        }
+
+        it = p.attributeIterator(new QName(null, "*"));
+        assertEquals("Iterator count differs from attribute count", node
+                .getAttributeCount(), iteratorSize(it));
+        for (int index = 1; it.setPosition(index); index++)
+        {
+            NodePointer pattr = it.getNodePointer();
+            assertTrue("Node pointer is no attribute", pattr.isAttribute());
+            assertEquals("Wrong attribute", node.getAttribute(index - 1), pattr
+                    .getNode());
+            checkIterators(pattr);
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/tree/xpath/TestConfigurationNodePointerFactory.java b/src/test/java/org/apache/commons/configuration/tree/xpath/TestConfigurationNodePointerFactory.java
new file mode 100644
index 0000000..34074b4
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/tree/xpath/TestConfigurationNodePointerFactory.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree.xpath;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.DefaultConfigurationNode;
+import org.apache.commons.jxpath.JXPathContext;
+import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for ConfigurationNodePointerFactory. This class does not directly
+ * call the factory's methods, but rather checks if it can be installed in a
+ * {@code JXPathContext} and if XPath expressions can be evaluated.
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: TestConfigurationNodePointerFactory.java 1226104 2011-12-31 15:37:16Z oheger $
+ */
+public class TestConfigurationNodePointerFactory extends AbstractXPathTest
+{
+    /** Stores the JXPathContext used for testing. */
+    JXPathContext context;
+
+    @Override
+    @Before
+    public void setUp() throws Exception
+    {
+        super.setUp();
+        JXPathContextReferenceImpl
+                .addNodePointerFactory(new ConfigurationNodePointerFactory());
+        context = JXPathContext.newContext(root);
+        context.setLenient(true);
+    }
+
+    /**
+     * Tests simple XPath expressions.
+     */
+    @Test
+    public void testSimpleXPath()
+    {
+        List<?> nodes = context.selectNodes(CHILD_NAME1);
+        assertEquals("Incorrect number of results", 2, nodes.size());
+        for (Iterator<?> it = nodes.iterator(); it.hasNext();)
+        {
+            ConfigurationNode node = (ConfigurationNode) it.next();
+            assertEquals("Incorrect node name", CHILD_NAME1, node.getName());
+            assertEquals("Incorrect parent node", root, node.getParentNode());
+        }
+
+        nodes = context.selectNodes("/" + CHILD_NAME1);
+        assertEquals("Incorrect number of results", 2, nodes.size());
+
+        nodes = context.selectNodes(CHILD_NAME2 + "/" + CHILD_NAME1 + "/"
+                + CHILD_NAME2);
+        assertEquals("Incorrect number of results", 18, nodes.size());
+    }
+
+    /**
+     * Tests using indices to specify elements.
+     */
+    @Test
+    public void testIndices()
+    {
+        assertEquals("Incorrect value", "1.2.3", context.getValue("/"
+                + CHILD_NAME2 + "[1]/" + CHILD_NAME1 + "[1]/" + CHILD_NAME2
+                + "[2]"));
+        assertEquals("Incorrect value of last node", String
+                .valueOf(CHILD_COUNT), context.getValue(CHILD_NAME2
+                + "[last()]"));
+
+        List<?> nodes = context.selectNodes("/" + CHILD_NAME1 + "[1]/*");
+        assertEquals("Wrong number of children", CHILD_COUNT, nodes.size());
+        int index = 1;
+        for (Iterator<?> it = nodes.iterator(); it.hasNext(); index++)
+        {
+            ConfigurationNode node = (ConfigurationNode) it.next();
+            assertEquals("Wrong node value for child " + index, "2." + index,
+                    node.getValue());
+        }
+    }
+
+    /**
+     * Tests accessing attributes.
+     */
+    @Test
+    public void testAttributes()
+    {
+        root.addAttribute(new DefaultConfigurationNode("testAttr", "true"));
+        assertEquals("Did not find attribute of root node", "true", context
+                .getValue("@testAttr"));
+        assertEquals("Incorrect attribute value", "1", context.getValue("/"
+                + CHILD_NAME2 + "[1]/@" + ATTR_NAME));
+
+        assertTrue("Found elements with name attribute", context.selectNodes(
+                "//" + CHILD_NAME2 + "[@name]").isEmpty());
+        ConfigurationNode node = root.getChild(2).getChild(
+                1).getChildren(CHILD_NAME2).get(1);
+        node.addAttribute(new DefaultConfigurationNode("name", "testValue"));
+        List<?> nodes = context.selectNodes("//" + CHILD_NAME2 + "[@name]");
+        assertEquals("Name attribute not found", 1, nodes.size());
+        assertEquals("Wrong node returned", node, nodes.get(0));
+    }
+
+    /**
+     * Tests accessing a node's text.
+     */
+    @Test
+    public void testText()
+    {
+        List<?> nodes = context.selectNodes("//" + CHILD_NAME2
+                + "[text()='1.1.1']");
+        assertEquals("Incorrect number of result nodes", 1, nodes.size());
+    }
+
+    /**
+     * Tests accessing the parent axis.
+     */
+    @Test
+    public void testParentAxis()
+    {
+        List<?> nodes = context.selectNodes("/" + CHILD_NAME2 + "/parent::*");
+        assertEquals("Wrong number of parent nodes", 1, nodes.size());
+    }
+
+    /**
+     * Tests accessing the following sibling axis.
+     */
+    @Test
+    public void testFollowingSiblingAxis()
+    {
+        List<?> nodes = context.selectNodes("/" + CHILD_NAME1
+                + "[2]/following-sibling::*");
+        assertEquals("Wrong number of following siblings", 1, nodes.size());
+        ConfigurationNode node = (ConfigurationNode) nodes.get(0);
+        assertEquals("Wrong node type", CHILD_NAME2, node.getName());
+        assertEquals("Wrong index", String.valueOf(CHILD_COUNT), node
+                .getValue());
+    }
+
+    /**
+     * Tests accessing the preceding sibling axis.
+     */
+    @Test
+    public void testPrecedingSiblingAxis()
+    {
+        List<?> nodes = context.selectNodes("/" + CHILD_NAME1
+                + "[2]/preceding-sibling::*");
+        assertEquals("Wrong number of preceding siblings", 3, nodes.size());
+        for (int index = 0, value = 3; index < nodes.size(); index++, value--)
+        {
+            assertEquals("Wrong node index", String.valueOf(value),
+                    ((ConfigurationNode) nodes.get(index)).getValue());
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/tree/xpath/TestXPathExpressionEngine.java b/src/test/java/org/apache/commons/configuration/tree/xpath/TestXPathExpressionEngine.java
new file mode 100644
index 0000000..a1f9f1a
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/tree/xpath/TestXPathExpressionEngine.java
@@ -0,0 +1,449 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree.xpath;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.DefaultConfigurationNode;
+import org.apache.commons.configuration.tree.NodeAddData;
+import org.apache.commons.jxpath.JXPathContext;
+import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
+import org.apache.commons.jxpath.ri.model.NodePointerFactory;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for XPathExpressionEngine.
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html">Commons
+ * Configuration team</a>
+ * @version $Id: TestXPathExpressionEngine.java 1226111 2011-12-31 15:44:50Z oheger $
+ */
+public class TestXPathExpressionEngine
+{
+    /** Constant for the test root node. */
+    static final ConfigurationNode ROOT = new DefaultConfigurationNode(
+            "testRoot");
+
+    /** Constant for the valid test key. */
+    static final String TEST_KEY = "TESTKEY";
+
+    /** The expression engine to be tested. */
+    XPathExpressionEngine engine;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        engine = new MockJXPathContextExpressionEngine();
+    }
+
+    /**
+     * Tests the query() method with a normal expression.
+     */
+    @Test
+    public void testQueryExpression()
+    {
+        List<ConfigurationNode> nodes = engine.query(ROOT, TEST_KEY);
+        assertEquals("Incorrect number of results", 1, nodes.size());
+        assertSame("Wrong result node", ROOT, nodes.get(0));
+        checkSelectCalls(1);
+    }
+
+    /**
+     * Tests a query that has no results. This should return an empty list.
+     */
+    @Test
+    public void testQueryWithoutResult()
+    {
+        List<ConfigurationNode> nodes = engine.query(ROOT, "a non existing key");
+        assertTrue("Result list is not empty", nodes.isEmpty());
+        checkSelectCalls(1);
+    }
+
+    /**
+     * Tests a query with an empty key. This should directly return the root
+     * node without invoking the JXPathContext.
+     */
+    @Test
+    public void testQueryWithEmptyKey()
+    {
+        checkEmptyKey("");
+    }
+
+    /**
+     * Tests a query with a null key. Same as an empty key.
+     */
+    @Test
+    public void testQueryWithNullKey()
+    {
+        checkEmptyKey(null);
+    }
+
+    /**
+     * Helper method for testing undefined keys.
+     *
+     * @param key the key
+     */
+    private void checkEmptyKey(String key)
+    {
+        List<ConfigurationNode> nodes = engine.query(ROOT, key);
+        assertEquals("Incorrect number of results", 1, nodes.size());
+        assertSame("Wrong result node", ROOT, nodes.get(0));
+        checkSelectCalls(0);
+    }
+
+    /**
+     * Tests if the used JXPathContext is correctly initialized.
+     */
+    @Test
+    public void testCreateContext()
+    {
+        JXPathContext ctx = new XPathExpressionEngine().createContext(ROOT,
+                TEST_KEY);
+        assertNotNull("Context is null", ctx);
+        assertTrue("Lenient mode is not set", ctx.isLenient());
+        assertSame("Incorrect context bean set", ROOT, ctx.getContextBean());
+
+        NodePointerFactory[] factories = JXPathContextReferenceImpl
+                .getNodePointerFactories();
+        boolean found = false;
+        for (int i = 0; i < factories.length; i++)
+        {
+            if (factories[i] instanceof ConfigurationNodePointerFactory)
+            {
+                found = true;
+            }
+        }
+        assertTrue("No configuration pointer factory found", found);
+    }
+
+    /**
+     * Tests a normal call of nodeKey().
+     */
+    @Test
+    public void testNodeKeyNormal()
+    {
+        assertEquals("Wrong node key", "parent/child", engine.nodeKey(
+                new DefaultConfigurationNode("child"), "parent"));
+    }
+
+    /**
+     * Tests nodeKey() for an attribute node.
+     */
+    @Test
+    public void testNodeKeyAttribute()
+    {
+        ConfigurationNode node = new DefaultConfigurationNode("attr");
+        node.setAttribute(true);
+        assertEquals("Wrong attribute key", "node/@attr", engine.nodeKey(node,
+                "node"));
+    }
+
+    /**
+     * Tests nodeKey() for the root node.
+     */
+    @Test
+    public void testNodeKeyForRootNode()
+    {
+        assertEquals("Wrong key for root node", "", engine.nodeKey(ROOT, null));
+        assertEquals("Null name not detected", "test", engine.nodeKey(
+                new DefaultConfigurationNode(), "test"));
+    }
+
+    /**
+     * Tests node key() for direct children of the root node.
+     */
+    @Test
+    public void testNodeKeyForRootChild()
+    {
+        ConfigurationNode node = new DefaultConfigurationNode("child");
+        assertEquals("Wrong key for root child node", "child", engine.nodeKey(
+                node, ""));
+        node.setAttribute(true);
+        assertEquals("Wrong key for root attribute", "@child", engine.nodeKey(
+                node, ""));
+    }
+
+    /**
+     * Tests adding a single child node.
+     */
+    @Test
+    public void testPrepareAddNode()
+    {
+        NodeAddData data = engine.prepareAdd(ROOT, TEST_KEY + "  newNode");
+        checkAddPath(data, new String[]
+        { "newNode" }, false);
+        checkSelectCalls(1);
+    }
+
+    /**
+     * Tests adding a new attribute node.
+     */
+    @Test
+    public void testPrepareAddAttribute()
+    {
+        NodeAddData data = engine.prepareAdd(ROOT, TEST_KEY + "\t at newAttr");
+        checkAddPath(data, new String[]
+        { "newAttr" }, true);
+        checkSelectCalls(1);
+    }
+
+    /**
+     * Tests adding a complete path.
+     */
+    @Test
+    public void testPrepareAddPath()
+    {
+        NodeAddData data = engine.prepareAdd(ROOT, TEST_KEY
+                + " \t a/full/path/node");
+        checkAddPath(data, new String[]
+        { "a", "full", "path", "node" }, false);
+        checkSelectCalls(1);
+    }
+
+    /**
+     * Tests adding a complete path whose final node is an attribute.
+     */
+    @Test
+    public void testPrepareAddAttributePath()
+    {
+        NodeAddData data = engine.prepareAdd(ROOT, TEST_KEY
+                + " a/full/path at attr");
+        checkAddPath(data, new String[]
+        { "a", "full", "path", "attr" }, true);
+        checkSelectCalls(1);
+    }
+
+    /**
+     * Tests adding a new node to the root.
+     */
+    @Test
+    public void testPrepareAddRootChild()
+    {
+        NodeAddData data = engine.prepareAdd(ROOT, " newNode");
+        checkAddPath(data, new String[]
+        { "newNode" }, false);
+        checkSelectCalls(0);
+    }
+
+    /**
+     * Tests adding a new attribute to the root.
+     */
+    @Test
+    public void testPrepareAddRootAttribute()
+    {
+        NodeAddData data = engine.prepareAdd(ROOT, " @attr");
+        checkAddPath(data, new String[]
+        { "attr" }, true);
+        checkSelectCalls(0);
+    }
+
+    /**
+     * Tests an add operation with a query that does not return a single node.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testPrepareAddInvalidParent()
+    {
+        engine.prepareAdd(ROOT, "invalidKey newNode");
+    }
+
+    /**
+     * Tests an add operation with an empty path for the new node.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testPrepareAddEmptyPath()
+    {
+        engine.prepareAdd(ROOT, TEST_KEY + " ");
+    }
+
+    /**
+     * Tests an add operation where the key is null.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testPrepareAddNullKey()
+    {
+        engine.prepareAdd(ROOT, null);
+    }
+
+    /**
+     * Tests an add operation where the key is null.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testPrepareAddEmptyKey()
+    {
+        engine.prepareAdd(ROOT, "");
+    }
+
+    /**
+     * Tests an add operation with an invalid path.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testPrepareAddInvalidPath()
+    {
+        engine.prepareAdd(ROOT, TEST_KEY + " an/invalid//path");
+    }
+
+    /**
+     * Tests an add operation with an invalid path: the path contains an
+     * attribute in the middle part.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testPrepareAddInvalidAttributePath()
+    {
+        engine.prepareAdd(ROOT, TEST_KEY + " a/path/with at an/attribute");
+    }
+
+    /**
+     * Tests an add operation with an invalid path: the path contains an
+     * attribute after a slash.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testPrepareAddInvalidAttributePath2()
+    {
+        engine.prepareAdd(ROOT, TEST_KEY + " a/path/with/@attribute");
+    }
+
+    /**
+     * Tests an add operation with an invalid path that starts with a slash.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testPrepareAddInvalidPathWithSlash()
+    {
+        engine.prepareAdd(ROOT, TEST_KEY + " /a/path/node");
+    }
+
+    /**
+     * Tests an add operation with an invalid path that contains multiple
+     * attribute components.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testPrepareAddInvalidPathMultipleAttributes()
+    {
+        engine.prepareAdd(ROOT, TEST_KEY + " an at attribute@path");
+    }
+
+    /**
+     * Helper method for testing the path nodes in the given add data object.
+     *
+     * @param data the data object to check
+     * @param expected an array with the expected path elements
+     * @param attr a flag if the new node is an attribute
+     */
+    private void checkAddPath(NodeAddData data, String[] expected, boolean attr)
+    {
+        assertSame("Wrong parent node", ROOT, data.getParent());
+        List<String> path = data.getPathNodes();
+        assertEquals("Incorrect number of path nodes", expected.length - 1,
+                path.size());
+        Iterator<String> it = path.iterator();
+        for (int idx = 0; idx < expected.length - 1; idx++)
+        {
+            assertEquals("Wrong node at position " + idx, expected[idx], it
+                    .next());
+        }
+        assertEquals("Wrong name of new node", expected[expected.length - 1],
+                data.getNewNodeName());
+        assertEquals("Incorrect attribute flag", attr, data.isAttribute());
+    }
+
+    /**
+     * Checks if the JXPath context's selectNodes() method was called as often
+     * as expected.
+     *
+     * @param expected the number of expected calls
+     */
+    protected void checkSelectCalls(int expected)
+    {
+        MockJXPathContext ctx = ((MockJXPathContextExpressionEngine) engine).getContext();
+        int calls = (ctx == null) ? 0 : ctx.selectInvocations;
+        assertEquals("Incorrect number of select calls", expected, calls);
+    }
+
+    /**
+     * A mock implementation of the JXPathContext class. This implementation
+     * will overwrite the <code>selectNodes()</code> method that is used by
+     * <code>XPathExpressionEngine</code> to count the invocations of this
+     * method.
+     */
+    static class MockJXPathContext extends JXPathContextReferenceImpl
+    {
+        int selectInvocations;
+
+        public MockJXPathContext(Object bean)
+        {
+            super(null, bean);
+        }
+
+        /**
+         * Dummy implementation of this method. If the passed in string is the
+         * test key, the root node will be returned in the list. Otherwise the
+         * return value is <b>null</b>.
+         */
+        @Override
+        public List<?> selectNodes(String xpath)
+        {
+            selectInvocations++;
+            if (TEST_KEY.equals(xpath))
+            {
+                List<ConfigurationNode> result = new ArrayList<ConfigurationNode>(1);
+                result.add(ROOT);
+                return result;
+            }
+            else
+            {
+                return null;
+            }
+        }
+    }
+
+    /**
+     * A special implementation of XPathExpressionEngine that overrides
+     * createContext() to return a mock context object.
+     */
+    static class MockJXPathContextExpressionEngine extends
+            XPathExpressionEngine
+    {
+        /** Stores the context instance. */
+        private MockJXPathContext context;
+
+        @Override
+        protected JXPathContext createContext(ConfigurationNode root, String key)
+        {
+            context = new MockJXPathContext(root);
+            return context;
+        }
+
+        /**
+         * Returns the context created by the last newContext() call.
+         *
+         * @return the current context
+         */
+        public MockJXPathContext getContext()
+        {
+            return context;
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/tree/xpath/TestXPathExpressionEngineInConfig.java b/src/test/java/org/apache/commons/configuration/tree/xpath/TestXPathExpressionEngineInConfig.java
new file mode 100644
index 0000000..4a3c0fb
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/tree/xpath/TestXPathExpressionEngineInConfig.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.tree.xpath;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.commons.configuration.XMLConfiguration;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * A test class for XPathExpressionEngine that tests the engine integrated into
+ * a hierarchical configuration.
+ *
+ * @author <a
+ *         href="http://commons.apache.org/configuration/team-list.html">Commons
+ *         Configuration team</a>
+ * @version $Id: TestXPathExpressionEngineInConfig.java 1226113 2011-12-31 15:46:53Z oheger $
+ */
+public class TestXPathExpressionEngineInConfig
+{
+    /** Constant for a test key. */
+    private static final String KEY = "test/expression/xpath";
+
+    /** Constant for a value for test properties. */
+    private static final String VALUE = "success";
+
+    /** The test configuration. */
+    private XMLConfiguration config;
+
+    @Before
+    public void setUp() throws Exception
+    {
+        config = new XMLConfiguration();
+        config.setExpressionEngine(new XPathExpressionEngine());
+    }
+
+    /**
+     * Tests whether an already existing property can be changed using
+     * setProperty().
+     */
+    @Test
+    public void testSetPropertyExisting()
+    {
+        config.addProperty(" " + KEY, "failure");
+        config.setProperty(KEY, VALUE);
+        assertEquals("Value not changed", VALUE, config.getString(KEY));
+    }
+
+    /**
+     * Tests setProperty() if the specified path partly exists.
+     */
+    @Test
+    public void testSetPropertyPartlyExisting()
+    {
+        final String testKey = KEY + "/sub";
+        config.addProperty(" " + KEY, "test");
+        config.setProperty(testKey, VALUE);
+        assertEquals("Value not set", VALUE, config.getString(testKey));
+    }
+
+    /**
+     * Tests whether setProperty() can be used to add a new attribute.
+     */
+    @Test
+    public void testSetPropertyNewAttribute()
+    {
+        final String keyAttr = KEY + "/@attr";
+        config.addProperty(" " + KEY, "test");
+        config.setProperty(keyAttr, VALUE);
+        assertEquals("Value not set", VALUE, config.getString(keyAttr));
+    }
+
+    /**
+     * Tests whether setProperty() can be used to create a completely new key.
+     */
+    @Test
+    public void testSetPropertyNewKey()
+    {
+        config.setProperty(KEY, VALUE);
+        assertEquals("Value not set", VALUE, config.getString(KEY));
+    }
+
+    /**
+     * Tests whether addProperty() can be used to create more complex
+     * hierarchical structures.
+     */
+    @Test
+    public void testAddPropertyComplexStructures()
+    {
+        config.addProperty("tables/table/name", "tasks");
+        config.addProperty("tables/table[last()]/@type", "system");
+        config.addProperty("tables/table[last()]/fields/field/name", "taskid");
+        config.addProperty("tables/table[last()]/fields/field[last()]/@type",
+                "int");
+        config.addProperty("tables table/name", "documents");
+        assertEquals("Wrong table 1", "tasks",
+                config.getString("tables/table[1]/name"));
+        assertEquals("Wrong table 2", "documents",
+                config.getString("tables/table[2]/name"));
+        assertEquals("Wrong field type", "int",
+                config.getString("tables/table[1]/fields/field[1]/@type"));
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/web/TestAppletConfiguration.java b/src/test/java/org/apache/commons/configuration/web/TestAppletConfiguration.java
new file mode 100644
index 0000000..0598139
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/web/TestAppletConfiguration.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.web;
+
+import static org.junit.Assert.fail;
+
+import java.applet.Applet;
+import java.util.Properties;
+
+import org.apache.commons.configuration.AbstractConfiguration;
+import org.apache.commons.configuration.BaseConfiguration;
+import org.apache.commons.configuration.MapConfiguration;
+import org.apache.commons.configuration.TestAbstractConfiguration;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test case for the {@link AppletConfiguration} class.
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: TestAppletConfiguration.java 1222465 2011-12-22 21:32:56Z oheger $
+ */
+public class TestAppletConfiguration extends TestAbstractConfiguration
+{
+    /** A flag whether tests with an applet can be run. */
+    boolean supportsApplet;
+
+    /**
+     * Initializes the tests. This implementation checks whether an applet can
+     * be used. Some environments, which do not support a GUI, don't allow
+     * creating an <code>Applet</code> instance. If we are in such an
+     * environment, some tests need to behave differently or be completely
+     * dropped.
+     */
+    @Before
+    public void setUp() throws Exception
+    {
+        try
+        {
+            new Applet();
+            supportsApplet = true;
+        }
+        catch (Exception ex)
+        {
+            // cannot use applets
+            supportsApplet = false;
+        }
+    }
+
+    @Override
+    protected AbstractConfiguration getConfiguration()
+    {
+        final Properties parameters = new Properties();
+        parameters.setProperty("key1", "value1");
+        parameters.setProperty("key2", "value2");
+        parameters.setProperty("list", "value1, value2");
+        parameters.setProperty("listesc", "value1\\,value2");
+
+        if (supportsApplet)
+        {
+            Applet applet = new Applet()
+            {
+                /**
+                 * Serial version UID.
+                 */
+                private static final long serialVersionUID = 1L;
+
+                @Override
+                public String getParameter(String key)
+                {
+                    return parameters.getProperty(key);
+                }
+
+                @Override
+                public String[][] getParameterInfo()
+                {
+                    return new String[][]
+                    {
+                    { "key1", "String", "" },
+                    { "key2", "String", "" },
+                    { "list", "String[]", "" },
+                    { "listesc", "String", "" } };
+                }
+            };
+
+            return new AppletConfiguration(applet);
+        }
+        else
+        {
+            return new MapConfiguration(parameters);
+        }
+    }
+
+    @Override
+    protected AbstractConfiguration getEmptyConfiguration()
+    {
+        if (supportsApplet)
+        {
+            return new AppletConfiguration(new Applet());
+        }
+        else
+        {
+            return new BaseConfiguration();
+        }
+    }
+
+    @Override
+    @Test
+    public void testAddPropertyDirect()
+    {
+        if (supportsApplet)
+        {
+            try
+            {
+                super.testAddPropertyDirect();
+                fail("addPropertyDirect should throw an UnsupportedException");
+            }
+            catch (UnsupportedOperationException e)
+            {
+                // ok
+            }
+        }
+    }
+
+    @Override
+    @Test
+    public void testClearProperty()
+    {
+        if (supportsApplet)
+        {
+            try
+            {
+                super.testClearProperty();
+                fail("testClearProperty should throw an UnsupportedException");
+            }
+            catch (UnsupportedOperationException e)
+            {
+                // ok
+            }
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/web/TestServletConfiguration.java b/src/test/java/org/apache/commons/configuration/web/TestServletConfiguration.java
new file mode 100644
index 0000000..6a05e7b
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/web/TestServletConfiguration.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.web;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.http.HttpServlet;
+
+import org.apache.commons.configuration.AbstractConfiguration;
+import org.apache.commons.configuration.TestAbstractConfiguration;
+import org.junit.Test;
+
+import com.mockobjects.servlet.MockServletConfig;
+
+/**
+ * Test case for the {@link ServletConfiguration} class.
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: TestServletConfiguration.java 1222465 2011-12-22 21:32:56Z oheger $
+ */
+public class TestServletConfiguration extends TestAbstractConfiguration
+{
+    @Override
+    protected AbstractConfiguration getConfiguration()
+    {
+        final MockServletConfig config = new MockServletConfig();
+        config.setInitParameter("key1", "value1");
+        config.setInitParameter("key2", "value2");
+        config.setInitParameter("list", "value1, value2");
+        config.setInitParameter("listesc", "value1\\,value2");
+
+        Servlet servlet = new HttpServlet() {
+            /**
+             * Serial version UID.
+             */
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public ServletConfig getServletConfig()
+            {
+                return config;
+            }
+        };
+
+        return new ServletConfiguration(servlet);
+    }
+
+    @Override
+    protected AbstractConfiguration getEmptyConfiguration()
+    {
+        return new ServletConfiguration(new MockServletConfig());
+    }
+
+    @Override
+    @Test(expected = UnsupportedOperationException.class)
+    public void testAddPropertyDirect()
+    {
+        super.testAddPropertyDirect();
+    }
+
+    @Override
+    @Test(expected = UnsupportedOperationException.class)
+    public void testClearProperty()
+    {
+       super.testClearProperty();
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/web/TestServletContextConfiguration.java b/src/test/java/org/apache/commons/configuration/web/TestServletContextConfiguration.java
new file mode 100644
index 0000000..8c16bae
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/web/TestServletContextConfiguration.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.web;
+
+import java.util.Enumeration;
+import java.util.Properties;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServlet;
+
+import org.apache.commons.configuration.AbstractConfiguration;
+import org.apache.commons.configuration.TestAbstractConfiguration;
+import org.junit.Test;
+
+import com.mockobjects.servlet.MockServletConfig;
+import com.mockobjects.servlet.MockServletContext;
+
+/**
+ * Test case for the {@link ServletContextConfiguration} class.
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: TestServletContextConfiguration.java 1222465 2011-12-22 21:32:56Z oheger $
+ */
+public class TestServletContextConfiguration extends TestAbstractConfiguration
+{
+    @Override
+    protected AbstractConfiguration getConfiguration()
+    {
+        final Properties parameters = new Properties();
+        parameters.setProperty("key1", "value1");
+        parameters.setProperty("key2", "value2");
+        parameters.setProperty("list", "value1, value2");
+        parameters.setProperty("listesc", "value1\\,value2");
+
+        // create a servlet context
+        ServletContext context = new MockServletContext()
+        {
+            @Override
+            public String getInitParameter(String key)
+            {
+                return parameters.getProperty(key);
+            }
+
+            @Override
+            public Enumeration<?> getInitParameterNames()
+            {
+                return parameters.keys();
+            }
+        };
+
+        // create a servlet config
+        final MockServletConfig config = new MockServletConfig();
+        config.setServletContext(context);
+
+        // create a servlet
+        Servlet servlet = new HttpServlet()
+        {
+            /**
+             * Serial version UID.
+             */
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public ServletConfig getServletConfig()
+            {
+                return config;
+            }
+        };
+
+        return new ServletContextConfiguration(servlet);
+    }
+
+    @Override
+    protected AbstractConfiguration getEmptyConfiguration()
+    {
+        // create a servlet context
+        ServletContext context = new MockServletContext()
+        {
+            @Override
+            public Enumeration<?> getInitParameterNames()
+            {
+                return new Properties().keys();
+            }
+        };
+
+        return new ServletContextConfiguration(context);
+    }
+
+    @Override
+    @Test(expected = UnsupportedOperationException.class)
+    public void testAddPropertyDirect()
+    {
+        super.testAddPropertyDirect();
+    }
+
+    @Override
+    @Test(expected = UnsupportedOperationException.class)
+    public void testClearProperty()
+    {
+        super.testClearProperty();
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/web/TestServletFilterConfiguration.java b/src/test/java/org/apache/commons/configuration/web/TestServletFilterConfiguration.java
new file mode 100644
index 0000000..1b01fc7
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/web/TestServletFilterConfiguration.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.web;
+
+import java.util.Enumeration;
+import java.util.Properties;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+
+import org.apache.commons.configuration.AbstractConfiguration;
+import org.apache.commons.configuration.TestAbstractConfiguration;
+import org.junit.Test;
+
+/**
+ * Test case for the {@link ServletFilterConfiguration} class.
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: TestServletFilterConfiguration.java 1222465 2011-12-22 21:32:56Z oheger $
+ */
+public class TestServletFilterConfiguration extends TestAbstractConfiguration
+{
+    @Override
+    protected AbstractConfiguration getConfiguration()
+    {
+        MockFilterConfig config = new MockFilterConfig();
+        config.setInitParameter("key1", "value1");
+        config.setInitParameter("key2", "value2");
+        config.setInitParameter("list", "value1, value2");
+        config.setInitParameter("listesc", "value1\\,value2");
+
+        return new ServletFilterConfiguration(config);
+    }
+
+    @Override
+    protected AbstractConfiguration getEmptyConfiguration()
+    {
+        return new ServletFilterConfiguration(new MockFilterConfig());
+    }
+
+    private class MockFilterConfig implements FilterConfig
+    {
+        private Properties parameters = new Properties();
+
+        public String getFilterName()
+        {
+            return null;
+        }
+
+        public ServletContext getServletContext()
+        {
+            return null;
+        }
+
+        public String getInitParameter(String key)
+        {
+            return parameters.getProperty(key);
+        }
+
+        public Enumeration<?> getInitParameterNames()
+        {
+            return parameters.keys();
+        }
+
+        public void setInitParameter(String key, String value)
+        {
+            parameters.setProperty(key, value);
+        }
+    }
+
+    @Override
+    @Test(expected = UnsupportedOperationException.class)
+    public void testAddPropertyDirect()
+    {
+        super.testAddPropertyDirect();
+    }
+
+    @Override
+    @Test(expected = UnsupportedOperationException.class)
+    public void testClearProperty()
+    {
+        super.testClearProperty();
+    }
+}
diff --git a/src/test/java/org/apache/commons/configuration/web/TestServletRequestConfiguration.java b/src/test/java/org/apache/commons/configuration/web/TestServletRequestConfiguration.java
new file mode 100644
index 0000000..2aa4d5f
--- /dev/null
+++ b/src/test/java/org/apache/commons/configuration/web/TestServletRequestConfiguration.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.configuration.web;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletRequest;
+
+import org.apache.commons.configuration.AbstractConfiguration;
+import org.apache.commons.configuration.BaseConfiguration;
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationMap;
+import org.apache.commons.configuration.TestAbstractConfiguration;
+import org.junit.Test;
+
+import com.mockobjects.servlet.MockHttpServletRequest;
+
+/**
+ * Test case for the {@link ServletRequestConfiguration} class.
+ *
+ * @author Emmanuel Bourg
+ * @version $Id: TestServletRequestConfiguration.java 1222465 2011-12-22 21:32:56Z oheger $
+ */
+public class TestServletRequestConfiguration extends TestAbstractConfiguration
+{
+    @Override
+    protected AbstractConfiguration getConfiguration()
+    {
+        final Configuration configuration = new BaseConfiguration();
+        ((BaseConfiguration) configuration).setListDelimiter('\0');
+        configuration.setProperty("key1", "value1");
+        configuration.setProperty("key2", "value2");
+        configuration.addProperty("list", "value1");
+        configuration.addProperty("list", "value2");
+        configuration.addProperty("listesc", "value1\\,value2");
+
+        return createConfiguration(configuration);
+    }
+
+    @Override
+    protected AbstractConfiguration getEmptyConfiguration()
+    {
+        ServletRequest request = new MockHttpServletRequest()
+        {
+            @Override
+            public String getParameter(String key)
+            {
+                return null;
+            }
+
+            @Override
+            public Map<?, ?> getParameterMap()
+            {
+                return new HashMap<Object, Object>();
+            }
+        };
+
+        return new ServletRequestConfiguration(request);
+    }
+
+    /**
+     * Returns a new servlet request configuration that is backed by the passed
+     * in configuration.
+     *
+     * @param base the configuration with the underlying values
+     * @return the servlet request configuration
+     */
+    private ServletRequestConfiguration createConfiguration(final Configuration base)
+    {
+        ServletRequest request = new MockHttpServletRequest()
+        {
+            @Override
+            public String[] getParameterValues(String key)
+            {
+                return base.getStringArray(key);
+            }
+
+            @Override
+            public Map<?, ?> getParameterMap()
+            {
+                return new ConfigurationMap(base);
+            }
+        };
+
+        return new ServletRequestConfiguration(request);
+    }
+
+    @Override
+    @Test(expected = UnsupportedOperationException.class)
+    public void testAddPropertyDirect()
+    {
+        super.testAddPropertyDirect();
+    }
+
+    @Override
+    @Test(expected = UnsupportedOperationException.class)
+    public void testClearProperty()
+    {
+        super.testClearProperty();
+    }
+
+    /**
+     * Tests a list with elements that contain an escaped list delimiter.
+     */
+    @Test
+    public void testListWithEscapedElements()
+    {
+        String[] values = { "test1", "test2\\,test3", "test4\\,test5" };
+        String listKey = "test.list";
+
+        BaseConfiguration config = new BaseConfiguration();
+        config.setListDelimiter('\0');
+        config.addProperty(listKey, values);
+
+        assertEquals("Wrong number of list elements", values.length, config.getList(listKey).size());
+
+        Configuration c = createConfiguration(config);
+        List<?> v = c.getList(listKey);
+
+        assertEquals("Wrong number of elements in list", values.length, v.size());
+
+        for (int i = 0; i < values.length; i++)
+        {
+            assertEquals("Wrong value at index " + i, values[i].replaceAll("\\\\", ""), v.get(i));
+        }
+    }
+}
diff --git a/src/test/resources/01/testMultiConfiguration_1001.xml b/src/test/resources/01/testMultiConfiguration_1001.xml
new file mode 100644
index 0000000..6e96ee9
--- /dev/null
+++ b/src/test/resources/01/testMultiConfiguration_1001.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<configuration>
+  <colors>
+    <background>#808080</background>
+    <text>#000000</text>
+    <header>#008000</header>
+    <link normal="#000080" visited="#800080"/>
+    <default>${colors.header}</default>
+  </colors>
+  <rowsPerPage>15</rowsPerPage>
+  <buttons>
+    <name>OK,Cancel,Help</name>
+  </buttons>
+  <numberFormat pattern="###\,###.##"/>
+  <Channels>
+    <Channel id="1">
+      <Name>My Channel</Name>
+    </Channel>
+    <Channel id="2">
+      <MoreChannelData>more test 2 data</MoreChannelData>
+    </Channel>
+  </Channels>
+</configuration>
diff --git a/src/test/resources/catalog.xml b/src/test/resources/catalog.xml
new file mode 100644
index 0000000..4d695c9
--- /dev/null
+++ b/src/test/resources/catalog.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
+
+  <public publicId="-//Apache//DTD Resolver Test V1.0//EN"
+          uri="resolver.dtd"/>
+  <rewriteSystem systemIdStartString="http://java.sun.com/dtd/"
+                 rewritePrefix="./"/>
+  <rewriteSystem systemIdStartString="http://commons.apache.org/" rewritePrefix="./"/>
+</catalog>
\ No newline at end of file
diff --git a/src/test/resources/catalog2.xml b/src/test/resources/catalog2.xml
new file mode 100644
index 0000000..a7d6de5
--- /dev/null
+++ b/src/test/resources/catalog2.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
+
+  <public publicId="-//Apache//DTD Resolver Test V1.0//EN"
+          uri="resolver.dtd"/>
+  <rewriteSystem systemIdStartString="http://java.sun.com/dtd/"
+                 rewritePrefix="./"/>
+  <rewriteSystem systemIdStartString="http://commons.apache.org/"
+                 rewritePrefix="file://${sys:user.dir}/target/test-classes/"/>
+</catalog>
\ No newline at end of file
diff --git a/src/test/resources/config/deep/deepinclude.properties b/src/test/resources/config/deep/deepinclude.properties
new file mode 100644
index 0000000..fe58457
--- /dev/null
+++ b/src/test/resources/config/deep/deepinclude.properties
@@ -0,0 +1,15 @@
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+deepinclude=true
diff --git a/src/test/resources/config/deep/deeptest.properties b/src/test/resources/config/deep/deeptest.properties
new file mode 100644
index 0000000..12991d1
--- /dev/null
+++ b/src/test/resources/config/deep/deeptest.properties
@@ -0,0 +1,17 @@
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+include=deepinclude.properties
+
+deeptest=true
diff --git a/src/test/resources/config/deep/deeptestinvalid.properties b/src/test/resources/config/deep/deeptestinvalid.properties
new file mode 100644
index 0000000..5c92dd8
--- /dev/null
+++ b/src/test/resources/config/deep/deeptestinvalid.properties
@@ -0,0 +1,19 @@
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+# Tries to include a non existing file
+include=nonexisting.properties
+
+deeptestinvalid=true
diff --git a/src/test/resources/config/deep/test.properties b/src/test/resources/config/deep/test.properties
new file mode 100644
index 0000000..5abfed8
--- /dev/null
+++ b/src/test/resources/config/deep/test.properties
@@ -0,0 +1,15 @@
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+somekey=somevalue
\ No newline at end of file
diff --git a/src/test/resources/config/deep/testEqualDeep.properties b/src/test/resources/config/deep/testEqualDeep.properties
new file mode 100644
index 0000000..b5d818f
--- /dev/null
+++ b/src/test/resources/config/deep/testEqualDeep.properties
@@ -0,0 +1,26 @@
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+property.a = a
+property.b = b
+property.c = 100
+
+#
+# Value set twice
+property.a = aa
+
+clear.property = delete me
+
+existing.property = i exist
+
diff --git a/src/test/resources/config/deep/testFileFromClasspath.xml b/src/test/resources/config/deep/testFileFromClasspath.xml
new file mode 100644
index 0000000..52b8216
--- /dev/null
+++ b/src/test/resources/config/deep/testFileFromClasspath.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<configuration>
+  <properties config-name="propConf" fileName="testEqual.properties"/>
+  <properties config-name="propConfDeep" fileName="testEqualDeep.properties"/>
+</configuration>
+
diff --git a/src/test/resources/config/test.properties b/src/test/resources/config/test.properties
new file mode 100644
index 0000000..74ea998
--- /dev/null
+++ b/src/test/resources/config/test.properties
@@ -0,0 +1,25 @@
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+boolean=true
+overwrite=80
+key=jndivalue
+key2=jndivalue2
+jndi=only_jndi
+byte=10
+double=10.25
+float=20.25
+integer=10
+long=1000000
+short=1
\ No newline at end of file
diff --git a/src/test/resources/configA.xml b/src/test/resources/configA.xml
new file mode 100644
index 0000000..5f6d6c9
--- /dev/null
+++ b/src/test/resources/configA.xml
@@ -0,0 +1,19 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<configuration>  
+  <property name="config" value="100"/>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/configB.xml b/src/test/resources/configB.xml
new file mode 100644
index 0000000..56e53f1
--- /dev/null
+++ b/src/test/resources/configB.xml
@@ -0,0 +1,19 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<configuration>
+  <stuff>test</stuff>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/dataset.xml b/src/test/resources/dataset.xml
new file mode 100644
index 0000000..6430406
--- /dev/null
+++ b/src/test/resources/dataset.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0"?>
+<!DOCTYPE dataset SYSTEM "dataset.dtd">
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<!-- dbunit DataSet for the TestDatabaseConfiguration test -->
+<dataset>
+
+    <table name="configuration">
+        <column>key</column>
+        <column>value</column>
+        <row>
+            <value>key1</value>
+            <value>value1</value>
+        </row>
+        <row>
+            <value>key2</value>
+            <value>value2</value>
+        </row>
+        <row>
+            <value>keyMulti</value>
+            <value>a;b;c</value>
+        </row>
+    </table>
+
+    <table name="configurations">
+        <column>name</column>
+        <column>key</column>
+        <column>value</column>
+        <row>
+            <value>test</value>
+            <value>key1</value>
+            <value>value1</value>
+        </row>
+        <row>
+            <value>test</value>
+            <value>key2</value>
+            <value>value2</value>
+        </row>
+    </table>
+    <table name="configurationList">
+		<column>id</column>
+        <column>key</column>
+        <column>value</column>
+        <row>
+			<value>1</value>
+            <value>key3</value>
+            <value>value1</value>
+        </row>		
+        <row>
+			<value>2</value>
+            <value>key3</value>
+            <value>value2</value>
+        </row>			
+        <row>
+			<value>3</value>			
+            <value>key3</value>
+            <value>value3</value>
+        </row>			
+    </table>	
+
+</dataset>
diff --git a/src/test/resources/include-interpol.properties b/src/test/resources/include-interpol.properties
new file mode 100644
index 0000000..8e4c167
--- /dev/null
+++ b/src/test/resources/include-interpol.properties
@@ -0,0 +1,18 @@
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+include.interpol.loaded = true
+
+
+
diff --git a/src/test/resources/include.properties b/src/test/resources/include.properties
new file mode 100644
index 0000000..14f7a58
--- /dev/null
+++ b/src/test/resources/include.properties
@@ -0,0 +1,19 @@
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+include.loaded = true
+
+packages = packageb, packagec
+
+
diff --git a/src/test/resources/jndi.properties b/src/test/resources/jndi.properties
new file mode 100644
index 0000000..87771b2
--- /dev/null
+++ b/src/test/resources/jndi.properties
@@ -0,0 +1,17 @@
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+java.naming.factory.initial=org.osjava.jndi.PropertiesFactory
+org.osjava.jndi.root=classpath://config
+org.osjava.jndi.delimiter=/
diff --git a/src/test/resources/log4j-test.xml b/src/test/resources/log4j-test.xml
new file mode 100644
index 0000000..ad50c1d
--- /dev/null
+++ b/src/test/resources/log4j-test.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+  <appender name="console" class="org.apache.log4j.ConsoleAppender">
+    <param name="Target" value="System.out"/>
+    <layout class="org.apache.log4j.PatternLayout">
+      <param name="ConversionPattern" value="%-5p %c{1} - %m%n"/>
+    </layout>
+  </appender>
+
+  <root>
+    <priority value ="debug" />
+    <appender-ref ref="console" />
+  </root>
+
+</log4j:configuration>
\ No newline at end of file
diff --git a/src/test/resources/resolver.dtd b/src/test/resources/resolver.dtd
new file mode 100644
index 0000000..67bb0e6
--- /dev/null
+++ b/src/test/resources/resolver.dtd
@@ -0,0 +1,19 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<!ELEMENT test (foo|bar)+>
+<!ELEMENT foo EMPTY>
+<!ELEMENT bar EMPTY>
\ No newline at end of file
diff --git a/src/test/resources/sample.xml b/src/test/resources/sample.xml
new file mode 100644
index 0000000..fc5b61c
--- /dev/null
+++ b/src/test/resources/sample.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<Employees xmlns="http://commons.apache.org/employee"
+           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+           xsi:schemaLocation="http://commons.apache.org/employee http://commons.apache.org/sample.xsd">
+  <Employee>
+    <SSN>555121211</SSN>
+    <Name>John Doe</Name>
+    <DateOfBirth>1975-05-15</DateOfBirth>
+    <EmployeeType>Exempt</EmployeeType>
+    <Salary>100000</Salary>
+  </Employee>
+</Employees>
\ No newline at end of file
diff --git a/src/test/resources/sample.xsd b/src/test/resources/sample.xsd
new file mode 100644
index 0000000..aeda543
--- /dev/null
+++ b/src/test/resources/sample.xsd
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified"
+           targetNamespace="http://commons.apache.org/employee"
+           xmlns:xs="http://www.w3.org/2001/XMLSchema">
+  <xs:element name="Employees" xmlns="http://commons.apache.org/employee">
+    <xs:complexType>
+      <xs:sequence>
+        <xs:element name="Employee">
+          <xs:complexType>
+            <xs:sequence>
+            <xs:element type="xs:string" name="SSN" minOccurs="0"/>
+            <xs:element type="xs:string" name="Name" minOccurs="0"/>
+            <xs:element type="xs:string" name="DateOfBirth" minOccurs="0"/>
+            <xs:element type="xs:string" name="EmployeeType" minOccurs="0"/>
+            <xs:element type="xs:string" name="Salary" minOccurs="0"/>
+            </xs:sequence>
+          </xs:complexType>
+        </xs:element>
+      </xs:sequence>
+    </xs:complexType>
+  </xs:element>
+</xs:schema>
\ No newline at end of file
diff --git a/src/test/resources/sample_1001.xml b/src/test/resources/sample_1001.xml
new file mode 100644
index 0000000..a0df635
--- /dev/null
+++ b/src/test/resources/sample_1001.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<Employees xmlns="http://commons.apache.org/employee"
+           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+           xsi:schemaLocation="http://commons.apache.org/employee http://commons.apache.org/sample.xsd">
+  <Employee>
+    <Name>Jane Doe</Name>
+  </Employee>
+</Employees>
\ No newline at end of file
diff --git a/src/test/resources/test.ini b/src/test/resources/test.ini
new file mode 100644
index 0000000..070e68c
--- /dev/null
+++ b/src/test/resources/test.ini
@@ -0,0 +1,17 @@
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+; Test ini file to be included by a configuration definition
+[testini]
+loaded=yes
diff --git a/src/test/resources/test.plist b/src/test/resources/test.plist
new file mode 100644
index 0000000..60a2a76
--- /dev/null
+++ b/src/test/resources/test.plist
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+{
+    simple-string = string1;
+    quoted-string = "string2";
+    quoted-string2 = "this is a string";
+    "complex-string" = "this is a \"complex\" string {(=,;)}";
+
+    // This is a test single-line comment
+    array = ( "value1", "value2", "value3" );
+
+    empty-array = ();
+
+    nested-arrays = ( (a , b) , (c, d) );
+
+    dictionary-array =
+    (
+        { foo = bar },
+        { key = value }
+    )
+
+    dictionary =
+    {
+        foo1 = bar1;
+        foo2 = bar2;
+    }
+
+    empty-dictionary = { };
+
+    nested-dictionaries =
+    {
+        foo =
+        {
+            bar =
+            {
+                "key" = "value";
+            }
+        }
+    }
+
+    date = <*D2002-03-22 11:30:00 +0100>;
+
+    data = <666F6f 20 626172>;
+
+    empty-data = < >;
+}
diff --git a/src/test/resources/test.plist.xml b/src/test/resources/test.plist.xml
new file mode 100644
index 0000000..c706bde
--- /dev/null
+++ b/src/test/resources/test.plist.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0"?>
+<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<plist version="1.0">
+    <dict>
+
+        <key>string</key>
+        <string>value1</string>
+
+        <key>integer</key>
+        <integer>12345678900</integer>
+
+        <key>real</key>
+        <real>-123.45E-1</real>
+
+        <key>boolean1</key>
+        <true/>
+
+        <key>boolean2</key>
+        <false/>
+
+        <key>date</key>
+        <date>2005-01-01T12:00:00Z</date>
+
+        <key>date-gnustep</key>
+        <date>2002-03-22 11:30:00 +0100</date>
+
+        <key>data</key>
+        <data>RHJhY28gRG9ybWllbnMgTnVucXVhbSBUaXRpbGxhbmR1cw==</data>
+
+        <key>array</key>
+        <array>
+            <string>value1</string>
+            <string>value2</string>
+            <string>value3</string>
+        </array>
+
+        <key>nested-array</key>
+        <array>
+            <array>
+                <string>a</string>
+                <string>b</string>
+            </array>
+            <array>
+                <string>c</string>
+                <string>d</string>
+            </array>
+        </array>
+
+        <key>dictionary-array</key>
+        <array>
+            <dict>
+                <key>foo</key>
+                <string>bar</string>
+            </dict>
+            <dict>
+                <key>key</key>
+                <string>value</string>
+            </dict>
+        </array>
+
+        <key>dictionary</key>
+        <dict>
+            <key>key1</key>
+            <string>value1</string>
+            <key>key2</key>
+            <string>value2</string>
+            <key>key3</key>
+            <string>value3</string>
+        </dict>
+
+        <key>nested</key>
+        <dict>
+            <key>node1</key>
+            <dict>
+                <key>node2</key>
+                <dict>
+                    <key>node3</key>
+                    <string>value</string>
+                </dict>
+            </dict>
+        </dict>
+
+        <key>empty-dictionary</key>
+        <dict/>
+
+    </dict>
+</plist>
diff --git a/src/test/resources/test.properties b/src/test/resources/test.properties
new file mode 100644
index 0000000..491742f
--- /dev/null
+++ b/src/test/resources/test.properties
@@ -0,0 +1,126 @@
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+configuration.loaded = true
+
+packages = packagea
+propertyInOrder = test.properties
+
+include = include.properties
+
+include.file = include-interpol.properties
+include = ${include.file}
+
+test.unescape = This \n string \t contains \" escaped \\ character\u0073
+test.unescape.list-separator = This string contains \, an escaped list separator
+
+#
+# Other test properties
+#
+
+test.equals = value=one
+
+test.empty =
+
+test.mixed.array = a
+test.mixed.array = b, c, d
+
+test.multilines = This is a value spread out across several adjacent \
+                  natural lines by escaping the line terminator with \
+                  a backslash character.
+
+#
+# Test a property that uses a previous property
+#
+
+base = base
+base.reference = ${base}extra
+base.reference.array = ${base}extra
+base.reference.array = ${base}extra
+
+#
+# Non String Properties
+#
+
+test.boolean       = true
+test.boolean.array = false
+test.boolean.array = true
+
+test.byte       = 10
+test.byte.array = 20
+test.byte.array = 30
+
+test.double       = 10.25
+test.double.array = 20.35
+test.double.array = 30.45
+
+test.float       = 20.25
+test.float.array = 30.35
+test.float.array = 40.45
+
+test.integer       = 10
+test.integer.array = 20
+test.integer.array = 30
+
+test.long       = 1000000
+test.long.array = 2000000
+test.long.array = 3000000
+
+test.short       = 1
+test.short.array = 2
+test.short.array = 3
+
+test.overwrite = 1
+
+#
+# Test complex line ending escaping
+#
+
+test.path = C:\\path1\\
+test.path = C:\\path2\\
+test.path = C:\\path3\\\
+complex\\test\\
+
+#
+# Test for the comment lines
+#
+
+#comment = this is not a property but a comment line starting with '#'
+!comment = this is not a property but a comment line starting with '!'
+
+#
+# Tests for the key/value separators ('=', ':' or white space, escaped or not)
+#
+
+test.separator\=in.key = foo
+test.separator\:in.key = bar
+test.separator\	in.key = foo
+test.separator\
in.key = bar
+test.separator\ in.key = foo
+
+test.separator.equal = foo
+test.separator.colon : foo
+test.separator.tab	foo
+test.separator.formfeed
foo
+test.separator.whitespace foo
+test.separator.no.space=foo
+
+# Tests for backslash escaping in lists
+test.share1 = \\\\\\\\share1a, \\\\\\\\share1b
+test.share2 = \\\\share2a
+test.share2 = \\\\share2b
+test.share3 = \\\\\\\\share3a\\\\\\\\,\\\\\\\\share3b\\
+
+# This is a foot comment
+
diff --git a/src/test/resources/test.properties.xml b/src/test/resources/test.properties.xml
new file mode 100644
index 0000000..f6d10d2
--- /dev/null
+++ b/src/test/resources/test.properties.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<properties>
+  <comment>Description of the property list</comment>
+  <entry key="key1">value1</entry>
+  <entry key="key2">value2</entry>
+  <entry key="key3">value3</entry>
+</properties>
diff --git a/src/test/resources/test.xml b/src/test/resources/test.xml
new file mode 100644
index 0000000..4f84463
--- /dev/null
+++ b/src/test/resources/test.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<!-- Test file for XMLConfiguration -->
+<testconfig>
+    <element>value</element>
+    <element2>
+        <subelement>
+            <subsubelement>I'm complex!</subsubelement>
+        </subelement>
+    </element2>
+    <element3 name="foo">value</element3>
+    <test>
+        <comment><!-- this value is commented --></comment>
+        <cdata><![CDATA[<cdata value>]]></cdata>
+        <entity name="foo"bar">1<2</entity>
+    </test>
+    <mean>This is
+<![CDATA[ A long story...]]>
+      <submean>really complex structure</submean>
+And even longer.
+    </mean>
+
+    <!-- non string properties -->
+    <test>
+        <short>8</short>
+    </test>
+	
+	<!-- list properties -->
+	<list>
+		<item name="one">one</item>
+		<item>two</item>
+	</list>
+	<list>
+		<item name="three">three</item>
+		<item>four</item>
+		<sublist>
+			<item>five</item>
+			<item>six</item>
+		</sublist>
+	</list>
+    
+    <!-- Comma delimited lists -->
+    <split>
+      <list1>a,b,c</list1>
+      <list2>a\,b\,c</list2>
+      <list3 values="a,b,c"/>
+      <list4 values="a\,b\,c"/>
+    </split>
+
+	<!-- clear tests -->
+	<clear>
+		<element>value</element>
+		<element2 id="element2">value</element2>
+		<comment><!-- this value is commented --></comment>
+		<cdata><![CDATA[<cdata value>]]></cdata>
+		<list>
+			<item id="1">one</item>
+			<item>two</item>
+			<item id="3">three</item>
+			<item>four</item>
+		</list>
+		<list>
+			<item>one</item>
+			<item id="2">two</item>
+			<item>three</item>
+			<item id="4">four</item>
+		</list>
+	</clear>
+    
+    <!-- Complex property names -->
+    <complexNames>
+      <my.elem>Name with dot
+        <sub.elem>Another dot</sub.elem>
+      </my.elem>
+    </complexNames>
+    
+    <!-- An empty element. This should occur in the configuration with an
+         empty string as value.
+    -->
+    <empty/>
+    
+    <!-- List nodes with attributes -->
+    <attrList>
+      <a name="x">ABC</a>
+      <a name="y">1,2,3</a>
+      <a name="u" test="yes">value1,value2</a>
+    </attrList>
+
+    <!-- An attribute with multiple values and escape characters for testing
+         splitting when delimiter parsing is disabled.
+    -->
+    <expressions value="a \|\| (b && c)|!d"
+      value2="a,b|c"/>
+
+    <!-- Tests for handling of spaces -->
+    <space xml:space="preserve">
+      <blanc> </blanc>
+      <stars> * * </stars>
+      <description xml:space="default">     Some text      </description>
+      <testInvalid xml:space="invalid">     Some other text </testInvalid>
+    </space>
+    <spaceElement xml:space="preserve"> preserved </spaceElement>
+</testconfig>
diff --git a/src/test/resources/test2.plist.xml b/src/test/resources/test2.plist.xml
new file mode 100644
index 0000000..778a6b4
--- /dev/null
+++ b/src/test/resources/test2.plist.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<plist version="1.0">
+  <array>
+    <array>
+      <string>http://www.google.com/search?hl=en&client=safari&rls=en&q=RFC822MessageDatasPboardType&btnG=Search&aq=f&oq=&aqi=</string>
+    </array>
+    <array>
+      <string>RFC822MessageDatasPboardType - Google Search</string>
+    </array>
+  </array>
+</plist>
+
diff --git a/src/test/resources/test2.properties b/src/test/resources/test2.properties
new file mode 100644
index 0000000..eadcc99
--- /dev/null
+++ b/src/test/resources/test2.properties
@@ -0,0 +1,59 @@
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+configuration.loaded = true
+
+packages = override.packages
+propertyInOrder=test2.properties
+
+
+#
+# Other test properties
+#
+
+test.equals = value=one
+
+test.empty=
+
+#
+# Non String Properties
+#
+
+test.boolean       = true
+test.boolean.array = false
+test.boolean.array = true
+
+test.byte       = 10
+test.byte.array = 20
+test.byte.array = 30
+
+test.double       = 10.25
+test.double.array = 20.35
+test.double.array = 30.45
+
+test.float       = 20.25
+test.float.array = 30.35
+test.float.array = 40.45
+
+test.integer       = 10
+test.integer.array = 20
+test.integer.array = 30
+
+test.long       = 1000000
+test.long.array = 2000000
+test.long.array = 3000000
+
+test.short       = 1
+test.short.array = 2
+test.short.array = 3
diff --git a/src/test/resources/testClasspath.properties b/src/test/resources/testClasspath.properties
new file mode 100644
index 0000000..0566098
--- /dev/null
+++ b/src/test/resources/testClasspath.properties
@@ -0,0 +1,67 @@
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+configuration.loaded = true
+
+packages = packagea
+
+include = include.properties
+
+#
+# Other test properties
+#
+
+test.equals = value=one
+
+test.empty=
+
+#
+# Test a property that uses a previous property
+#
+base=base
+base.reference=${base}extra
+
+#
+# Non String Properties
+#
+
+test.boolean       = true
+test.boolean.array = false
+test.boolean.array = true
+
+test.byte       = 10
+test.byte.array = 20
+test.byte.array = 30
+
+test.double       = 10.25
+test.double.array = 20.35
+test.double.array = 30.45
+
+test.float       = 20.25
+test.float.array = 30.35
+test.float.array = 40.45
+
+test.integer       = 10
+test.integer.array = 20
+test.integer.array = 30
+
+test.long       = 1000000
+test.long.array = 2000000
+test.long.array = 3000000
+
+test.short       = 1
+test.short.array = 2
+test.short.array = 3
+
+test.overwrite       = 1
\ No newline at end of file
diff --git a/src/test/resources/testComplexInitialization.xml b/src/test/resources/testComplexInitialization.xml
new file mode 100644
index 0000000..dc3999c
--- /dev/null
+++ b/src/test/resources/testComplexInitialization.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true" forceReloadCheck="true"
+      ignoreReloadExceptions="true">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.OverrideCombiner"/>
+      <expressionEngine config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <combiner>
+      <override>
+        <list-nodes>
+          <node>table</node>
+          <node>list</node>
+        </list-nodes>
+      </override>
+    </combiner>
+  </header>
+  <system/>
+  <properties fileName="test.properties" throwExceptionOnMissing="true"
+    config-name="properties">
+    <reloadingStrategy config-class="org.apache.commons.configuration.reloading.FileChangedReloadingStrategy"
+      refreshDelay="10000"/>
+  </properties>
+  <!-- Fetch the file name from a variable -->
+  <xml fileName="${test_file_xml}" config-name="xml">
+    <expressionEngine config-class="org.apache.commons.configuration.tree.DefaultExpressionEngine"
+      propertyDelimiter="/" indexStart="[" indexEnd="]"/>
+  </xml>
+  <additional>
+    <xml config-name="combiner1" fileName="${test_file_combine}"/>  -->
+    <xml config-name="combiner2" fileName="testcombine2.xml"/>
+  </additional>
+</configuration>
diff --git a/src/test/resources/testConfigurationInterpolatorUpdate.xml b/src/test/resources/testConfigurationInterpolatorUpdate.xml
new file mode 100644
index 0000000..d857263
--- /dev/null
+++ b/src/test/resources/testConfigurationInterpolatorUpdate.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.OverrideCombiner"/>
+      <expressionEngine config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <providers>
+      <provider config-tag="test"
+                config-class="org.apache.commons.configuration.DefaultConfigurationBuilder$XMLConfigurationProvider"
+                configurationClass="org.apache.commons.configuration.TestDefaultConfigurationBuilder$ExtendedXMLConfiguration"/>
+    </providers>
+    <combiner>
+      <override>
+        <list-nodes>
+          <node>table</node>
+          <node>list</node>
+        </list-nodes>
+      </override>
+    </combiner>
+  </header>
+  <system/>
+  <properties fileName="test.properties" throwExceptionOnMissing="true"
+    config-name="properties">
+    <reloadingStrategy config-class="org.apache.commons.configuration.reloading.FileChangedReloadingStrategy"
+      refreshDelay="10000"/>
+  </properties>
+  <!-- Fetch the file name from a variable -->
+  <test fileName="${test_file_xml}" config-name="xml">
+    <expressionEngine config-class="org.apache.commons.configuration.tree.DefaultExpressionEngine"
+      propertyDelimiter="/" indexStart="[" indexEnd="]"/>
+  </test>
+  <additional>
+    <xml config-name="combiner1" fileName="${test_file_combine}"/>  -->
+    <xml config-name="combiner2" fileName="testcombine2.xml"/>
+  </additional>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testConfigurationProvider.xml b/src/test/resources/testConfigurationProvider.xml
new file mode 100644
index 0000000..cfd0038
--- /dev/null
+++ b/src/test/resources/testConfigurationProvider.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.OverrideCombiner"/>
+      <expressionEngine config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <providers>
+      <provider config-tag="test"
+                config-class="org.apache.commons.configuration.DefaultConfigurationBuilder$FileConfigurationProvider"/>
+    </providers>
+    <combiner>
+      <override>
+        <list-nodes>
+          <node>table</node>
+          <node>list</node>
+        </list-nodes>
+      </override>
+    </combiner>
+  </header>
+  <system/>
+  <properties fileName="test.properties" throwExceptionOnMissing="true"
+    config-name="properties">
+    <reloadingStrategy config-class="org.apache.commons.configuration.reloading.FileChangedReloadingStrategy"
+      refreshDelay="10000"/>
+  </properties>
+  <!-- Fetch the file name from a variable -->
+  <xml fileName="${test_file_xml}" config-name="xml">
+    <expressionEngine config-class="org.apache.commons.configuration.tree.DefaultExpressionEngine"
+      propertyDelimiter="/" indexStart="[" indexEnd="]"/>
+  </xml>
+  <additional>
+    <xml config-name="combiner1" fileName="${test_file_combine}"/>  -->
+    <xml config-name="combiner2" fileName="testcombine2.xml"/>
+  </additional>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testConfigurationXMLDocument.xml b/src/test/resources/testConfigurationXMLDocument.xml
new file mode 100644
index 0000000..7c017ca
--- /dev/null
+++ b/src/test/resources/testConfigurationXMLDocument.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Configuration test file that demonstrates usage of ConfigurationXMLDocument -->
+
+<configuration>
+  <additional>
+    <hierarchicalXml fileName="testHierarchicalXMLConfiguration.xml" at="database"/>
+    <hierarchicalXml fileName="testDigesterConfigurationInclude1.xml" at="database.tables"/>
+    <hierarchicalXml fileName="testDigesterCreateObject.xml" at="database"/>
+  </additional>
+</configuration>
diff --git a/src/test/resources/testDigesterBadXML.xml b/src/test/resources/testDigesterBadXML.xml
new file mode 100644
index 0000000..2f10a33
--- /dev/null
+++ b/src/test/resources/testDigesterBadXML.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Configuration test file that tests that bad
+	XML generates a SAXException that is properly
+	wrapped in a ConfigurationInitializationException -->
+
+<configuration>
+  <additional>
+    <hierarchicalXml fileName="testHierarchicalXMLConfiguration.xml"/>
+   
+</configuration>
diff --git a/src/test/resources/testDigesterConfiguration.xml b/src/test/resources/testDigesterConfiguration.xml
new file mode 100644
index 0000000..a3253b5
--- /dev/null
+++ b/src/test/resources/testDigesterConfiguration.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<configuration>
+  <properties fileName="test.properties"/>
+  <properties fileName="test.properties.xml"/>
+  <xml fileName="test.xml"/>
+</configuration>
diff --git a/src/test/resources/testDigesterConfiguration2.xml b/src/test/resources/testDigesterConfiguration2.xml
new file mode 100644
index 0000000..81e1a8e
--- /dev/null
+++ b/src/test/resources/testDigesterConfiguration2.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Configuration test file that demonstrates the
+     override and additional sections -->
+
+<configuration>
+  <additional>
+    <hierarchicalXml fileName="testHierarchicalXMLConfiguration.xml"/>
+    <hierarchicalXml fileName="testDigesterConfigurationInclude1.xml" at="tables"/>
+    <properties fileName="testDigesterConfigurationInclude2.properties" at="mail"/>
+  </additional>
+
+  <override>
+    <properties fileName="testDigesterConfigurationOverwrite.properties"/>
+  </override>
+</configuration>
diff --git a/src/test/resources/testDigesterConfiguration3.xml b/src/test/resources/testDigesterConfiguration3.xml
new file mode 100644
index 0000000..6240d55
--- /dev/null
+++ b/src/test/resources/testDigesterConfiguration3.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Configuration test file that checks all known configuration types -->
+
+<configuration>
+  <additional>
+    <system/>
+    <xml fileName="test.xml"/>
+    <hierarchicalXml fileName="testDigesterConfigurationInclude1.xml" at="tables"/>
+    <properties fileName="testDigesterConfigurationInclude2.properties" at="mail"/>
+    <jndi prefix=""/>
+    <ini fileName="test.ini"/>
+    <env at="env"/>
+  </additional>
+</configuration>
diff --git a/src/test/resources/testDigesterConfigurationBasePath.xml b/src/test/resources/testDigesterConfigurationBasePath.xml
new file mode 100644
index 0000000..f1a5ea9
--- /dev/null
+++ b/src/test/resources/testDigesterConfigurationBasePath.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<configuration>
+  <properties fileName="test.properties"/>
+</configuration>
+
+  
+  
+
+
+
diff --git a/src/test/resources/testDigesterConfigurationInclude1.xml b/src/test/resources/testDigesterConfigurationInclude1.xml
new file mode 100644
index 0000000..eff6e6d
--- /dev/null
+++ b/src/test/resources/testDigesterConfigurationInclude1.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<config>
+  <table tableType="application">
+    <name>tasks</name>
+    <fields>
+      <field>
+        <name>taskid</name>
+        <type>long</type>
+      </field>
+      <field>
+        <name>name</name>
+        <type>java.lang.String</type>
+      </field>
+      <field>
+        <name>description</name>
+        <type>java.lang.String</type>
+      </field>
+      <field>
+        <name>responsibleID</name>
+        <type>long</type>
+      </field>
+      <field>
+        <name>creatorID</name>
+        <type>long</type>
+      </field>
+      <field>
+        <name>startDate</name>
+        <type>java.util.Date</type>
+      </field>
+      <field>
+        <name>endDate</name>
+        <type>java.util.Date</type>
+      </field>
+    </fields>
+  </table>
+</config>
\ No newline at end of file
diff --git a/src/test/resources/testDigesterConfigurationInclude2.properties b/src/test/resources/testDigesterConfigurationInclude2.properties
new file mode 100644
index 0000000..d6d2b14
--- /dev/null
+++ b/src/test/resources/testDigesterConfigurationInclude2.properties
@@ -0,0 +1,22 @@
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+# Properties for Email configuration
+
+host.smtp = smtp.mydomain.org
+host.pop = pop3.mydomain.org
+account.user = postmaster
+account.psswd = secret
+account.type = pop3
diff --git a/src/test/resources/testDigesterConfigurationNamespaceAware.xml b/src/test/resources/testDigesterConfigurationNamespaceAware.xml
new file mode 100644
index 0000000..4ef922d
--- /dev/null
+++ b/src/test/resources/testDigesterConfigurationNamespaceAware.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<configuration xmlns:foo="namespace-one"
+               xmlns:bar="namespace-two">
+  <foo:properties fileName="test.properties"/>
+  <bar:xml fileName="test.xml"/>
+</configuration>
diff --git a/src/test/resources/testDigesterConfigurationOverwrite.properties b/src/test/resources/testDigesterConfigurationOverwrite.properties
new file mode 100644
index 0000000..f6482c2
--- /dev/null
+++ b/src/test/resources/testDigesterConfigurationOverwrite.properties
@@ -0,0 +1,21 @@
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+# Properties for Email configuration
+
+mail.account.user = masterOfPost
+mail.account.psswd = topsecret
+
+test.configuration = enhanced factory
diff --git a/src/test/resources/testDigesterConfigurationReverseOrder.xml b/src/test/resources/testDigesterConfigurationReverseOrder.xml
new file mode 100644
index 0000000..ffcbbb4
--- /dev/null
+++ b/src/test/resources/testDigesterConfigurationReverseOrder.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<configuration>
+  <xml fileName="test.xml"/>
+  <properties fileName="test.properties"/>
+</configuration>
diff --git a/src/test/resources/testDigesterConfigurationSysProps.xml b/src/test/resources/testDigesterConfigurationSysProps.xml
new file mode 100644
index 0000000..0afdd23
--- /dev/null
+++ b/src/test/resources/testDigesterConfigurationSysProps.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- A test configuration file for loading a file specified by a
+     system property.
+-->
+<configuration>
+  <properties fileName="${config.file}"/>
+</configuration>
diff --git a/src/test/resources/testDigesterConfigurationWithProps.xml b/src/test/resources/testDigesterConfigurationWithProps.xml
new file mode 100644
index 0000000..750e4ac
--- /dev/null
+++ b/src/test/resources/testDigesterConfigurationWithProps.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<configuration>
+  <properties fileName="test.properties" listDelimiter=";"/>
+</configuration>
diff --git a/src/test/resources/testDigesterCreateObject.xml b/src/test/resources/testDigesterCreateObject.xml
new file mode 100644
index 0000000..7399341
--- /dev/null
+++ b/src/test/resources/testDigesterCreateObject.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<configuration>
+	<connection>
+		<class name="org.apache.commons.configuration.TestConfigurationXMLDocument$ConnectionData">
+			<property name="dsn" value="MyData"/>
+			<property name="user" value="scott"/>
+			<property name="passwd" value="tiger"/>
+		</class>
+	</connection>
+</configuration>
diff --git a/src/test/resources/testDigesterOptionalConfiguration.xml b/src/test/resources/testDigesterOptionalConfiguration.xml
new file mode 100644
index 0000000..a71d0ab
--- /dev/null
+++ b/src/test/resources/testDigesterOptionalConfiguration.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Configuration test file that demonstrates
+     optional configurations -->
+
+<configuration>
+  <properties fileName="test.properties"/>
+  <xml fileName="test.xml" optional="false"/>
+  <hierarchicalXml fileName="nonExistingHierarchicalXML1.xml" optional="true"/>
+  <properties fileName="nonExistingProperties1.properties" optional="yes"/>
+  <xml fileName="nonExistingXML1.xml" optional="true"/>
+  
+  <additional>
+    <properties fileName="nonExistingProperties2.properties" optional="yes"/>
+    <hierarchicalXml fileName="nonExistingHierarchicalXML2.xml" optional="true"/>
+    <xml fileName="nonExistingXML2.xml" optional="true"/>
+  </additional>
+</configuration>
diff --git a/src/test/resources/testDigesterOptionalConfigurationEx.xml b/src/test/resources/testDigesterOptionalConfigurationEx.xml
new file mode 100644
index 0000000..f1fded4
--- /dev/null
+++ b/src/test/resources/testDigesterOptionalConfigurationEx.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Configuration test file that is used to test whether invalid
+     file names cause exceptions to be thrown -->
+
+<configuration>
+  <!-- This should not cause an exception -->
+  <properties fileName="unexisting.properties" optional="true"/>
+  <!-- But this should! -->
+  <properties fileName="unexisting.properties"/>
+</configuration>
diff --git a/src/test/resources/testDtd.xml b/src/test/resources/testDtd.xml
new file mode 100644
index 0000000..4d5bb54
--- /dev/null
+++ b/src/test/resources/testDtd.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Tests whether a DTD can be referenced when loaded from XMLConfiguration -->
+<!DOCTYPE properties SYSTEM "properties.dtd">
+<properties version="1.0">
+  <entry key="test1">value1</entry>
+  <entry key="test2">value2</entry>
+</properties>
\ No newline at end of file
diff --git a/src/test/resources/testEncoding.xml b/src/test/resources/testEncoding.xml
new file mode 100644
index 0000000..9fdf77a
Binary files /dev/null and b/src/test/resources/testEncoding.xml differ
diff --git a/src/test/resources/testEqual.properties b/src/test/resources/testEqual.properties
new file mode 100644
index 0000000..b5d818f
--- /dev/null
+++ b/src/test/resources/testEqual.properties
@@ -0,0 +1,26 @@
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+property.a = a
+property.b = b
+property.c = 100
+
+#
+# Value set twice
+property.a = aa
+
+clear.property = delete me
+
+existing.property = i exist
+
diff --git a/src/test/resources/testEqualDigester.xml b/src/test/resources/testEqualDigester.xml
new file mode 100644
index 0000000..086c0d2
--- /dev/null
+++ b/src/test/resources/testEqualDigester.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<configuration>
+  <properties fileName="testEqual.properties"/>
+</configuration>
diff --git a/src/test/resources/testExpression.xml b/src/test/resources/testExpression.xml
new file mode 100644
index 0000000..2d90e40
--- /dev/null
+++ b/src/test/resources/testExpression.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true" forceReloadCheck="true"
+            config-class="org.apache.commons.configuration.DynamicCombinedConfiguration"
+            keyPattern="$${sys:Id}">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.MergeCombiner"/>
+      <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <lookups>
+      <lookup config-prefix="mdc" config-class="org.slf4j.ext.MDCStrLookup"/>
+      <lookup config-prefix="expr"
+              config-class="org.apache.commons.configuration.interpol.ExprLookup">
+        <variables>
+          <variable name="String" value="Class:org.apache.commons.lang.StringUtils"/>
+          <variable name="MDC" value="Class:org.slf4j.MDC"/>
+        </variables>
+      </lookup>
+    </lookups>
+    <providers>
+      <provider config-tag="multifile"
+         config-class="org.apache.commons.configuration.DefaultConfigurationBuilder$FileConfigurationProvider"
+         configurationClass="org.apache.commons.configuration.MultiFileHierarchicalConfiguration"/>
+    </providers>
+  </header>
+  <override>
+    <multifile filePattern='$$${expr:String.right(MDC.get("Id"), 2)}/testMultiConfiguration_$$${sys:Id}.xml'
+               config-name="clientConfig" delimiterParsingDisabled="true">
+       <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </multifile>
+    <xml fileName="testMultiConfiguration_default.xml"
+         config-name="defaultConfig" delimiterParsingDisabled="true">
+      <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </xml>
+  </override>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testExtendedClass.xml b/src/test/resources/testExtendedClass.xml
new file mode 100644
index 0000000..f87c5b1
--- /dev/null
+++ b/src/test/resources/testExtendedClass.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true"
+            config-class="org.apache.commons.configuration.TestDefaultConfigurationBuilder$ExtendedCombinedConfiguration">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.OverrideCombiner"/>
+      <expressionEngine config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <combiner>
+      <override>
+        <list-nodes>
+          <node>table</node>
+          <node>list</node>
+        </list-nodes>
+      </override>
+    </combiner>
+  </header>
+  <system/>
+  <properties fileName="test.properties" throwExceptionOnMissing="true"
+    config-name="properties">
+    <reloadingStrategy config-class="org.apache.commons.configuration.reloading.FileChangedReloadingStrategy"
+      refreshDelay="10000"/>
+  </properties>
+  <!-- Fetch the file name from a variable -->
+  <xml fileName="${test_file_xml}" config-name="xml">
+    <expressionEngine config-class="org.apache.commons.configuration.tree.DefaultExpressionEngine"
+      propertyDelimiter="/" indexStart="[" indexEnd="]"/>
+  </xml>
+  <additional>
+    <xml config-name="combiner1" fileName="${test_file_combine}"/>  -->
+    <xml config-name="combiner2" fileName="testcombine2.xml"/>
+  </additional>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testExtendedXMLConfigurationProvider.xml b/src/test/resources/testExtendedXMLConfigurationProvider.xml
new file mode 100644
index 0000000..d857263
--- /dev/null
+++ b/src/test/resources/testExtendedXMLConfigurationProvider.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.OverrideCombiner"/>
+      <expressionEngine config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <providers>
+      <provider config-tag="test"
+                config-class="org.apache.commons.configuration.DefaultConfigurationBuilder$XMLConfigurationProvider"
+                configurationClass="org.apache.commons.configuration.TestDefaultConfigurationBuilder$ExtendedXMLConfiguration"/>
+    </providers>
+    <combiner>
+      <override>
+        <list-nodes>
+          <node>table</node>
+          <node>list</node>
+        </list-nodes>
+      </override>
+    </combiner>
+  </header>
+  <system/>
+  <properties fileName="test.properties" throwExceptionOnMissing="true"
+    config-name="properties">
+    <reloadingStrategy config-class="org.apache.commons.configuration.reloading.FileChangedReloadingStrategy"
+      refreshDelay="10000"/>
+  </properties>
+  <!-- Fetch the file name from a variable -->
+  <test fileName="${test_file_xml}" config-name="xml">
+    <expressionEngine config-class="org.apache.commons.configuration.tree.DefaultExpressionEngine"
+      propertyDelimiter="/" indexStart="[" indexEnd="]"/>
+  </test>
+  <additional>
+    <xml config-name="combiner1" fileName="${test_file_combine}"/>  -->
+    <xml config-name="combiner2" fileName="testcombine2.xml"/>
+  </additional>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testFactoryPropertiesInclude.xml b/src/test/resources/testFactoryPropertiesInclude.xml
new file mode 100644
index 0000000..f4b4137
--- /dev/null
+++ b/src/test/resources/testFactoryPropertiesInclude.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Tests embedding properties in a deep directory structure, which in turn
+     include another property file.
+-->
+<configuration>
+  <properties fileName="config/deep/deeptest.properties"/>
+  <properties fileName="config/deep/deeptestinvalid.properties" optional="true"/>
+</configuration>
diff --git a/src/test/resources/testFileReloadConfigurationBuilder.xml b/src/test/resources/testFileReloadConfigurationBuilder.xml
new file mode 100644
index 0000000..fc70ec2
--- /dev/null
+++ b/src/test/resources/testFileReloadConfigurationBuilder.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true" forceReloadCheck="true"
+            config-class="org.apache.commons.configuration.DynamicCombinedConfiguration"
+            keyPattern="$${sys:Id}">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.MergeCombiner"/>
+      <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <fileSystem config-class="org.apache.commons.configuration.VFSFileSystem"/>
+    <providers>
+      <provider config-tag="multifile"
+         config-class="org.apache.commons.configuration.DefaultConfigurationBuilder$FileConfigurationProvider"
+         configurationClass="org.apache.commons.configuration.MultiFileHierarchicalConfiguration"/>
+    </providers>
+  </header>
+  <override>
+    <multifile filePattern="testwrite/testMultiConfiguration_$$${sys:Id}.xml"
+               config-name="clientConfig" delimiterParsingDisabled="true" schemaValidation="false">
+       <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+       <reloadingStrategy refreshDelay="0"
+          config-class="org.apache.commons.configuration.reloading.VFSFileChangedReloadingStrategy"/>
+    </multifile>
+    <xml fileName="testMultiConfiguration_default.xml"
+         config-name="defaultConfig" delimiterParsingDisabled="true">
+      <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+      <reloadingStrategy refreshDelay="0"
+          config-class="org.apache.commons.configuration.reloading.VFSFileChangedReloadingStrategy"/>
+    </xml>
+  </override>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testFileReloadConfigurationBuilder2.xml b/src/test/resources/testFileReloadConfigurationBuilder2.xml
new file mode 100644
index 0000000..99295ce
--- /dev/null
+++ b/src/test/resources/testFileReloadConfigurationBuilder2.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true" forceReloadCheck="true"
+            config-class="org.apache.commons.configuration.DynamicCombinedConfiguration"
+            keyPattern="$${sys:Id}">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.MergeCombiner"/>
+      <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <fileSystem config-class="org.apache.commons.configuration.VFSFileSystem"/>
+    <providers>
+      <provider config-tag="multifile"
+         config-class="org.apache.commons.configuration.DefaultConfigurationBuilder$FileConfigurationProvider"
+         configurationClass="org.apache.commons.configuration.MultiFileHierarchicalConfiguration"/>
+    </providers>
+  </header>
+  <override>
+    <multifile filePattern="${sys:basePath}/testwrite/testMultiConfiguration_$$${sys:Id}.xml"
+               config-name="clientConfig" delimiterParsingDisabled="true" schemaValidation="false">
+       <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+       <reloadingStrategy refreshDelay="500"
+          config-class="org.apache.commons.configuration.reloading.VFSFileChangedReloadingStrategy"/>
+    </multifile>
+    <xml fileName="testMultiConfiguration_default.xml"
+         config-name="defaultConfig" delimiterParsingDisabled="true">
+      <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+      <reloadingStrategy refreshDelay="500"
+          config-class="org.apache.commons.configuration.reloading.VFSFileChangedReloadingStrategy"/>
+    </xml>
+  </override>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testFileSystem.xml b/src/test/resources/testFileSystem.xml
new file mode 100644
index 0000000..200f459
--- /dev/null
+++ b/src/test/resources/testFileSystem.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.OverrideCombiner"/>
+      <expressionEngine config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <fileSystem config-class="org.apache.commons.configuration.VFSFileSystem"/>
+    <providers>
+      <provider config-tag="test"
+                config-class="org.apache.commons.configuration.DefaultConfigurationBuilder$FileConfigurationProvider"/>
+    </providers>
+    <combiner>
+      <override>
+        <list-nodes>
+          <node>table</node>
+          <node>list</node>
+        </list-nodes>
+      </override>
+    </combiner>
+  </header>
+  <system/>
+  <properties fileName="test.properties" throwExceptionOnMissing="true"
+    config-name="properties">
+    <reloadingStrategy config-class="org.apache.commons.configuration.reloading.FileChangedReloadingStrategy"
+      refreshDelay="10000"/>
+  </properties>
+  <!-- Fetch the file name from a variable -->
+  <xml fileName="${test_file_xml}" config-name="xml">
+    <expressionEngine config-class="org.apache.commons.configuration.tree.DefaultExpressionEngine"
+      propertyDelimiter="/" indexStart="[" indexEnd="]"/>
+  </xml>
+  <additional>
+    <xml config-name="combiner1" fileName="${test_file_combine}"/>  -->
+    <xml config-name="combiner2" fileName="testcombine2.xml"/>
+  </additional>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testGlobalLookup.xml b/src/test/resources/testGlobalLookup.xml
new file mode 100644
index 0000000..0fa2a8a
--- /dev/null
+++ b/src/test/resources/testGlobalLookup.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.OverrideCombiner"/>
+      <expressionEngine config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <lookups>
+      <lookup config-prefix="test"
+              config-class="org.apache.commons.configuration.TestDefaultConfigurationBuilder$TestLookup"/>
+    </lookups>
+    <combiner>
+      <override>
+        <list-nodes>
+          <node>table</node>
+          <node>list</node>
+        </list-nodes>
+      </override>
+    </combiner>
+  </header>
+  <system/>
+  <properties fileName="test.properties" throwExceptionOnMissing="true"
+    config-name="properties">
+    <reloadingStrategy config-class="org.apache.commons.configuration.reloading.FileChangedReloadingStrategy"
+      refreshDelay="10000"/>
+  </properties>
+  <!-- Fetch the file name from a variable -->
+  <xml fileName="${test:test_file_xml}" config-name="xml">
+    <expressionEngine config-class="org.apache.commons.configuration.tree.DefaultExpressionEngine"
+      propertyDelimiter="/" indexStart="[" indexEnd="]"/>
+  </xml>
+  <additional>
+    <xml config-name="combiner1" fileName="${test:test_file_combine}"/>  -->
+    <xml config-name="combiner2" fileName="testcombine2.xml"/>
+  </additional>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testHierarchicalXMLConfiguration.xml b/src/test/resources/testHierarchicalXMLConfiguration.xml
new file mode 100644
index 0000000..626fadd
--- /dev/null
+++ b/src/test/resources/testHierarchicalXMLConfiguration.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<database>
+  <tables>
+    <table tableType="system">
+      <name>users</name>
+      <fields>
+        <field>
+	  <name>uid</name>
+	  <type>long</type>
+	</field>
+        <field>
+	  <name>uname</name>
+	  <type>java.lang.String</type>
+	</field>
+        <field>
+	  <name>firstName</name>
+	  <type>java.lang.String</type>
+	</field>
+        <field>
+	  <name>lastName</name>
+	  <type>java.lang.String</type>
+	</field>
+        <field>
+	  <name>email</name>
+	  <type>java.lang.String</type>
+	</field>
+      </fields>
+    </table>
+    <table tableType="application">
+      <name>documents</name>
+      <fields>
+        <field>
+	  <name>docid</name>
+	  <type>long</type>
+	</field>
+        <field>
+	  <name>name</name>
+	  <type>java.lang.String</type>
+	</field>
+        <field>
+	  <name>creationDate</name>
+	  <type>java.util.Date</type>
+	</field>
+        <field>
+	  <name>authorID</name>
+	  <type>long</type>
+	</field>
+        <field>
+	  <name>version</name>
+	  <type>int</type>
+	</field>
+      </fields>
+    </table>
+  </tables>
+</database>
diff --git a/src/test/resources/testHierarchicalXMLConfiguration2.xml b/src/test/resources/testHierarchicalXMLConfiguration2.xml
new file mode 100644
index 0000000..5072566
--- /dev/null
+++ b/src/test/resources/testHierarchicalXMLConfiguration2.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<!-- Test config to confirm proper consumption of various XML nodes. -->
+<config>
+    <case1>
+        <!-- Comment contained in element. -->
+        Case1Text
+    </case1>
+    <case2>
+        <!-- Comment sibling to element. -->
+        <child>
+
+            Case2Text
+        </child>
+    </case2>
+    <case3>
+        <!-- Comment sibling to CDATA. -->
+        <![CDATA[
+            Case3Text
+        ]]>
+    </case3>
+    <case4>
+        <!-- Comment sibling to PI and text. -->
+
+        <?xml-stylesheet href="pi.css" type="text/css" ?>
+        Case4Text
+    </case4>
+    <case5 attr="Case5Text">
+        <!-- Comment in element with text in attribute -->
+    </case5>
+</config>
diff --git a/src/test/resources/testInterpolation.properties b/src/test/resources/testInterpolation.properties
new file mode 100644
index 0000000..31a0580
--- /dev/null
+++ b/src/test/resources/testInterpolation.properties
@@ -0,0 +1,22 @@
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+# A test configuration file for testing variable substitution across multiple
+# configuration sources created by a DefaultConfigurationBuilder.
+# This properties configuration defines a property which is referenced by
+# another configuration source.
+# $Id: testInterpolation.properties 1295276 2012-02-29 21:11:35Z oheger $
+myvar=abc
+
diff --git a/src/test/resources/testInterpolation.xml b/src/test/resources/testInterpolation.xml
new file mode 100644
index 0000000..2f7b3ee
--- /dev/null
+++ b/src/test/resources/testInterpolation.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!--
+  A test configuration file for testing variable substitution across multiple
+  configuration sources created by a DefaultConfigurationBuilder.
+  This XML configuration references a variable defined by another configuration
+  source.
+  $Id: testInterpolation.xml 1295276 2012-02-29 21:11:35Z oheger $
+-->
+<test>
+  <products>
+    <product name="abc">
+      <desc>${myvar}-product</desc>
+    </product>
+  </products>
+</test>
diff --git a/src/test/resources/testInterpolationBuilder.xml b/src/test/resources/testInterpolationBuilder.xml
new file mode 100644
index 0000000..55651b6
--- /dev/null
+++ b/src/test/resources/testInterpolationBuilder.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!--
+  A test configuration file for testing variable substitution across multiple
+  configuration sources created by a DefaultConfigurationBuilder.
+  This configuration definition file for a DefaultConfigurationBuilder
+  references multiple sources which contain variable references.
+  $Id: testInterpolationBuilder.xml 1295276 2012-02-29 21:11:35Z oheger $
+-->
+<configuration>
+  <system/>
+  <properties fileName="testInterpolation.properties"/>
+  <xml fileName="testInterpolation.xml" config-name="test">
+    <expressionEngine config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+  </xml>
+</configuration>
diff --git a/src/test/resources/testMultiConfiguration.xsd b/src/test/resources/testMultiConfiguration.xsd
new file mode 100644
index 0000000..84c7c56
--- /dev/null
+++ b/src/test/resources/testMultiConfiguration.xsd
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
+  <xs:element name="configuration" type="configurationType"/>
+  <xs:complexType name="configurationType">
+    <xs:sequence>
+      <xs:element type="colorsType" name="colors" minOccurs="0"/>
+      <xs:element type="xs:integer" name="rowsPerPage" minOccurs="0"/>
+      <xs:element type="buttonsType" name="buttons" minOccurs="0"/>
+      <xs:element type="numberFormatType" name="numberFormat" minOccurs="0"/>
+      <xs:element type="splitType" name="split" minOccurs="0">
+        <xs:annotation>
+          <xs:documentation>Comma delimited lists</xs:documentation>
+        </xs:annotation>
+      </xs:element>
+      <xs:element type="ChannelsType" name="Channels" minOccurs="0"/>
+    </xs:sequence>
+  </xs:complexType>
+  <xs:complexType name="splitType">
+    <xs:sequence>
+      <xs:element type="xs:string" name="list1" minOccurs="0"/>
+      <xs:element type="xs:string" name="list2" minOccurs="0"/>
+      <xs:element type="list3Type" name="list3" minOccurs="0"/>
+      <xs:element type="list4Type" name="list4" minOccurs="0"/>
+    </xs:sequence>
+  </xs:complexType>
+  <xs:complexType name="colorsType">
+    <xs:sequence>
+      <xs:element type="xs:string" name="background" minOccurs="0"/>
+      <xs:element type="xs:string" name="text" minOccurs="0"/>
+      <xs:element type="xs:string" name="header" minOccurs="0"/>
+      <xs:element type="linkType" name="link" minOccurs="0"/>
+      <xs:element type="xs:string" name="default" minOccurs="0"/>
+    </xs:sequence>
+  </xs:complexType>
+  <xs:complexType name="list4Type">
+    <xs:simpleContent>
+      <xs:extension base="xs:string">
+        <xs:attribute type="xs:string" name="values"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+  <xs:complexType name="linkType">
+    <xs:simpleContent>
+      <xs:extension base="xs:string">
+        <xs:attribute type="xs:string" name="normal"/>
+        <xs:attribute type="xs:string" name="visited"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+  <xs:complexType name="list3Type">
+    <xs:simpleContent>
+      <xs:extension base="xs:string">
+        <xs:attribute type="xs:string" name="values"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+  <xs:complexType name="buttonsType">
+    <xs:sequence>
+      <xs:element type="xs:string" name="name" minOccurs="0"/>
+    </xs:sequence>
+  </xs:complexType>
+  <xs:complexType name="numberFormatType">
+    <xs:simpleContent>
+      <xs:extension base="xs:string">
+        <xs:attribute type="xs:string" name="pattern"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+  <xs:complexType name="ChannelType">
+    <xs:sequence>
+      <xs:element name="Name" minOccurs="0">
+        <xs:simpleType>
+          <xs:restriction base="xs:string">
+          </xs:restriction>
+        </xs:simpleType>
+      </xs:element>
+      <xs:element name="ChannelData" minOccurs="0">
+        <xs:simpleType>
+          <xs:restriction base="xs:string">
+          </xs:restriction>
+        </xs:simpleType>
+      </xs:element>
+      <xs:element name="MoreChannelData" minOccurs="0">
+        <xs:simpleType>
+          <xs:restriction base="xs:string">
+          </xs:restriction>
+        </xs:simpleType>
+      </xs:element>
+    </xs:sequence>
+    <xs:attribute type="xs:string" name="id" use="optional"/>
+  </xs:complexType>
+  <xs:complexType name="ChannelsType">
+    <xs:choice maxOccurs="unbounded" minOccurs="0">
+      <xs:element type="ChannelType" name="Channel" minOccurs="0"/>
+    </xs:choice>
+  </xs:complexType>
+</xs:schema>
\ No newline at end of file
diff --git a/src/test/resources/testMultiConfiguration_1001.xml b/src/test/resources/testMultiConfiguration_1001.xml
new file mode 100644
index 0000000..6e96ee9
--- /dev/null
+++ b/src/test/resources/testMultiConfiguration_1001.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<configuration>
+  <colors>
+    <background>#808080</background>
+    <text>#000000</text>
+    <header>#008000</header>
+    <link normal="#000080" visited="#800080"/>
+    <default>${colors.header}</default>
+  </colors>
+  <rowsPerPage>15</rowsPerPage>
+  <buttons>
+    <name>OK,Cancel,Help</name>
+  </buttons>
+  <numberFormat pattern="###\,###.##"/>
+  <Channels>
+    <Channel id="1">
+      <Name>My Channel</Name>
+    </Channel>
+    <Channel id="2">
+      <MoreChannelData>more test 2 data</MoreChannelData>
+    </Channel>
+  </Channels>
+</configuration>
diff --git a/src/test/resources/testMultiConfiguration_1002.xml b/src/test/resources/testMultiConfiguration_1002.xml
new file mode 100644
index 0000000..908ac81
--- /dev/null
+++ b/src/test/resources/testMultiConfiguration_1002.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<configuration>
+  <colors>
+    <background>#2222222</background>
+    <text>#000000</text>
+    <header>#222222</header>
+    <link normal="#020202" visited="#202020"/>
+    <default>${colors.header3}</default>
+  </colors>
+  <rowsPerPage>25</rowsPerPage>
+  <buttons>
+    <name>OK-2,Cancel-2,Help-2</name>
+  </buttons>
+  <numberFormat pattern="###\,###.##"/>
+</configuration>
diff --git a/src/test/resources/testMultiConfiguration_1003.xml b/src/test/resources/testMultiConfiguration_1003.xml
new file mode 100644
index 0000000..ee54e92
--- /dev/null
+++ b/src/test/resources/testMultiConfiguration_1003.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<configuration>
+  <colors>
+    <background>#333333</background>
+    <text>#FFFFFF</text>
+    <header>#333333</header>
+    <link normal="#030303" visited="#303030"/>
+    <default>${colors.header3}</default>
+  </colors>
+  <rowsPerPage>35</rowsPerPage>
+  <buttons>
+    <name>OK-3,Cancel-3,Help-3</name>
+  </buttons>
+  <numberFormat pattern="###\,###.##"/>
+</configuration>
diff --git a/src/test/resources/testMultiConfiguration_1004.xml b/src/test/resources/testMultiConfiguration_1004.xml
new file mode 100644
index 0000000..2fafc2e
--- /dev/null
+++ b/src/test/resources/testMultiConfiguration_1004.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<configuration>
+  <colors>
+    <default>${colors.header4}</default>
+  </colors>
+  <buttons>
+    <name>OK-1,Cancel-2,Help-3</name>
+  </buttons>
+  <numberFormat pattern="###\,###.##"/>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testMultiConfiguration_2001.xml b/src/test/resources/testMultiConfiguration_2001.xml
new file mode 100644
index 0000000..a1173f3
--- /dev/null
+++ b/src/test/resources/testMultiConfiguration_2001.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<configuration
+           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+           xsi:noNamespaceSchemaLocation="http://commons.apache.org/testMultiConfiguration.xsd">
+  <colors>
+    <undefined>This will throw a schema exception</undefined>
+  </colors>
+  <buttons>
+    <name>OK-1,Cancel-2,Help-3</name>
+  </buttons>
+  <numberFormat pattern="###\,###.##"/>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testMultiConfiguration_2002.xml b/src/test/resources/testMultiConfiguration_2002.xml
new file mode 100644
index 0000000..4c52d04
--- /dev/null
+++ b/src/test/resources/testMultiConfiguration_2002.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<configuration
+           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+           xsi:noNamespaceSchemaLocation="http://commons.apache.org/testMultiConfiguration.xsd">
+  <colors>
+    <background>#2222222</background>
+    <text>#000000</text>
+    <header>#222222</header>
+    <link normal="#020202" visited="#202020"/>
+    <default>${colors.header3}</default>
+  </colors>
+  <rowsPerPage>25</rowsPerPage>
+  <buttons>
+    <name>OK-2,Cancel-2,Help-2</name>
+  </buttons>
+  <numberFormat pattern="###\,###.##"/>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testMultiConfiguration_3001.xml b/src/test/resources/testMultiConfiguration_3001.xml
new file mode 100644
index 0000000..44e665c
--- /dev/null
+++ b/src/test/resources/testMultiConfiguration_3001.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+           xsi:noNamespaceSchemaLocation="http://commons.apache.org/testMultiConfiguration.xsd">
+  <colors>
+    <background>#808080</background>
+    <text>#000000</text>
+    <header>#008000</header>
+    <link normal="#000080" visited="#800080"/>
+    <default>${colors.header}</default>
+  </colors>
+  <rowsPerPage>15</rowsPerPage>
+  <buttons>
+    <name>OK,Cancel,Help</name>
+  </buttons>
+  <numberFormat pattern="###\,###.##"/>
+  <Channels>
+    <Channel id="1">
+      <Name>My Channel</Name>
+    </Channel>
+    <Channel id="2">
+      <MoreChannelData>more test 2 data</MoreChannelData>
+    </Channel>
+  </Channels>
+</configuration>
diff --git a/src/test/resources/testMultiConfiguration_3002.xml b/src/test/resources/testMultiConfiguration_3002.xml
new file mode 100644
index 0000000..3fe8d97
--- /dev/null
+++ b/src/test/resources/testMultiConfiguration_3002.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+           xsi:noNamespaceSchemaLocation="http://commons.apache.org/testMultiConfiguration.xsd">
+  <colors>
+    <background>#2222222</background>
+    <text>#000000</text>
+    <header>#222222</header>
+    <link normal="#020202" visited="#202020"/>
+    <default>${colors.header3}</default>
+  </colors>
+  <rowsPerPage>25</rowsPerPage>
+  <buttons>
+    <name>OK-2,Cancel-2,Help-2</name>
+  </buttons>
+  <numberFormat pattern="###\,###.##"/>
+</configuration>
diff --git a/src/test/resources/testMultiConfiguration_default.xml b/src/test/resources/testMultiConfiguration_default.xml
new file mode 100644
index 0000000..cc23f25
--- /dev/null
+++ b/src/test/resources/testMultiConfiguration_default.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+           xsi:noNamespaceSchemaLocation="http://commons.apache.org/testMultiConfiguration.xsd">
+  <colors>
+    <background>#40404040</background>
+    <text>#000000</text>
+    <header>#444444</header>
+    <link normal="#040404" visited="#404040"/>
+    <default>${colors.default}</default>
+  </colors>
+  <rowsPerPage>50</rowsPerPage>
+  <buttons>
+    <name>OK-4,Cancel-4,Help-4</name>
+  </buttons>
+  <numberFormat pattern="###\,###.##"/>
+  <!-- Comma delimited lists -->
+  <split>
+    <list1>a,b,c</list1>
+    <list2>a\,b\,c</list2>
+    <list3 values="a,b,c"/>
+    <list4 values="a\,b\,c"/>
+  </split>
+  <Channels>
+    <Channel id="1">
+      <Name>Channel 1</Name>
+      <ChannelData>test 1 data</ChannelData>
+    </Channel>
+    <Channel id="2">
+      <Name>Channel 2</Name>
+      <ChannelData>test 2 data</ChannelData>
+    </Channel>
+  </Channels>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testMultiDynamic_default.xml b/src/test/resources/testMultiDynamic_default.xml
new file mode 100644
index 0000000..bc4b6a8
--- /dev/null
+++ b/src/test/resources/testMultiDynamic_default.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+           xsi:noNamespaceSchemaLocation="http://commons.apache.org/testMultiConfiguration.xsd">
+  <colors>
+    <background>#40404040</background>
+    <text>#000000</text>
+    <header>#444444</header>
+    <link normal="#040404" visited="#404040"/>
+    <default>${colors.default}</default>
+  </colors>
+  <Product>
+    <FIIndex>
+        <FI id="123456781">ID0001</FI>
+        <FI id="AUTO01">IDAUTO01</FI>
+        <FI id="AUTO02">IDAUTO02</FI>
+    </FIIndex>
+  </Product>
+  <rowsPerPage>50</rowsPerPage>
+  <buttons>
+    <name>OK-4,Cancel-4,Help-4</name>
+  </buttons>
+  <numberFormat pattern="###\,###.##"/>
+  <!-- Comma delimited lists -->
+  <split>
+    <list1>a,b,c</list1>
+    <list2>a\,b\,c</list2>
+    <list3 values="a,b,c"/>
+    <list4 values="a\,b\,c"/>
+  </split>
+  <Channels>
+    <Channel id="1">
+      <Name>Channel 1</Name>
+      <ChannelData>test 1 data</ChannelData>
+    </Channel>
+    <Channel id="2">
+      <Name>Channel 2</Name>
+      <ChannelData>test 2 data</ChannelData>
+    </Channel>
+  </Channels>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testMultiDynamic_default2.xml b/src/test/resources/testMultiDynamic_default2.xml
new file mode 100644
index 0000000..44ff7fc
--- /dev/null
+++ b/src/test/resources/testMultiDynamic_default2.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+           xsi:noNamespaceSchemaLocation="http://commons.apache.org/testMultiConfiguration.xsd">
+  <colors>
+    <background>#40404040</background>
+    <text>#000000</text>
+    <header>#444444</header>
+    <link normal="#040404" visited="#404040"/>
+    <default>${colors.default}</default>
+  </colors>
+  <Product>
+    <FIIndex>
+        <FI id="123456781">ID0001</FI>
+        <FI id="123456782">ID0002</FI>
+        <FI id="AUTO01">IDAUTO01</FI>
+        <FI id="AUTO02">IDAUTO02</FI>
+    </FIIndex>
+  </Product>
+  <rowsPerPage>25</rowsPerPage>
+  <buttons>
+    <name>OK-4,Cancel-4,Help-4</name>
+  </buttons>
+  <numberFormat pattern="###\,###.##"/>
+  <!-- Comma delimited lists -->
+  <split>
+    <list1>a,b,c</list1>
+    <list2>a\,b\,c</list2>
+    <list3 values="a,b,c"/>
+    <list4 values="a\,b\,c"/>
+  </split>
+  <Channels>
+    <Channel id="1">
+      <Name>Channel 1</Name>
+      <ChannelData>test 1 data</ChannelData>
+    </Channel>
+    <Channel id="2">
+      <Name>Channel 2</Name>
+      <ChannelData>test 2 data</ChannelData>
+    </Channel>
+  </Channels>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testMultiTenentConfigurationBuilder.xml b/src/test/resources/testMultiTenentConfigurationBuilder.xml
new file mode 100644
index 0000000..a053316
--- /dev/null
+++ b/src/test/resources/testMultiTenentConfigurationBuilder.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true" forceReloadCheck="true" loggerName="TestLogger"
+            config-class="org.apache.commons.configuration.DynamicCombinedConfiguration"
+            keyPattern="$${sys:Id}">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.MergeCombiner"/>
+      <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <providers>
+      <provider config-tag="multifile"
+         config-class="org.apache.commons.configuration.DefaultConfigurationBuilder$FileConfigurationProvider"
+         configurationClass="org.apache.commons.configuration.MultiFileHierarchicalConfiguration"/>
+    </providers>
+  </header>
+  <override>
+    <multifile filePattern="testMultiConfiguration_$$${sys:Id}.xml"
+               config-name="clientConfig" delimiterParsingDisabled="true" schemaValidation="false">
+       <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </multifile>
+    <xml fileName="testMultiConfiguration_default.xml"
+         config-name="defaultConfig" delimiterParsingDisabled="true">
+      <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </xml>
+  </override>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testMultiTenentConfigurationBuilder2.xml b/src/test/resources/testMultiTenentConfigurationBuilder2.xml
new file mode 100644
index 0000000..df2ba5c
--- /dev/null
+++ b/src/test/resources/testMultiTenentConfigurationBuilder2.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true" forceReloadCheck="true" loggerName="TestLogger"
+            config-class="org.apache.commons.configuration.DynamicCombinedConfiguration"
+            keyPattern="$${sys:Id}">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.MergeCombiner"/>
+      <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <entity-resolver catalogFiles="catalog.xml"/>
+    <providers>
+      <provider config-tag="multifile"
+         config-class="org.apache.commons.configuration.DefaultConfigurationBuilder$FileConfigurationProvider"
+         configurationClass="org.apache.commons.configuration.MultiFileHierarchicalConfiguration"/>
+    </providers>
+  </header>
+  <override>
+    <multifile filePattern="testMultiConfiguration_$$${sys:Id}.xml"
+               config-name="clientConfig" delimiterParsingDisabled="true" schemaValidation="true">
+       <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+       <reloadingStrategy refreshDelay="500"
+          config-class="org.apache.commons.configuration.reloading.FileChangedReloadingStrategy"/>
+    </multifile>
+    <xml fileName="testMultiConfiguration_default.xml"
+         config-name="defaultConfig" delimiterParsingDisabled="true" schemaValidation="true">
+      <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+      <reloadingStrategy refreshDelay="500"
+          config-class="org.apache.commons.configuration.reloading.FileChangedReloadingStrategy"/>
+    </xml>
+  </override>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testMultiTenentConfigurationBuilder3.xml b/src/test/resources/testMultiTenentConfigurationBuilder3.xml
new file mode 100644
index 0000000..1481cb3
--- /dev/null
+++ b/src/test/resources/testMultiTenentConfigurationBuilder3.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true" forceReloadCheck="true" loggerName="TestLogger"
+            config-class="org.apache.commons.configuration.DynamicCombinedConfiguration"
+            keyPattern="$${sys:Id}">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.MergeCombiner"/>
+      <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <entity-resolver catalogFiles="catalog.xml"/>
+    <providers>
+      <provider config-tag="multifile"
+         config-class="org.apache.commons.configuration.DefaultConfigurationBuilder$FileConfigurationProvider"
+         configurationClass="org.apache.commons.configuration.MultiFileHierarchicalConfiguration"/>
+    </providers>
+  </header>
+  <override>
+    <multifile filePattern="testwrite/testMultiConfiguration_$$${sys:Id}.xml"
+               config-name="clientConfig" delimiterParsingDisabled="true" schemaValidation="true">
+       <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+       <reloadingStrategy refreshDelay="500"
+          config-class="org.apache.commons.configuration.reloading.FileChangedReloadingStrategy"/>
+    </multifile>
+    <xml fileName="testMultiConfiguration_default.xml"
+         config-name="defaultConfig" delimiterParsingDisabled="true" schemaValidation="true">
+      <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+      <reloadingStrategy refreshDelay="500"
+          config-class="org.apache.commons.configuration.reloading.FileChangedReloadingStrategy"/>
+    </xml>
+  </override>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testMultiTenentConfigurationBuilder4.xml b/src/test/resources/testMultiTenentConfigurationBuilder4.xml
new file mode 100644
index 0000000..8d5ccf5
--- /dev/null
+++ b/src/test/resources/testMultiTenentConfigurationBuilder4.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true" forceReloadCheck="true" loggerName="TestLogger"
+            config-class="org.apache.commons.configuration.DynamicCombinedConfiguration"
+            keyPattern="$${test:Id}">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.MergeCombiner"/>
+      <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <lookups>
+      <lookup config-prefix="test"
+              config-class="org.apache.commons.configuration.TestDynamicCombinedConfiguration$ThreadLookup"/>
+    </lookups>
+    <entity-resolver catalogFiles="catalog.xml"/>
+    <providers>
+      <provider config-tag="multifile"
+         config-class="org.apache.commons.configuration.DefaultConfigurationBuilder$FileConfigurationProvider"
+         configurationClass="org.apache.commons.configuration.MultiFileHierarchicalConfiguration"/>
+    </providers>
+  </header>
+  <override>
+    <multifile filePattern="testMultiConfiguration_$$${test:Id}.xml"
+               config-name="clientConfig" delimiterParsingDisabled="true" schemaValidation="true">
+       <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+       <reloadingStrategy refreshDelay="500"
+          config-class="org.apache.commons.configuration.reloading.FileRandomReloadingStrategy"/>
+    </multifile>
+    <xml fileName="testMultiConfiguration_default.xml"
+         config-name="defaultConfig" delimiterParsingDisabled="true" schemaValidation="true">
+      <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+      <reloadingStrategy refreshDelay="500"
+          config-class="org.apache.commons.configuration.reloading.FileRandomReloadingStrategy"/>
+    </xml>
+  </override>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testMultiTenentConfigurationBuilder5.xml b/src/test/resources/testMultiTenentConfigurationBuilder5.xml
new file mode 100644
index 0000000..de93caa
--- /dev/null
+++ b/src/test/resources/testMultiTenentConfigurationBuilder5.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true" forceReloadCheck="true" loggerName="TestLogger"
+            config-class="org.apache.commons.configuration.DynamicCombinedConfiguration"
+            keyPattern="$${sys:Id}">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.MergeCombiner"/>
+      <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <entity-resolver catalogFiles="catalog.xml"/>
+    <providers>
+      <provider config-tag="multifile"
+         config-class="org.apache.commons.configuration.DefaultConfigurationBuilder$FileConfigurationProvider"
+         configurationClass="org.apache.commons.configuration.MultiFileHierarchicalConfiguration"/>
+    </providers>
+  </header>
+  <override>
+    <multifile filePattern="testwrite/testMultiConfiguration_$$${sys:Id}.xml"
+               config-name="clientConfig" delimiterParsingDisabled="true" schemaValidation="false">
+       <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+       <reloadingStrategy refreshDelay="500"
+          config-class="org.apache.commons.configuration.reloading.FileChangedReloadingStrategy"/>
+    </multifile>
+    <xml fileName="testwrite/testMultiDynamic_default.xml"
+         config-name="defaultConfig" delimiterParsingDisabled="true" schemaValidation="false">
+      <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+      <reloadingStrategy refreshDelay="500"
+          config-class="org.apache.commons.configuration.reloading.FileChangedReloadingStrategy"/>
+    </xml>
+  </override>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testPatternSubtreeConfig.xml b/src/test/resources/testPatternSubtreeConfig.xml
new file mode 100644
index 0000000..994bbf9
--- /dev/null
+++ b/src/test/resources/testPatternSubtreeConfig.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<Configuration>
+  <BusinessClient name="1001">
+    <colors>
+      <background>#808080</background>
+      <text>#000000</text>
+      <header>#008000</header>
+      <link normal="#000080" visited="#800080"/>
+      <default>${colors.header}</default>
+    </colors>
+    <rowsPerPage>15</rowsPerPage>
+    <buttons>
+      <name>OK,Cancel,Help</name>
+    </buttons>
+    <numberFormat pattern="###\,###.##"/>
+  </BusinessClient>
+  <BusinessClient name="1002">
+    <colors>
+      <background>#2222222</background>
+      <text>#000000</text>
+      <header>#222222</header>
+      <link normal="#020202" visited="#202020"/>
+      <default>${colors.header3}</default>
+    </colors>
+    <rowsPerPage>25</rowsPerPage>
+    <buttons>
+      <name>OK-2,Cancel-2,Help-2</name>
+    </buttons>
+    <numberFormat pattern="###\,###.##"/>
+  </BusinessClient>
+  <BusinessClient name="1003">
+    <colors>
+      <background>#333333</background>
+      <text>#FFFFFF</text>
+      <header>#333333</header>
+      <link normal="#030303" visited="#303030"/>
+      <default>${colors.header3}</default>
+    </colors>
+    <rowsPerPage>35</rowsPerPage>
+    <buttons>
+      <name>OK-3,Cancel-3,Help-3</name>
+    </buttons>
+    <numberFormat pattern="###\,###.##"/>
+  </BusinessClient>
+</Configuration>
\ No newline at end of file
diff --git a/src/test/resources/testResolver.xml b/src/test/resources/testResolver.xml
new file mode 100644
index 0000000..7d04dc7
--- /dev/null
+++ b/src/test/resources/testResolver.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<!DOCTYPE test PUBLIC "-//Apache//DTD Resolver Test V1.0//EN"
+                      "http://example.com/does-not-exist">
+<test>
+<foo/>
+</test>
\ No newline at end of file
diff --git a/src/test/resources/testSequence.properties b/src/test/resources/testSequence.properties
new file mode 100644
index 0000000..7d6d0bd
--- /dev/null
+++ b/src/test/resources/testSequence.properties
@@ -0,0 +1,23 @@
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+prefix.Fa.postfix=value.Fa
+prefix.Po.postfix=value.Po
+prefix.Ru.postfix=value.Ru
+prefix.Se.postfix=value.Se
+prefix.As.postfix=value.As
+prefix.Gl.postfix=value.Gl
+prefix.Pu.postfix=value.Pu
+prefix.Te.postfix=value.Te
+prefix.Ve.postfix=value.Ve
diff --git a/src/test/resources/testSequenceDigester.xml b/src/test/resources/testSequenceDigester.xml
new file mode 100644
index 0000000..6b9aac5
--- /dev/null
+++ b/src/test/resources/testSequenceDigester.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<configuration>
+  <properties fileName="testSequence.properties"/>
+</configuration>
diff --git a/src/test/resources/testSystemProperties.xml b/src/test/resources/testSystemProperties.xml
new file mode 100644
index 0000000..015de57
--- /dev/null
+++ b/src/test/resources/testSystemProperties.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Test configuration definition file that sets system properties from an XML file -->
+<configuration systemProperties="test.properties.xml">
+  <header>
+    <result delimiterParsingDisabled="true">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.OverrideCombiner"/>
+      <expressionEngine config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+  </header>
+  <system/>
+  <properties fileName="test2.properties" throwExceptionOnMissing="true"
+    config-name="properties">
+    <reloadingStrategy config-class="org.apache.commons.configuration.reloading.FileChangedReloadingStrategy"
+      refreshDelay="10000"/>
+  </properties>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testVFSMultiTenentConfigurationBuilder1.xml b/src/test/resources/testVFSMultiTenentConfigurationBuilder1.xml
new file mode 100644
index 0000000..565b230
--- /dev/null
+++ b/src/test/resources/testVFSMultiTenentConfigurationBuilder1.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true" forceReloadCheck="true" loggerName="TestLogger"
+            config-class="org.apache.commons.configuration.DynamicCombinedConfiguration"
+            keyPattern="$${sys:Id}">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.MergeCombiner"/>
+      <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <fileSystem config-class="org.apache.commons.configuration.VFSFileSystem"/>     
+    <entity-resolver catalogFiles="catalog.xml"/>
+    <providers>
+      <provider config-tag="multifile"
+         config-class="org.apache.commons.configuration.DefaultConfigurationBuilder$FileConfigurationProvider"
+         configurationClass="org.apache.commons.configuration.MultiFileHierarchicalConfiguration"/>
+    </providers>
+  </header>
+  <override>
+    <multifile filePattern="testMultiConfiguration_$$${sys:Id}.xml"
+               config-name="clientConfig" delimiterParsingDisabled="true" schemaValidation="true">
+       <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+       <reloadingStrategy refreshDelay="500"
+          config-class="org.apache.commons.configuration.reloading.VFSFileChangedReloadingStrategy"/>
+    </multifile>
+    <xml fileName="testMultiConfiguration_default.xml"
+         config-name="defaultConfig" delimiterParsingDisabled="true" schemaValidation="true">
+      <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+      <reloadingStrategy refreshDelay="500"
+          config-class="org.apache.commons.configuration.reloading.VFSFileChangedReloadingStrategy"/>
+    </xml>
+  </override>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testVFSMultiTenentConfigurationBuilder2.xml b/src/test/resources/testVFSMultiTenentConfigurationBuilder2.xml
new file mode 100644
index 0000000..fd5dc23
--- /dev/null
+++ b/src/test/resources/testVFSMultiTenentConfigurationBuilder2.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true" forceReloadCheck="true" loggerName="TestLogger"
+            config-class="org.apache.commons.configuration.DynamicCombinedConfiguration"
+            keyPattern="$${sys:Id}">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.MergeCombiner"/>
+      <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <fileSystem config-class="org.apache.commons.configuration.VFSFileSystem"/>
+    <entity-resolver catalogFiles="catalog.xml"/>
+    <providers>
+      <provider config-tag="multifile"
+         config-class="org.apache.commons.configuration.DefaultConfigurationBuilder$FileConfigurationProvider"
+         configurationClass="org.apache.commons.configuration.MultiFileHierarchicalConfiguration"/>
+    </providers>
+  </header>
+  <override>
+    <multifile filePattern="testwrite/testMultiConfiguration_$$${sys:Id}.xml"
+               config-name="clientConfig" delimiterParsingDisabled="true" schemaValidation="true">
+       <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+       <reloadingStrategy refreshDelay="500"
+          config-class="org.apache.commons.configuration.reloading.VFSFileChangedReloadingStrategy"/>
+    </multifile>
+    <xml fileName="testMultiConfiguration_default.xml"
+         config-name="defaultConfig" delimiterParsingDisabled="true" schemaValidation="true">
+      <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+      <reloadingStrategy refreshDelay="500"
+          config-class="org.apache.commons.configuration.reloading.VFSFileChangedReloadingStrategy"/>
+    </xml>
+  </override>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testValidateInvalid.xml b/src/test/resources/testValidateInvalid.xml
new file mode 100644
index 0000000..0261046
--- /dev/null
+++ b/src/test/resources/testValidateInvalid.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!--
+  This is an invalid XML document, it does not conform to the declared DTD
+  (a type is missing in one field element). This document is used for testing
+  XMLConfiguration with a validating parser. It should be possible to load
+  it if validation is disabled, but if validation is enabled, an exception
+  should be thrown.
+-->
+<!DOCTYPE database [
+<!ELEMENT database (table+)>
+<!ELEMENT table (name, fields)>
+
+<!ELEMENT fields (field+)>
+<!ELEMENT field (name, type)>
+
+<!ELEMENT name (#PCDATA)>
+<!ELEMENT type (#PCDATA)>
+]>
+<database>
+  <table>
+    <name>customers</name>
+    <fields>
+      <field>
+        <name>custID</name>
+        <type>java.lang.Long</type>
+      </field>
+      <field>
+        <name>custName</name>
+      </field>
+    </fields>
+  </table>
+</database>
\ No newline at end of file
diff --git a/src/test/resources/testValidateValid.xml b/src/test/resources/testValidateValid.xml
new file mode 100644
index 0000000..52ee767
--- /dev/null
+++ b/src/test/resources/testValidateValid.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!--
+  A valid XML document according to its DTD.
+-->
+<!DOCTYPE database [
+<!ELEMENT database (table+)>
+<!ELEMENT table (name, fields)>
+
+<!ELEMENT fields (field+)>
+<!ELEMENT field (name, type)>
+
+<!ELEMENT name (#PCDATA)>
+<!ELEMENT type (#PCDATA)>
+]>
+
+<database>
+  <table>
+    <name>customers</name>
+    <fields>
+      <field>
+        <name>custID</name>
+        <type>java.lang.Long</type>
+      </field>
+      <field>
+        <name>custName</name>
+        <type>java.lang.String</type>
+      </field>
+    </fields>
+  </table>
+</database>
\ No newline at end of file
diff --git a/src/test/resources/testValidation.xml b/src/test/resources/testValidation.xml
new file mode 100644
index 0000000..6a6f5a7
--- /dev/null
+++ b/src/test/resources/testValidation.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.OverrideCombiner"/>
+      <expressionEngine config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <entity-resolver catalogFiles="catalog.xml"/>    
+  </header>
+  <system/>
+  <properties fileName="test.properties.xml" throwExceptionOnMissing="true"
+    config-name="properties">
+  </properties>
+  <xml fileName="sample.xml" config-name="xml" schemaValidation="true"/>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testValidation2.xml b/src/test/resources/testValidation2.xml
new file mode 100644
index 0000000..f80bfd9
--- /dev/null
+++ b/src/test/resources/testValidation2.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.OverrideCombiner"/>
+      <expressionEngine config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <entity-resolver catalogFiles="file://${sys:user.dir}/target/test-classes/catalog2.xml"/>
+  </header>
+  <system/>
+  <properties fileName="test.properties.xml" throwExceptionOnMissing="true"
+    config-name="properties">
+  </properties>
+  <xml fileName="file://${sys:user.dir}/target/test-classes/sample.xml" config-name="xml" schemaValidation="true"/>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/testValidation3.xml b/src/test/resources/testValidation3.xml
new file mode 100644
index 0000000..9015217
--- /dev/null
+++ b/src/test/resources/testValidation3.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true" forceReloadCheck="true" loggerName="TestLogger"
+            config-class="org.apache.commons.configuration.DynamicCombinedConfiguration"
+            keyPattern="$${sys:Id}">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.MergeCombiner"/>
+      <expressionEngine config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <providers>
+      <provider config-tag="multifile"
+         config-class="org.apache.commons.configuration.DefaultConfigurationBuilder$FileConfigurationProvider"
+         configurationClass="org.apache.commons.configuration.MultiFileHierarchicalConfiguration"/>
+    </providers><entity-resolver catalogFiles="catalog.xml"/>
+  </header>
+  <system/>
+  <properties fileName="test.properties.xml" throwExceptionOnMissing="true"
+    config-name="properties">
+  </properties>
+  <multifile filePattern="sample_$$${sys:Id}.xml"
+             config-name="clientConfig" delimiterParsingDisabled="true" schemaValidation="true">
+  </multifile>
+  <xml fileName="sample.xml" config-name="xml" schemaValidation="true"/>
+</configuration>
\ No newline at end of file
diff --git a/src/test/resources/test_invalid_date.plist.xml b/src/test/resources/test_invalid_date.plist.xml
new file mode 100644
index 0000000..c78d175
--- /dev/null
+++ b/src/test/resources/test_invalid_date.plist.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!-- Test configuration file with an invalid date property.
+     $Id: test_invalid_date.plist.xml 1367253 2012-07-30 19:59:36Z oheger $
+-->
+<plist version="1.0">
+    <dict>
+
+        <key>string</key>
+        <string>value1</string>
+
+        <key>date</key>
+        <date>not a valid date</date>
+
+    </dict>
+</plist>
diff --git a/src/test/resources/testcombine1.xml b/src/test/resources/testcombine1.xml
new file mode 100644
index 0000000..72c3428
--- /dev/null
+++ b/src/test/resources/testcombine1.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<config>
+  <gui>
+    <bgcolor>green</bgcolor>
+    <selcolor>yellow</selcolor>
+    <level default="2">1</level>
+  </gui>
+  <net>
+    <proxy>
+      <url>http://www.url1.org</url>
+      <url>http://www.url2.org</url>
+      <url>http://www.url3.org</url>
+    </proxy>
+    <service>
+      <url>http://service1.org</url>
+    </service>
+    <server>
+    </server>
+  </net>
+  <base>
+    <services>
+      <security>
+        <login>
+          <user>Admin</user>
+          <passwd type="secret"/>
+        </login>
+      </security>
+    </services>
+  </base>
+  <database>
+    <tables>
+      <table id="1">
+        <name>documents</name>
+        <fields>
+          <field>
+            <name>docid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>docname</name>
+            <type>varchar</type>
+          </field>
+          <field>
+            <name>authorID</name>
+            <type>int</type>
+          </field>
+        </fields>
+      </table>
+    </tables>
+  </database>
+  <Channels>
+    <Channel id="1" type="half">
+      <Name>My Channel</Name>
+    </Channel>
+    <Channel id="2">
+      <MoreChannelData>more test 2 data</MoreChannelData>
+    </Channel>
+    <Channel id="3" type="half">
+      <Name>Test Channel</Name>
+    </Channel> 
+  </Channels>  
+</config>
diff --git a/src/test/resources/testcombine2.xml b/src/test/resources/testcombine2.xml
new file mode 100644
index 0000000..5ef048c
--- /dev/null
+++ b/src/test/resources/testcombine2.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<config>
+  <base>
+    <services>
+      <security>
+        <login>
+          <user type="default">scotty</user>
+          <passwd>BeamMeUp</passwd>
+        </login>
+      </security>
+    </services>
+  </base>
+  <gui>
+    <bgcolor>black</bgcolor>
+    <fgcolor>blue</fgcolor>
+    <level min="1">4</level>
+  </gui>
+  <net>
+    <server>
+      <url>http://appsvr1.com</url>
+      <url>http://appsvr2.com</url>
+      <url>http://testsvr.com</url>
+      <url>http://backupsvr.com</url>
+    </server>
+    <service>
+      <url type="2">http://service2.org</url>
+      <url type="2">http://service3.org</url>
+    </service>
+  </net>
+  <database>
+    <tables>
+      <table id="2">
+        <name>tasks</name>
+        <fields>
+          <field>
+            <name>taskid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>taskname</name>
+            <type>varchar</type>
+          </field>
+        </fields>
+      </table>
+    </tables>
+  </database>
+  <Channels>
+    <Channel id="1">
+      <Name>Channel 1</Name>
+      <ChannelData>test 1 data</ChannelData>
+    </Channel>
+    <Channel id="2" type="full">
+      <Name>Channel 2</Name>
+      <ChannelData>test 2 data</ChannelData>
+    </Channel>
+    <Channel id="3" type="full">
+      <Name>Channel 3</Name>
+      <ChannelData>test 3 data</ChannelData>
+    </Channel>
+  </Channels>
+</config>
diff --git a/src/test/resources/testdb.script b/src/test/resources/testdb.script
new file mode 100644
index 0000000..30a57b5
--- /dev/null
+++ b/src/test/resources/testdb.script
@@ -0,0 +1,95 @@
+;   Licensed to the Apache Software Foundation (ASF) under one or more
+;   contributor license agreements.  See the NOTICE file distributed with
+;   this work for additional information regarding copyright ownership.
+;   The ASF licenses this file to You under the Apache License, Version 2.0
+;   (the "License"); you may not use this file except in compliance with
+;   the License.  You may obtain a copy of the License at
+;
+;       http://www.apache.org/licenses/LICENSE-2.0
+;
+;   Unless required by applicable law or agreed to in writing, software
+;   distributed under the License is distributed on an "AS IS" BASIS,
+;   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+;   See the License for the specific language governing permissions and
+;   limitations under the License.
+
+DROP TABLE CONFIGURATION IF EXISTS;
+CREATE TABLE CONFIGURATION(KEY VARCHAR(256) NOT NULL PRIMARY KEY,VALUE VARCHAR(256));
+
+DROP TABLE CONFIGURATIONS IF EXISTS;
+CREATE TABLE CONFIGURATIONS(NAME VARCHAR(256) NOT NULL,KEY VARCHAR(256) NOT NULL,VALUE VARCHAR(256),CONSTRAINT SYS_PK_CONFIGURATIONS PRIMARY KEY(NAME,KEY));
+
+DROP TABLE CONFIGURATIONLIST IF EXISTS;
+CREATE TABLE CONFIGURATIONLIST(ID VARCHAR(256) NOT NULL PRIMARY KEY, KEY VARCHAR(256) NOT NULL,VALUE VARCHAR(256));;
+;
+GRANT ALL ON CLASS "java.lang.Math" TO PUBLIC;
+GRANT ALL ON CLASS "org.hsqldb.Library" TO PUBLIC;
+CREATE USER SA PASSWORD "" ADMIN;
+CREATE ALIAS DAYNAME FOR "org.hsqldb.Library.dayname";
+CREATE ALIAS SPACE FOR "org.hsqldb.Library.space";
+CREATE ALIAS SUBSTRING FOR "org.hsqldb.Library.substring";
+CREATE ALIAS HEXTORAW FOR "org.hsqldb.Library.hexToRaw";
+CREATE ALIAS SQRT FOR "java.lang.Math.sqrt";
+CREATE ALIAS ABS FOR "org.hsqldb.Library.abs";
+CREATE ALIAS POWER FOR "java.lang.Math.pow";
+CREATE ALIAS CHAR FOR "org.hsqldb.Library.character";
+CREATE ALIAS CONCAT FOR "org.hsqldb.Library.concat";
+CREATE ALIAS PI FOR "org.hsqldb.Library.pi";
+CREATE ALIAS RAWTOHEX FOR "org.hsqldb.Library.rawToHex";
+CREATE ALIAS SECOND FOR "org.hsqldb.Library.second";
+CREATE ALIAS TRUNCATE FOR "org.hsqldb.Library.truncate";
+CREATE ALIAS MONTH FOR "org.hsqldb.Library.month";
+CREATE ALIAS LOWER FOR "org.hsqldb.Library.lcase";
+CREATE ALIAS ATAN2 FOR "java.lang.Math.atan2";
+CREATE ALIAS REPEAT FOR "org.hsqldb.Library.repeat";
+CREATE ALIAS DAYOFMONTH FOR "org.hsqldb.Library.dayofmonth";
+CREATE ALIAS TAN FOR "java.lang.Math.tan";
+CREATE ALIAS RADIANS FOR "java.lang.Math.toRadians";
+CREATE ALIAS FLOOR FOR "java.lang.Math.floor";
+CREATE ALIAS NOW FOR "org.hsqldb.Library.now";
+CREATE ALIAS ACOS FOR "java.lang.Math.acos";
+CREATE ALIAS DAYOFWEEK FOR "org.hsqldb.Library.dayofweek";
+CREATE ALIAS CEILING FOR "java.lang.Math.ceil";
+CREATE ALIAS DAYOFYEAR FOR "org.hsqldb.Library.dayofyear";
+CREATE ALIAS LCASE FOR "org.hsqldb.Library.lcase";
+CREATE ALIAS WEEK FOR "org.hsqldb.Library.week";
+CREATE ALIAS SOUNDEX FOR "org.hsqldb.Library.soundex";
+CREATE ALIAS ASIN FOR "java.lang.Math.asin";
+CREATE ALIAS LOCATE FOR "org.hsqldb.Library.locate";
+CREATE ALIAS EXP FOR "java.lang.Math.exp";
+CREATE ALIAS MONTHNAME FOR "org.hsqldb.Library.monthname";
+CREATE ALIAS YEAR FOR "org.hsqldb.Library.year";
+CREATE ALIAS LEFT FOR "org.hsqldb.Library.left";
+CREATE ALIAS ROUNDMAGIC FOR "org.hsqldb.Library.roundMagic";
+CREATE ALIAS BITOR FOR "org.hsqldb.Library.bitor";
+CREATE ALIAS LTRIM FOR "org.hsqldb.Library.ltrim";
+CREATE ALIAS COT FOR "org.hsqldb.Library.cot";
+CREATE ALIAS COS FOR "java.lang.Math.cos";
+CREATE ALIAS MOD FOR "org.hsqldb.Library.mod";
+CREATE ALIAS SIGN FOR "org.hsqldb.Library.sign";
+CREATE ALIAS DEGREES FOR "java.lang.Math.toDegrees";
+CREATE ALIAS LOG FOR "java.lang.Math.log";
+CREATE ALIAS SIN FOR "java.lang.Math.sin";
+CREATE ALIAS CURTIME FOR "org.hsqldb.Library.curtime";
+CREATE ALIAS DIFFERENCE FOR "org.hsqldb.Library.difference";
+CREATE ALIAS INSERT FOR "org.hsqldb.Library.insert";
+CREATE ALIAS SUBSTR FOR "org.hsqldb.Library.substring";
+CREATE ALIAS DATABASE FOR "org.hsqldb.Library.database";
+CREATE ALIAS MINUTE FOR "org.hsqldb.Library.minute";
+CREATE ALIAS HOUR FOR "org.hsqldb.Library.hour";
+CREATE ALIAS IDENTITY FOR "org.hsqldb.Library.identity";
+CREATE ALIAS QUARTER FOR "org.hsqldb.Library.quarter";
+CREATE ALIAS CURDATE FOR "org.hsqldb.Library.curdate";
+CREATE ALIAS BITAND FOR "org.hsqldb.Library.bitand";
+CREATE ALIAS USER FOR "org.hsqldb.Library.user";
+CREATE ALIAS UCASE FOR "org.hsqldb.Library.ucase";
+CREATE ALIAS RTRIM FOR "org.hsqldb.Library.rtrim";
+CREATE ALIAS LOG10 FOR "org.hsqldb.Library.log10";
+CREATE ALIAS RIGHT FOR "org.hsqldb.Library.right";
+CREATE ALIAS ATAN FOR "java.lang.Math.atan";
+CREATE ALIAS UPPER FOR "org.hsqldb.Library.ucase";
+CREATE ALIAS ASCII FOR "org.hsqldb.Library.ascii";
+CREATE ALIAS RAND FOR "java.lang.Math.random";
+CREATE ALIAS LENGTH FOR "org.hsqldb.Library.length";
+CREATE ALIAS ROUND FOR "org.hsqldb.Library.round";
+CREATE ALIAS REPLACE FOR "org.hsqldb.Library.replace";
diff --git a/src/test/resources/threesome.properties b/src/test/resources/threesome.properties
new file mode 100644
index 0000000..5fec7d1
--- /dev/null
+++ b/src/test/resources/threesome.properties
@@ -0,0 +1,22 @@
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+test.threesome.one = aaa
+test.threesome.one = bbb, ccc
+
+test.threesome.two = aaa, bbb, ccc
+
+test.threesome.three = aaa
+test.threesome.three = bbb
+test.threesome.three = ccc

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/commons-configuration.git



More information about the pkg-java-commits mailing list