View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.configuration2;
19  
20  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
21  import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
22  import static org.junit.jupiter.api.Assertions.assertEquals;
23  import static org.junit.jupiter.api.Assertions.assertFalse;
24  import static org.junit.jupiter.api.Assertions.assertNotNull;
25  import static org.junit.jupiter.api.Assertions.assertNotSame;
26  import static org.junit.jupiter.api.Assertions.assertNull;
27  import static org.junit.jupiter.api.Assertions.assertSame;
28  import static org.junit.jupiter.api.Assertions.assertThrows;
29  import static org.junit.jupiter.api.Assertions.assertTrue;
30  import static org.mockito.Mockito.mock;
31  
32  import java.io.File;
33  import java.util.ArrayList;
34  import java.util.Arrays;
35  import java.util.Collection;
36  import java.util.Iterator;
37  import java.util.List;
38  import java.util.NoSuchElementException;
39  
40  import org.apache.commons.configuration2.SynchronizerTestImpl.Methods;
41  import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
42  import org.apache.commons.configuration2.convert.LegacyListDelimiterHandler;
43  import org.apache.commons.configuration2.convert.ListDelimiterHandler;
44  import org.apache.commons.configuration2.event.ConfigurationEvent;
45  import org.apache.commons.configuration2.event.EventListenerTestImpl;
46  import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
47  import org.apache.commons.configuration2.io.FileHandler;
48  import org.apache.commons.io.FileUtils;
49  import org.junit.jupiter.api.BeforeEach;
50  import org.junit.jupiter.api.Test;
51  
52  /**
53   * Test class for {@code CompositeConfiguration}.
54   */
55  public class TestCompositeConfiguration {
56  
57      /** Constant for a test property to be checked. */
58      private static final String TEST_PROPERTY = "test.source.property";
59  
60      protected PropertiesConfiguration conf1;
61      protected PropertiesConfiguration conf2;
62      protected XMLConfiguration xmlConf;
63      protected CompositeConfiguration cc;
64  
65      /**
66       * The File that we test with
67       */
68      private final String testProperties = ConfigurationAssert.getTestFile("test.properties").getAbsolutePath();
69      private final String testProperties2 = ConfigurationAssert.getTestFile("test2.properties").getAbsolutePath();
70      private final String testPropertiesXML = ConfigurationAssert.getTestFile("test.xml").getAbsolutePath();
71  
72      /**
73       * Helper method for testing whether the list delimiter is correctly handled.
74       */
75      private void checkSetListDelimiterHandler() {
76          cc.addProperty("test.list", "a/b/c");
77          cc.addProperty("test.property", "a,b,c");
78          assertEquals(3, cc.getList("test.list").size());
79          assertEquals("a,b,c", cc.getString("test.property"));
80  
81          final AbstractConfiguration config = (AbstractConfiguration) cc.getInMemoryConfiguration();
82          final DefaultListDelimiterHandler listHandler = (DefaultListDelimiterHandler) config.getListDelimiterHandler();
83          assertEquals('/', listHandler.getDelimiter());
84      }
85  
86      /**
87       * Creates a test synchronizer and installs it at the test configuration.
88       *
89       * @return the test synchronizer
90       */
91      private SynchronizerTestImpl installSynchronizer() {
92          cc.addConfiguration(conf1);
93          cc.addConfiguration(conf2);
94          final SynchronizerTestImpl sync = new SynchronizerTestImpl();
95          cc.setSynchronizer(sync);
96          return sync;
97      }
98  
99      /**
100      * Prepares a test for interpolation with multiple configurations and similar properties.
101      */
102     private void prepareInterpolationTest() {
103         final PropertiesConfiguration p = new PropertiesConfiguration();
104         p.addProperty("foo", "initial");
105         p.addProperty("bar", "${foo}");
106         p.addProperty("prefix.foo", "override");
107 
108         cc.addConfiguration(p.subset("prefix"));
109         cc.addConfiguration(p);
110         assertEquals("override", cc.getString("bar"));
111     }
112 
113     @BeforeEach
114     public void setUp() throws Exception {
115         cc = new CompositeConfiguration();
116         final ListDelimiterHandler listHandler = new LegacyListDelimiterHandler(',');
117         conf1 = new PropertiesConfiguration();
118         conf1.setListDelimiterHandler(listHandler);
119         final FileHandler handler1 = new FileHandler(conf1);
120         handler1.setFileName(testProperties);
121         handler1.load();
122         conf2 = new PropertiesConfiguration();
123         conf2.setListDelimiterHandler(listHandler);
124         final FileHandler handler2 = new FileHandler(conf2);
125         handler2.setFileName(testProperties2);
126         handler2.load();
127         xmlConf = new XMLConfiguration();
128         final FileHandler handler3 = new FileHandler(xmlConf);
129         handler3.load(new File(testPropertiesXML));
130 
131         cc.setThrowExceptionOnMissing(true);
132     }
133 
134     /**
135      * Prepares a test of the getSource() method.
136      */
137     private void setUpSourceTest() {
138         cc.addConfiguration(conf1);
139         cc.addConfiguration(conf2);
140     }
141 
142     /**
143      * Tests whether adding a child configuration is synchronized.
144      */
145     @Test
146     void testAddConfigurationSynchronized() {
147         final SynchronizerTestImpl sync = installSynchronizer();
148         cc.addConfiguration(xmlConf);
149         sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE);
150     }
151 
152     @Test
153     void testAddFirstRemoveConfigurations() throws Exception {
154         cc.addConfigurationFirst(conf1);
155         assertEquals(2, cc.getNumberOfConfigurations());
156         cc.addConfigurationFirst(conf1);
157         assertEquals(2, cc.getNumberOfConfigurations());
158         cc.addConfigurationFirst(conf2);
159         assertEquals(3, cc.getNumberOfConfigurations());
160         cc.removeConfiguration(conf1);
161         assertEquals(2, cc.getNumberOfConfigurations());
162         cc.clear();
163         assertEquals(1, cc.getNumberOfConfigurations());
164     }
165 
166     /**
167      * Tests adding values. Make sure they _DON'T_ override any other properties but add to the existing properties and keep
168      * sequence
169      */
170     @Test
171     void testAddingProperty() throws Exception {
172         cc.addConfiguration(conf1);
173         cc.addConfiguration(xmlConf);
174 
175         String[] values = cc.getStringArray("test.short");
176 
177         assertArrayEquals(new String[] {"1"}, values);
178 
179         cc.addProperty("test.short", "88");
180 
181         values = cc.getStringArray("test.short");
182 
183         assertArrayEquals(new String[] {"1", "88"}, values);
184     }
185 
186     @Test
187     void testAddRemoveConfigurations() throws Exception {
188         cc.addConfiguration(conf1);
189         assertEquals(2, cc.getNumberOfConfigurations());
190         cc.addConfiguration(conf1);
191         assertEquals(2, cc.getNumberOfConfigurations());
192         cc.addConfiguration(conf2);
193         assertEquals(3, cc.getNumberOfConfigurations());
194         cc.removeConfiguration(conf1);
195         assertEquals(2, cc.getNumberOfConfigurations());
196         cc.clear();
197         assertEquals(1, cc.getNumberOfConfigurations());
198     }
199 
200     @Test
201     void testCantRemoveMemoryConfig() throws Exception {
202         cc.clear();
203         assertEquals(1, cc.getNumberOfConfigurations());
204 
205         final Configuration internal = cc.getConfiguration(0);
206         cc.removeConfiguration(internal);
207 
208         assertEquals(1, cc.getNumberOfConfigurations());
209     }
210 
211     @Test
212     void testCheckingInMemoryConfiguration() throws Exception {
213         final String testKey = "testKey";
214         final Configuration defaults = new PropertiesConfiguration();
215         defaults.setProperty(testKey, "testValue");
216         final Configuration testConfiguration = new CompositeConfiguration(defaults);
217         assertTrue(testConfiguration.containsKey(testKey));
218         assertFalse(testConfiguration.isEmpty());
219         boolean foundTestKey = false;
220         final Iterator<String> i = testConfiguration.getKeys();
221         // assertInstanceOf(IteratorChain.class, i);
222         // IteratorChain ic = (IteratorChain)i;
223         // assertEquals(2,i.size());
224         while (i.hasNext()) {
225             final String key = i.next();
226             if (key.equals(testKey)) {
227                 foundTestKey = true;
228             }
229         }
230         assertTrue(foundTestKey);
231         testConfiguration.clearProperty(testKey);
232         assertFalse(testConfiguration.containsKey(testKey));
233     }
234 
235     /**
236      * Tests setting values. These are set in memory mode only!
237      */
238     @Test
239     void testClearingProperty() throws Exception {
240         cc.addConfiguration(conf1);
241         cc.addConfiguration(xmlConf);
242         cc.clearProperty("test.short");
243         assertFalse(cc.containsKey("test.short"));
244     }
245 
246     @Test
247     void testClone() {
248         final CompositeConfiguration cc2 = (CompositeConfiguration) cc.clone();
249         assertEquals(cc.getNumberOfConfigurations(), cc2.getNumberOfConfigurations());
250 
251         final StrictConfigurationComparator comp = new StrictConfigurationComparator();
252         for (int i = 0; i < cc.getNumberOfConfigurations(); i++) {
253             assertEquals(cc.getConfiguration(i).getClass(), cc2.getConfiguration(i).getClass(), "Wrong configuration class at " + i);
254             assertNotSame(cc.getConfiguration(i), cc2.getConfiguration(i));
255             assertTrue(comp.compare(cc.getConfiguration(i), cc2.getConfiguration(i)), "Configurations at " + i + " not equal");
256         }
257 
258         assertTrue(comp.compare(cc, cc2));
259     }
260 
261     /**
262      * Ensures that event listeners are not cloned.
263      */
264     @Test
265     void testCloneEventListener() {
266         cc.addEventListener(ConfigurationEvent.ANY, new EventListenerTestImpl(null));
267         final CompositeConfiguration cc2 = (CompositeConfiguration) cc.clone();
268         assertTrue(cc2.getEventListeners(ConfigurationEvent.ANY).isEmpty());
269     }
270 
271     /**
272      * Tests whether interpolation works as expected after cloning.
273      */
274     @Test
275     void testCloneInterpolation() {
276         final CompositeConfiguration cc2 = (CompositeConfiguration) cc.clone();
277         assertNotSame(cc.getInterpolator(), cc2.getInterpolator());
278     }
279 
280     /**
281      * Tests cloning if one of the contained configurations does not support this operation. This should cause an exception.
282      */
283     @Test
284     void testCloneNotSupported() {
285         cc.addConfiguration(new NonCloneableConfiguration());
286         assertThrows(ConfigurationRuntimeException.class, cc::clone);
287     }
288 
289     /**
290      * Tests getting a default when the key doesn't exist
291      */
292     @Test
293     void testDefaultValueWhenKeyMissing() throws Exception {
294         cc.addConfiguration(conf1);
295         cc.addConfiguration(xmlConf);
296         assertEquals("default", cc.getString("bogus", "default"));
297         assertEquals(1.4, cc.getDouble("bogus", 1.4), 0.0);
298         assertEquals(1.4, cc.getDouble("bogus", 1.4), 0.0);
299     }
300 
301     /**
302      * Tests whether add property events are triggered.
303      */
304     @Test
305     void testEventAddProperty() {
306         final EventListenerTestImpl listener = new EventListenerTestImpl(cc);
307         cc.addEventListener(ConfigurationEvent.ANY, listener);
308         cc.addProperty("test", "value");
309         listener.checkEvent(ConfigurationEvent.ADD_PROPERTY, "test", "value", true);
310         listener.checkEvent(ConfigurationEvent.ADD_PROPERTY, "test", "value", false);
311         listener.done();
312     }
313 
314     /**
315      * Tests whether clear property events are triggered.
316      */
317     @Test
318     void testEventClearProperty() {
319         cc.addConfiguration(conf1);
320         final String key = "configuration.loaded";
321         assertTrue(cc.getBoolean(key));
322         final EventListenerTestImpl listener = new EventListenerTestImpl(cc);
323         cc.addEventListener(ConfigurationEvent.ANY, listener);
324         cc.clearProperty(key);
325         assertFalse(cc.containsKey(key));
326         listener.checkEvent(ConfigurationEvent.CLEAR_PROPERTY, key, null, true);
327         listener.checkEvent(ConfigurationEvent.CLEAR_PROPERTY, key, null, false);
328         listener.done();
329     }
330 
331     /**
332      * Tests whether set property events are triggered.
333      */
334     @Test
335     void testEventSetProperty() {
336         final EventListenerTestImpl listener = new EventListenerTestImpl(cc);
337         cc.addEventListener(ConfigurationEvent.ANY, listener);
338         cc.setProperty("test", "value");
339         listener.checkEvent(ConfigurationEvent.SET_PROPERTY, "test", "value", true);
340         listener.checkEvent(ConfigurationEvent.SET_PROPERTY, "test", "value", false);
341         listener.done();
342     }
343 
344     /**
345      * Tests whether access to a configuration by index is synchronized.
346      */
347     @Test
348     void testGetConfigurationSynchronized() {
349         final SynchronizerTestImpl sync = installSynchronizer();
350         assertEquals(conf1, cc.getConfiguration(0));
351         sync.verify(Methods.BEGIN_READ, Methods.END_READ);
352     }
353 
354     /**
355      * Tests whether access to the in-memory configuration is synchronized.
356      */
357     @Test
358     void testGetInMemoryConfigurationSynchronized() {
359         final SynchronizerTestImpl sync = installSynchronizer();
360         cc.getInMemoryConfiguration();
361         sync.verify(Methods.BEGIN_READ, Methods.END_READ);
362     }
363 
364     /**
365      * Tests {@code getKeys(String key)} preserves the order
366      */
367     @Test
368     void testGetKeys2PreservesOrder() throws Exception {
369         cc.addConfiguration(conf1);
370         final List<String> orderedList = new ArrayList<>();
371         for (final Iterator<String> keys = conf1.getKeys("test"); keys.hasNext();) {
372             orderedList.add(keys.next());
373         }
374         final List<String> iteratedList = new ArrayList<>();
375         for (final Iterator<String> keys = cc.getKeys("test"); keys.hasNext();) {
376             iteratedList.add(keys.next());
377         }
378         assertEquals(orderedList, iteratedList);
379     }
380 
381     /**
382      * Tests {@code getKeys} preserves the order
383      */
384     @Test
385     void testGetKeysPreservesOrder() throws Exception {
386         cc.addConfiguration(conf1);
387         final List<String> orderedList = new ArrayList<>();
388         for (final Iterator<String> keys = conf1.getKeys(); keys.hasNext();) {
389             orderedList.add(keys.next());
390         }
391         final List<String> iteratedList = new ArrayList<>();
392         for (final Iterator<String> keys = cc.getKeys(); keys.hasNext();) {
393             iteratedList.add(keys.next());
394         }
395         assertEquals(orderedList, iteratedList);
396     }
397 
398     @Test
399     void testGetList() {
400         final Configuration conf1 = new BaseConfiguration();
401         conf1.addProperty("array", "value1");
402         conf1.addProperty("array", "value2");
403 
404         final Configuration conf2 = new BaseConfiguration();
405         conf2.addProperty("array", "value3");
406         conf2.addProperty("array", "value4");
407 
408         cc.addConfiguration(conf1);
409         cc.addConfiguration(conf2);
410 
411         // check the composite 'array' property
412         List<Object> list = cc.getList("array");
413         assertEquals(Arrays.asList("value1", "value2"), list);
414 
415         // add an element to the list in the composite configuration
416         cc.addProperty("array", "value5");
417 
418         // test the new list
419         list = cc.getList("array");
420         assertEquals(Arrays.asList("value1", "value2", "value5"), list);
421     }
422 
423     /**
424      * Tests querying a list when a tricky interpolation is involved. This is related to CONFIGURATION-339.
425      */
426     @Test
427     void testGetListWithInterpolation() {
428         prepareInterpolationTest();
429         final List<Object> lst = cc.getList("bar");
430         assertEquals(Arrays.asList("override"), lst);
431     }
432 
433     /**
434      * Tests whether querying the number of child configurations is synchronized.
435      */
436     @Test
437     void testGetNumberOfConfigurationsSynchronized() {
438         final SynchronizerTestImpl sync = installSynchronizer();
439         assertEquals(3, cc.getNumberOfConfigurations());
440         sync.verify(Methods.BEGIN_READ, Methods.END_READ);
441     }
442 
443     @Test
444     void testGetProperty() throws Exception {
445         cc.addConfiguration(conf1);
446         cc.addConfiguration(conf2);
447         assertEquals("test.properties", cc.getString("propertyInOrder"));
448         cc.clear();
449 
450         cc.addConfiguration(conf2);
451         cc.addConfiguration(conf1);
452         assertEquals("test2.properties", cc.getString("propertyInOrder"));
453         cc.clear();
454 
455         cc.addConfiguration(conf1);
456         cc.addConfigurationFirst(conf2);
457         assertEquals("test2.properties", cc.getString("propertyInOrder"));
458         cc.clear();
459     }
460 
461     @Test
462     void testGetPropertyMissing() throws Exception {
463         cc.addConfiguration(conf1);
464         cc.addConfiguration(conf2);
465         final NoSuchElementException nsee = assertThrows(NoSuchElementException.class, () -> cc.getString("bogus.property"));
466         assertTrue(nsee.getMessage().contains("bogus.property"));
467 
468         assertFalse(cc.getBoolean("test.missing.boolean", false));
469         assertTrue(cc.getBoolean("test.missing.boolean.true", true));
470     }
471 
472     @Test
473     void testGetPropertyWIncludes() throws Exception {
474         cc.addConfiguration(conf1);
475         cc.addConfiguration(conf2);
476         final List<Object> l = cc.getList("packages");
477         assertTrue(l.contains("packagea"));
478     }
479 
480     /**
481      * Tests the getSource() method for a property contained in the in memory configuration.
482      */
483     @Test
484     void testGetSourceInMemory() {
485         setUpSourceTest();
486         cc.addProperty(TEST_PROPERTY, Boolean.TRUE);
487         assertSame(cc.getInMemoryConfiguration(), cc.getSource(TEST_PROPERTY));
488     }
489 
490     /**
491      * Tests the getSource() method if the property is defined by multiple child configurations. In this case an exception
492      * should be thrown.
493      */
494     @Test
495     void testGetSourceMultiple() {
496         setUpSourceTest();
497         conf1.addProperty(TEST_PROPERTY, Boolean.TRUE);
498         cc.addProperty(TEST_PROPERTY, "a value");
499         assertThrows(IllegalArgumentException.class, () -> cc.getSource(TEST_PROPERTY));
500     }
501 
502     /**
503      * Tests the getSource() method for a null key. This should cause an exception.
504      */
505     @Test
506     void testGetSourceNull() {
507         assertThrows(IllegalArgumentException.class, () -> cc.getSource(null));
508     }
509 
510     /**
511      * Tests the getSource() method if the property is defined in a single child configuration.
512      */
513     @Test
514     void testGetSourceSingle() {
515         setUpSourceTest();
516         conf1.addProperty(TEST_PROPERTY, Boolean.TRUE);
517         assertSame(conf1, cc.getSource(TEST_PROPERTY));
518     }
519 
520     /**
521      * Tests the getSource() method for an unknown property key.
522      */
523     @Test
524     void testGetSourceUnknown() {
525         setUpSourceTest();
526         assertNull(cc.getSource(TEST_PROPERTY));
527     }
528 
529     /**
530      * Tests querying a string array when a tricky interpolation is involved.
531      */
532     @Test
533     void testGetStringArrayWithInterpolation() {
534         prepareInterpolationTest();
535         final String[] values = cc.getStringArray("bar");
536         assertArrayEquals(new String[] {"override"}, values);
537     }
538 
539     @Test
540     void testGetStringWithDefaults() {
541         final BaseConfiguration defaults = new BaseConfiguration();
542         defaults.addProperty("default", "default string");
543 
544         final CompositeConfiguration c = new CompositeConfiguration(defaults);
545         c.setThrowExceptionOnMissing(cc.isThrowExceptionOnMissing());
546         c.addProperty("string", "test string");
547 
548         assertEquals("test string", c.getString("string"));
549         assertThrows(NoSuchElementException.class, () -> c.getString("XXX"));
550 
551         // test defaults
552         assertEquals("test string", c.getString("string", "some default value"));
553         assertEquals("default string", c.getString("default"));
554         assertEquals("default string", c.getString("default", "some default value"));
555         assertEquals("some default value", c.getString("XXX", "some default value"));
556     }
557 
558     @Test
559     void testGettingConfiguration() throws Exception {
560         cc.addConfiguration(conf1);
561         cc.addConfiguration(xmlConf);
562         assertEquals(PropertiesConfiguration.class, cc.getConfiguration(0).getClass());
563         assertEquals(XMLConfiguration.class, cc.getConfiguration(1).getClass());
564     }
565 
566     /**
567      * Tests retrieving subsets of configurations
568      */
569     @Test
570     void testGettingSubset() throws Exception {
571         cc.addConfiguration(conf1);
572         cc.addConfiguration(xmlConf);
573 
574         Configuration subset = cc.subset("test");
575         assertNotNull(subset);
576         assertFalse(subset.isEmpty());
577         assertEquals("1", subset.getString("short"));
578 
579         cc.setProperty("test.short", "43");
580         subset = cc.subset("test");
581         assertEquals("43", subset.getString("short"));
582     }
583 
584     @Test
585     void testInstanciateWithCollection() {
586         final Collection<Configuration> configs = new ArrayList<>();
587         configs.add(xmlConf);
588         configs.add(conf1);
589         configs.add(conf2);
590 
591         final CompositeConfiguration config = new CompositeConfiguration(configs);
592         assertEquals(4, config.getNumberOfConfigurations());
593         assertTrue(config.getInMemoryConfiguration().isEmpty());
594     }
595 
596     /**
597      * Tests whether interpolation works if a variable references a property with multiple values. This test is related to
598      * CONFIGURATION-632.
599      */
600     @Test
601     void testInterpolationArrayReference() {
602         final Configuration props = new PropertiesConfiguration();
603         final String[] values = {"a", "property", "with", "multiple", "values"};
604         props.addProperty("keyMultiValues", values);
605         props.addProperty("keyReference", "${keyMultiValues}");
606         cc.addConfiguration(props);
607         assertArrayEquals(values, cc.getStringArray("keyReference"));
608     }
609 
610     /**
611      * Tests whether interpolation works if multiple configurations are involved. This test is related to CONFIGURATION-441.
612      */
613     @Test
614     void testInterpolationInMultipleConfigs() {
615         final Configuration c1 = new PropertiesConfiguration();
616         c1.addProperty("property.one", "one");
617         c1.addProperty("property.two", "two");
618         final Configuration c2 = new PropertiesConfiguration();
619         c2.addProperty("property.one.ref", "${property.one}");
620         cc.addConfiguration(c1);
621         cc.addConfiguration(c2);
622         assertEquals("one", cc.getString("property.one.ref"));
623     }
624 
625     /**
626      * Tests {@code List} parsing.
627      */
628     @Test
629     void testList() throws Exception {
630         cc.addConfiguration(conf1);
631         cc.addConfiguration(xmlConf);
632 
633         List<Object> packages = cc.getList("packages");
634         // we should get 3 packages here
635         assertEquals(3, packages.size());
636 
637         final List<Object> defaultList = new ArrayList<>();
638         defaultList.add("1");
639         defaultList.add("2");
640 
641         packages = cc.getList("packages.which.dont.exist", defaultList);
642         // we should get 2 packages here
643         assertEquals(2, packages.size());
644 
645     }
646 
647     /**
648      * Tests whether global interpolation works with lists.
649      */
650     @Test
651     void testListInterpolation() {
652         final PropertiesConfiguration c1 = new PropertiesConfiguration();
653         c1.addProperty("c1.value", "test1");
654         c1.addProperty("c1.value", "${c2.value}");
655         cc.addConfiguration(c1);
656         final PropertiesConfiguration c2 = new PropertiesConfiguration();
657         c2.addProperty("c2.value", "test2");
658         cc.addConfiguration(c2);
659         final List<Object> lst = cc.getList("c1.value");
660         assertEquals(Arrays.asList("test1", "test2"), lst);
661     }
662 
663     /**
664      * Tests {@code List} parsing.
665      */
666     @Test
667     void testMultipleTypesOfConfigs() throws Exception {
668         cc.addConfiguration(conf1);
669         cc.addConfiguration(xmlConf);
670         assertEquals(1, cc.getInt("test.short"));
671         cc.clear();
672 
673         cc.addConfiguration(xmlConf);
674         cc.addConfiguration(conf1);
675         assertEquals(8, cc.getInt("test.short"));
676     }
677 
678     @Test
679     void testPropertyExistsInOnlyOneConfig() throws Exception {
680         cc.addConfiguration(conf1);
681         cc.addConfiguration(xmlConf);
682         assertEquals("value", cc.getString("element"));
683     }
684 
685     /**
686      * Tests whether removing a child configuration is synchronized.
687      */
688     @Test
689     void testRemoveConfigurationSynchronized() {
690         final SynchronizerTestImpl sync = installSynchronizer();
691         cc.removeConfiguration(conf1);
692         sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE);
693     }
694 
695     /**
696      * Tests whether the in-memory configuration can be replaced by a new child configuration.
697      */
698     @Test
699     void testReplaceInMemoryConfig() {
700         conf1.setProperty(TEST_PROPERTY, "conf1");
701         conf2.setProperty(TEST_PROPERTY, "conf2");
702         cc.addConfiguration(conf1, true);
703         cc.addProperty("newProperty1", "newValue1");
704         cc.addConfiguration(conf2, true);
705         cc.addProperty("newProperty2", "newValue2");
706         assertEquals("conf1", cc.getString(TEST_PROPERTY));
707         assertEquals("newValue1", conf1.getString("newProperty1"));
708         assertEquals("newValue2", conf2.getString("newProperty2"));
709     }
710 
711     /**
712      * Tests changing the list delimiter handler.
713      */
714     @Test
715     void testSetListDelimiter() {
716         cc.setListDelimiterHandler(new DefaultListDelimiterHandler('/'));
717         checkSetListDelimiterHandler();
718     }
719 
720     /**
721      * Tests whether the correct list delimiter handler is set after a clear operation.
722      */
723     @Test
724     void testSetListDelimiterAfterClear() {
725         cc.setListDelimiterHandler(new DefaultListDelimiterHandler('/'));
726         cc.clear();
727         checkSetListDelimiterHandler();
728     }
729 
730     /**
731      * Tests the behavior of setListDelimiterHandler() if the in-memory configuration is not derived from BaseConfiguration.
732      * This test is related to CONFIGURATION-476.
733      */
734     @Test
735     void testSetListDelimiterInMemoryConfigNonBaseConfig() {
736         final Configuration inMemoryConfig = mock(Configuration.class);
737         cc = new CompositeConfiguration(inMemoryConfig);
738         assertDoesNotThrow(() -> cc.setListDelimiterHandler(new DefaultListDelimiterHandler(';')));
739     }
740 
741     /**
742      * Tests setting values. These are set in memory mode only!
743      */
744     @Test
745     void testSettingMissingProperty() throws Exception {
746         cc.addConfiguration(conf1);
747         cc.addConfiguration(xmlConf);
748         cc.setProperty("my.new.property", "supernew");
749         assertEquals("supernew", cc.getString("my.new.property"));
750     }
751 
752     /**
753      * Tests {@code String} array parsing.
754      */
755     @Test
756     void testStringArray() throws Exception {
757         cc.addConfiguration(conf1);
758         cc.addConfiguration(xmlConf);
759 
760         String[] packages = cc.getStringArray("packages");
761         // we should get 3 packages here
762         assertEquals(3, packages.length);
763 
764         packages = cc.getStringArray("packages.which.dont.exist");
765         // we should get 0 packages here
766         assertEquals(0, packages.length);
767     }
768 
769     @Test
770     void testStringArrayInterpolation() {
771         final CompositeConfiguration config = new CompositeConfiguration();
772         config.addProperty("base", "foo");
773         config.addProperty("list", "${base}.bar1");
774         config.addProperty("list", "${base}.bar2");
775         config.addProperty("list", "${base}.bar3");
776 
777         final String[] array = config.getStringArray("list");
778         assertArrayEquals(new String[] {"foo.bar1", "foo.bar2", "foo.bar3"}, array);
779     }
780 
781     /**
782      * Tests subsets and still can resolve elements
783      */
784     @Test
785     void testSubsetCanResolve() throws Exception {
786         cc = new CompositeConfiguration();
787         final BaseConfiguration config = new BaseConfiguration();
788         config.addProperty("subset.tempfile", "${java.io.tmpdir}/file.tmp");
789         cc.addConfiguration(config);
790         cc.addConfiguration(ConfigurationConverter.getConfiguration(System.getProperties()));
791 
792         final Configuration subset = cc.subset("subset");
793         assertEquals(FileUtils.getTempDirectoryPath() + "/file.tmp", subset.getString("tempfile"));
794     }
795 
796     @Test
797     void testThrowExceptionOnMissing() {
798         assertTrue(cc.isThrowExceptionOnMissing());
799     }
800 
801     /**
802      * Tests whether a configuration can act as both regular child configuration and in-memory configuration. This test is
803      * related to CONFIGURATION-471.
804      */
805     @Test
806     void testUseChildConfigAsInMemoryConfig() {
807         conf1.setProperty(TEST_PROPERTY, "conf1");
808         conf2.setProperty(TEST_PROPERTY, "conf2");
809         cc.addConfiguration(conf1, true);
810         cc.addConfiguration(conf2);
811         assertEquals(2, cc.getNumberOfConfigurations());
812         assertEquals("conf1", cc.getString(TEST_PROPERTY));
813         cc.addProperty("newProperty", "newValue");
814         assertEquals("newValue", conf1.getString("newProperty"));
815     }
816 }