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