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  package org.apache.commons.configuration2;
18  
19  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
22  import static org.junit.jupiter.api.Assertions.assertNull;
23  import static org.junit.jupiter.api.Assertions.assertSame;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.junit.jupiter.api.Assertions.assertTrue;
26  import static org.mockito.Mockito.mock;
27  import static org.mockito.Mockito.when;
28  
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.Collection;
32  import java.util.Collections;
33  import java.util.HashMap;
34  import java.util.Iterator;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.NoSuchElementException;
38  
39  import org.apache.commons.configuration2.convert.ConversionHandler;
40  import org.apache.commons.configuration2.convert.DefaultConversionHandler;
41  import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
42  import org.apache.commons.configuration2.convert.DisabledListDelimiterHandler;
43  import org.apache.commons.configuration2.event.ConfigurationEvent;
44  import org.apache.commons.configuration2.event.EventListener;
45  import org.apache.commons.configuration2.event.EventType;
46  import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
47  import org.apache.commons.configuration2.interpol.Lookup;
48  import org.apache.commons.lang3.ArrayUtils;
49  import org.junit.jupiter.api.Test;
50  
51  /**
52   * A test class for some of the basic functionality implemented by AbstractConfiguration.
53   */
54  public class TestAbstractConfigurationBasicFeatures {
55  
56      /**
57       * An event listener implementation that simply collects all received configuration events.
58       */
59      private static final class CollectingConfigurationListener implements EventListener<ConfigurationEvent> {
60  
61          private final List<ConfigurationEvent> events = new ArrayList<>();
62  
63          @Override
64          public void onEvent(final ConfigurationEvent event) {
65              events.add(event);
66          }
67      }
68  
69      /**
70       * A test configuration implementation. This implementation inherits directly from AbstractConfiguration. For
71       * implementing the required functionality another implementation of AbstractConfiguration is used; all methods that
72       * need to be implemented delegate to this wrapped configuration.
73       */
74      static class TestConfigurationImpl extends AbstractConfiguration {
75  
76          /** Stores the underlying configuration. */
77          private final AbstractConfiguration config;
78  
79          public TestConfigurationImpl(final AbstractConfiguration wrappedConfig) {
80              config = wrappedConfig;
81          }
82  
83          @Override
84          protected void addPropertyDirect(final String key, final Object value) {
85              config.addPropertyDirect(key, value);
86          }
87  
88          @Override
89          protected void clearPropertyDirect(final String key) {
90              config.clearPropertyDirect(key);
91          }
92  
93          @Override
94          protected boolean containsKeyInternal(final String key) {
95              return config.containsKey(key);
96          }
97  
98          @Override
99          protected boolean containsValueInternal(final Object value) {
100             return config.containsValue(value);
101         }
102 
103         @Override
104         protected Iterator<String> getKeysInternal() {
105             return config.getKeys();
106         }
107 
108         @Override
109         protected Object getPropertyInternal(final String key) {
110             return config.getProperty(key);
111         }
112 
113         public AbstractConfiguration getUnderlyingConfiguration() {
114             return config;
115         }
116 
117         @Override
118         protected boolean isEmptyInternal() {
119             return config.isEmpty();
120         }
121     }
122 
123     /** Constant for text to be used in tests for variable substitution. */
124     private static final String SUBST_TXT = "The ${animal} jumps over the ${target}.";
125 
126     /** Constant for the prefix of test keys. */
127     private static final String KEY_PREFIX = "key";
128 
129     /** Constant for the number of properties in tests for copy operations. */
130     private static final int PROP_COUNT = 12;
131 
132     /**
133      * Prepares a test configuration for a test for a list conversion. The configuration is populated with a list property.
134      * The returned list contains the expected list values converted to integers.
135      *
136      * @param config the test configuration
137      * @return the list with expected values
138      */
139     private static List<Integer> prepareListTest(final PropertiesConfiguration config) {
140         final List<Integer> expected = new ArrayList<>(PROP_COUNT);
141         for (int i = 0; i < PROP_COUNT; i++) {
142             config.addProperty(KEY_PREFIX, String.valueOf(i));
143             expected.add(i);
144         }
145         return expected;
146     }
147 
148     /**
149      * Helper method for adding properties with multiple values.
150      *
151      * @param config the configuration to be used for testing
152      */
153     private void checkAddListProperty(final AbstractConfiguration config) {
154         config.addProperty("test", "value1");
155         final Object[] lstValues1 = {"value2", "value3"};
156         final Object[] lstValues2 = {"value4", "value5", "value6"};
157         config.addProperty("test", lstValues1);
158         config.addProperty("test", Arrays.asList(lstValues2));
159         final List<Object> lst = config.getList("test");
160 
161         final List<Object> expected = new ArrayList<>();
162         for (int i = 0; i < lst.size(); i++) {
163             expected.add("value" + (i + 1));
164         }
165         assertEquals(expected, lst);
166     }
167 
168     /**
169      * Tests whether the correct events are received for a copy operation.
170      *
171      * @param l the event listener
172      * @param src the configuration that was copied
173      * @param eventType the expected event type
174      */
175     private void checkCopyEvents(final CollectingConfigurationListener l, final Configuration src, final EventType<?> eventType) {
176         final Map<String, ConfigurationEvent> events = new HashMap<>();
177         for (final ConfigurationEvent e : l.events) {
178             assertEquals(eventType, e.getEventType());
179             assertTrue(src.containsKey(e.getPropertyName()), "Unknown property: " + e.getPropertyName());
180             if (!e.isBeforeUpdate()) {
181                 assertTrue(events.containsKey(e.getPropertyName()));
182             } else {
183                 events.put(e.getPropertyName(), e);
184             }
185         }
186 
187         for (final Iterator<String> it = src.getKeys(); it.hasNext();) {
188             final String key = it.next();
189             assertTrue(events.containsKey(key), "No event received for key " + key);
190         }
191     }
192 
193     /**
194      * Helper method for checking getList() if the property value is a scalar.
195      *
196      * @param value the value of the property
197      */
198     private void checkGetListScalar(final Object value) {
199         final BaseConfiguration config = new BaseConfiguration();
200         config.addProperty(KEY_PREFIX, value);
201         final List<Object> lst = config.getList(KEY_PREFIX);
202         assertEquals(Arrays.asList(value.toString()), lst);
203     }
204 
205     /**
206      * Helper method for checking getStringArray() if the property value is a scalar.
207      *
208      * @param value the value of the property
209      */
210     private void checkGetStringArrayScalar(final Object value) {
211         final BaseConfiguration config = new BaseConfiguration();
212         config.addProperty(KEY_PREFIX, value);
213         final String[] array = config.getStringArray(KEY_PREFIX);
214         assertArrayEquals(new String[] {value.toString()}, array);
215     }
216 
217     /**
218      * Tests the values of list properties after a copy operation.
219      *
220      * @param config the configuration to test
221      */
222     private void checkListProperties(final Configuration config) {
223         List<Object> values = config.getList("list1");
224         assertEquals(3, values.size());
225         values = config.getList("list2");
226         assertEquals(Arrays.asList("3,1415", "9,81"), values);
227     }
228 
229     /**
230      * Creates the destination configuration for testing the copy() and append() methods. This configuration contains keys
231      * with a running index and corresponding values starting with the prefix "value".
232      *
233      * @return the destination configuration for copy operations
234      */
235     private AbstractConfiguration setUpDestConfig() {
236         final AbstractConfiguration config = new TestConfigurationImpl(new PropertiesConfiguration());
237         config.setListDelimiterHandler(new DefaultListDelimiterHandler(','));
238         for (int i = 0; i < PROP_COUNT; i++) {
239             config.addProperty(KEY_PREFIX + i, "value" + i);
240         }
241         return config;
242     }
243 
244     /**
245      * Creates the source configuration for testing the copy() and append() methods. This configuration contains keys with
246      * an odd index and values starting with the prefix "src". There are also some list properties.
247      *
248      * @return the source configuration for copy operations
249      */
250     private Configuration setUpSourceConfig() {
251         final BaseConfiguration config = new BaseConfiguration();
252         config.setListDelimiterHandler(new DefaultListDelimiterHandler(','));
253         for (int i = 1; i < PROP_COUNT; i += 2) {
254             config.addProperty(KEY_PREFIX + i, "src" + i);
255         }
256         config.addProperty("list1", "1,2,3");
257         config.addProperty("list2", "3\\,1415,9\\,81");
258         return config;
259     }
260 
261     /**
262      * Tests adding list properties. The single elements of the list should be added.
263      */
264     @Test
265     void testAddPropertyList() {
266         checkAddListProperty(new TestConfigurationImpl(new PropertiesConfiguration()));
267     }
268 
269     /**
270      * Tests adding list properties if delimiter parsing is disabled.
271      */
272     @Test
273     void testAddPropertyListNoDelimiterParsing() {
274         final AbstractConfiguration config = new TestConfigurationImpl(new PropertiesConfiguration());
275         checkAddListProperty(config);
276     }
277 
278     /**
279      * Tests the append() method.
280      */
281     @Test
282     void testAppend() {
283         final AbstractConfiguration config = setUpDestConfig();
284         final Configuration srcConfig = setUpSourceConfig();
285         config.append(srcConfig);
286         for (int i = 0; i < PROP_COUNT; i++) {
287             final String key = KEY_PREFIX + i;
288             if (srcConfig.containsKey(key)) {
289                 final List<Object> values = config.getList(key);
290                 assertEquals(Arrays.asList("value" + i, "src" + i), values, "Wrong values for " + key);
291             } else {
292                 assertEquals("value" + i, config.getProperty(key), "Value modified: " + key);
293             }
294         }
295     }
296 
297     /**
298      * Tests whether the list delimiter is correctly handled if a configuration is appended.
299      */
300     @Test
301     void testAppendDelimiterHandling() {
302         final BaseConfiguration srcConfig = new BaseConfiguration();
303         final BaseConfiguration dstConfig = new BaseConfiguration();
304         dstConfig.setListDelimiterHandler(new DefaultListDelimiterHandler(','));
305         srcConfig.setProperty(KEY_PREFIX, "C:\\Temp\\,D:\\Data");
306         dstConfig.append(srcConfig);
307         assertEquals(srcConfig.getString(KEY_PREFIX), dstConfig.getString(KEY_PREFIX));
308     }
309 
310     /**
311      * Tests the events generated by an append() operation.
312      */
313     @Test
314     void testAppendEvents() {
315         final AbstractConfiguration config = setUpDestConfig();
316         final Configuration srcConfig = setUpSourceConfig();
317         final CollectingConfigurationListener l = new CollectingConfigurationListener();
318         config.addEventListener(ConfigurationEvent.ANY, l);
319         config.append(srcConfig);
320         checkCopyEvents(l, srcConfig, ConfigurationEvent.ADD_PROPERTY);
321     }
322 
323     /**
324      * Tests appending a null configuration. This should be a noop.
325      */
326     @Test
327     void testAppendNull() {
328         final AbstractConfiguration config = setUpDestConfig();
329         config.append(null);
330         ConfigurationAssert.assertConfigurationEquals(setUpDestConfig(), config);
331     }
332 
333     /**
334      * Tests the append() method when properties with multiple values and escaped list delimiters are involved.
335      */
336     @Test
337     void testAppendWithLists() {
338         final AbstractConfiguration config = setUpDestConfig();
339         config.append(setUpSourceConfig());
340         checkListProperties(config);
341     }
342 
343     /**
344      * Tests the clear() implementation of AbstractConfiguration if the iterator returned by getKeys() does not support the
345      * remove() operation.
346      */
347     @Test
348     void testClearIteratorNoRemove() {
349         final AbstractConfiguration config = new TestConfigurationImpl(new BaseConfiguration()) {
350             // return an iterator that does not support remove operations
351             @Override
352             protected Iterator<String> getKeysInternal() {
353                 final Collection<String> keyCol = new ArrayList<>();
354                 ConfigurationAssert.appendKeys(getUnderlyingConfiguration(), keyCol);
355                 final String[] keys = keyCol.toArray(new String[keyCol.size()]);
356                 return Arrays.asList(keys).iterator();
357             }
358         };
359         for (int i = 0; i < 20; i++) {
360             config.addProperty("key" + i, "value" + i);
361         }
362         config.clear();
363         assertTrue(config.isEmpty());
364     }
365 
366     /**
367      * Tests the copy() method.
368      */
369     @Test
370     void testCopy() {
371         final AbstractConfiguration config = setUpDestConfig();
372         final Configuration srcConfig = setUpSourceConfig();
373         config.copy(srcConfig);
374         for (int i = 0; i < PROP_COUNT; i++) {
375             final String key = KEY_PREFIX + i;
376             if (srcConfig.containsKey(key)) {
377                 assertEquals(srcConfig.getProperty(key), config.getProperty(key), "Value not replaced: " + key);
378             } else {
379                 assertEquals("value" + i, config.getProperty(key), "Value modified: " + key);
380             }
381         }
382     }
383 
384     /**
385      * Tests whether list delimiters are correctly handled when copying a configuration.
386      */
387     @Test
388     void testCopyDelimiterHandling() {
389         final BaseConfiguration srcConfig = new BaseConfiguration();
390         final BaseConfiguration dstConfig = new BaseConfiguration();
391         dstConfig.setListDelimiterHandler(new DefaultListDelimiterHandler(','));
392         srcConfig.setProperty(KEY_PREFIX, "C:\\Temp\\,D:\\Data");
393         dstConfig.copy(srcConfig);
394         assertEquals(srcConfig.getString(KEY_PREFIX), dstConfig.getString(KEY_PREFIX));
395     }
396 
397     /**
398      * Tests the events generated by a copy() operation.
399      */
400     @Test
401     void testCopyEvents() {
402         final AbstractConfiguration config = setUpDestConfig();
403         final Configuration srcConfig = setUpSourceConfig();
404         final CollectingConfigurationListener l = new CollectingConfigurationListener();
405         config.addEventListener(ConfigurationEvent.ANY, l);
406         config.copy(srcConfig);
407         checkCopyEvents(l, srcConfig, ConfigurationEvent.SET_PROPERTY);
408     }
409 
410     /**
411      * Tests copying a null configuration. This should be a noop.
412      */
413     @Test
414     void testCopyNull() {
415         final AbstractConfiguration config = setUpDestConfig();
416         config.copy(null);
417         ConfigurationAssert.assertConfigurationEquals(setUpDestConfig(), config);
418     }
419 
420     /**
421      * Tests the copy() method if properties with multiple values and escaped list delimiters are involved.
422      */
423     @Test
424     void testCopyWithLists() {
425         final Configuration srcConfig = setUpSourceConfig();
426         final AbstractConfiguration config = setUpDestConfig();
427         config.copy(srcConfig);
428         checkListProperties(config);
429     }
430 
431     /**
432      * Tests an interpolation that leads to a cycle. This should throw an exception.
433      */
434     @Test
435     void testCyclicInterpolation() {
436         final PropertiesConfiguration config = new PropertiesConfiguration();
437         config.addProperty("animal", "${animal_attr} ${species}");
438         config.addProperty("animal_attr", "quick brown");
439         config.addProperty("species", "${animal}");
440         config.addProperty(KEY_PREFIX, "This is a ${animal}");
441         assertThrows(IllegalStateException.class, () -> config.getString(KEY_PREFIX));
442     }
443 
444     /**
445      * Tests whether a configuration instance has a default conversion hander.
446      */
447     @Test
448     void testDefaultConversionHandler() {
449         final PropertiesConfiguration config = new PropertiesConfiguration();
450         assertEquals(DefaultConversionHandler.class, config.getConversionHandler().getClass());
451     }
452 
453     /**
454      * Tests that the default conversion handler is shared between all configuration instances.
455      */
456     @Test
457     void testDefaultConversionHandlerSharedInstance() {
458         final PropertiesConfiguration config = new PropertiesConfiguration();
459         final PropertiesConfiguration config2 = new PropertiesConfiguration();
460         assertSame(config.getConversionHandler(), config2.getConversionHandler());
461     }
462 
463     /**
464      * Tests the default list delimiter hander.
465      */
466     @Test
467     void testDefaultListDelimiterHandler() {
468         final BaseConfiguration config = new BaseConfiguration();
469         assertInstanceOf(DisabledListDelimiterHandler.class, config.getListDelimiterHandler());
470     }
471 
472     /**
473      * Tests the generic get() method.
474      */
475     @Test
476     void testGet() {
477         final PropertiesConfiguration config = new PropertiesConfiguration();
478         final Integer value = 20130816;
479         config.addProperty(KEY_PREFIX, value.toString());
480         assertEquals(value, config.get(Integer.class, KEY_PREFIX));
481     }
482 
483     /**
484      * Tests whether conversion to an array is possible.
485      */
486     @Test
487     void testGetArray() {
488         final PropertiesConfiguration config = new PropertiesConfiguration();
489         final Integer[] expected = new Integer[PROP_COUNT];
490         for (int i = 0; i < PROP_COUNT; i++) {
491             config.addProperty(KEY_PREFIX, String.valueOf(i));
492             expected[i] = Integer.valueOf(i);
493         }
494         final Integer[] result = config.get(Integer[].class, KEY_PREFIX);
495         assertArrayEquals(expected, result);
496     }
497 
498     /**
499      * Tests getArray() if the default value is not an array.
500      */
501     @Test
502     void testGetArrayDefaultValueNotAnArray() {
503         final PropertiesConfiguration config = new PropertiesConfiguration();
504         assertThrows(IllegalArgumentException.class, () -> config.getArray(Integer.class, KEY_PREFIX, this));
505     }
506 
507     /**
508      * Tests getArray() if the default value is an array with a different component type.
509      */
510     @Test
511     void testGetArrayDefaultValueWrongComponentClass() {
512         final PropertiesConfiguration config = new PropertiesConfiguration();
513         assertThrows(IllegalArgumentException.class, () -> config.getArray(Integer.class, KEY_PREFIX, new short[1]));
514     }
515 
516     /**
517      * Tests a conversion to an array of primitive types.
518      */
519     @Test
520     void testGetArrayPrimitive() {
521         final PropertiesConfiguration config = new PropertiesConfiguration();
522         final short[] expected = new short[PROP_COUNT];
523         for (int i = 0; i < PROP_COUNT; i++) {
524             config.addProperty(KEY_PREFIX, String.valueOf(i));
525             expected[i] = (short) i;
526         }
527         final short[] result = config.get(short[].class, KEY_PREFIX, ArrayUtils.EMPTY_SHORT_ARRAY);
528         assertArrayEquals(expected, result);
529     }
530 
531     /**
532      * Tests get() for an unknown array property if no default value is provided.
533      */
534     @Test
535     void testGetArrayUnknownNoDefault() {
536         final PropertiesConfiguration config = new PropertiesConfiguration();
537         assertNull(config.get(Integer[].class, KEY_PREFIX));
538     }
539 
540     /**
541      * Tests get() for an unknown array property if a default value is provided.
542      */
543     @Test
544     void testGetArrayUnknownWithDefault() {
545         final PropertiesConfiguration config = new PropertiesConfiguration();
546         final int[] defValue = {1, 2, 3};
547         assertArrayEquals(defValue, config.get(int[].class, KEY_PREFIX, defValue));
548     }
549 
550     /**
551      * Tests a conversion to a collection.
552      */
553     @Test
554     void testGetCollection() {
555         final PropertiesConfiguration config = new PropertiesConfiguration();
556         final List<Integer> expected = prepareListTest(config);
557         final List<Integer> result = new ArrayList<>(PROP_COUNT);
558         assertSame(result, config.getCollection(Integer.class, KEY_PREFIX, result));
559         assertEquals(expected, result);
560     }
561 
562     /**
563      * Tests getCollection() if no target collection is provided.
564      */
565     @Test
566     void testGetCollectionNullTarget() {
567         final PropertiesConfiguration config = new PropertiesConfiguration();
568         final List<Integer> expected = prepareListTest(config);
569         final Collection<Integer> result = config.getCollection(Integer.class, KEY_PREFIX, null, new ArrayList<>());
570         assertEquals(expected, result);
571     }
572 
573     /**
574      * Tests whether a single value property can be converted to a collection.
575      */
576     @Test
577     void testGetCollectionSingleValue() {
578         final PropertiesConfiguration config = new PropertiesConfiguration();
579         config.addProperty(KEY_PREFIX, "1");
580         final List<Integer> result = new ArrayList<>(1);
581         config.getCollection(Integer.class, KEY_PREFIX, result);
582         assertEquals(Arrays.asList(1), result);
583     }
584 
585     /**
586      * Tests getCollection() for an unknown property if no default value is provided.
587      */
588     @Test
589     void testGetCollectionUnknownNoDefault() {
590         final PropertiesConfiguration config = new PropertiesConfiguration();
591         final List<Integer> result = new ArrayList<>();
592         assertNull(config.getCollection(Integer.class, KEY_PREFIX, result));
593         assertTrue(result.isEmpty());
594     }
595 
596     /**
597      * Tests getCollection() for an unknown property if a default collection is provided.
598      */
599     @Test
600     void testGetCollectionUnknownWithDefault() {
601         final PropertiesConfiguration config = new PropertiesConfiguration();
602         final List<Integer> defValue = Arrays.asList(1, 2, 4, 8, 16, 32);
603         final Collection<Integer> result = config.getCollection(Integer.class, KEY_PREFIX, null, defValue);
604         assertEquals(defValue, result);
605     }
606 
607     /**
608      * Tries to query an encoded string without a decoder.
609      */
610     @Test
611     void testGetEncodedStringNoDecoder() {
612         final PropertiesConfiguration config = new PropertiesConfiguration();
613         assertThrows(IllegalArgumentException.class, () -> config.getEncodedString(KEY_PREFIX, null));
614     }
615 
616     /**
617      * Tries to query an encoded string with the default decoder if this property is not defined.
618      */
619     @Test
620     void testGetEncodedStringNoDefaultDecoderDefined() {
621         final PropertiesConfiguration config = new PropertiesConfiguration();
622         assertThrows(IllegalStateException.class, () -> config.getEncodedString(KEY_PREFIX));
623     }
624 
625     /**
626      * Tests whether undefined keys are handled when querying encoded strings.
627      */
628     @Test
629     void testGetEncodedStringNoValue() {
630         final ConfigurationDecoder decoder = mock(ConfigurationDecoder.class);
631         final PropertiesConfiguration config = new PropertiesConfiguration();
632         assertNull(config.getEncodedString(KEY_PREFIX, decoder));
633     }
634 
635     /**
636      * Tests whether an encoded value can be retrieved.
637      */
638     @Test
639     void testGetEncodedStringValue() {
640         final ConfigurationDecoder decoder = mock(ConfigurationDecoder.class);
641         final String value = "original value";
642         final String decodedValue = "decoded value";
643 
644         when(decoder.decode(value)).thenReturn(decodedValue);
645 
646         final PropertiesConfiguration config = new PropertiesConfiguration();
647         config.addProperty(KEY_PREFIX, value);
648         assertEquals(decodedValue, config.getEncodedString(KEY_PREFIX, decoder));
649     }
650 
651     /**
652      * Tests whether a default decoder can be set which is queried for encoded strings.
653      */
654     @Test
655     void testGetEncodedStringWithDefaultDecoder() {
656         final ConfigurationDecoder decoder = mock(ConfigurationDecoder.class);
657         final String value = "original value";
658         final String decodedValue = "decoded value";
659 
660         when(decoder.decode(value)).thenReturn(decodedValue);
661 
662         final PropertiesConfiguration config = new PropertiesConfiguration();
663         config.setConfigurationDecoder(decoder);
664         config.addProperty(KEY_PREFIX, value);
665         assertEquals(decodedValue, config.getEncodedString(KEY_PREFIX));
666     }
667 
668     /**
669      * Tests a conversion to a list.
670      */
671     @Test
672     void testGetList() {
673         final PropertiesConfiguration config = new PropertiesConfiguration();
674         final List<Integer> expected = prepareListTest(config);
675         final List<Integer> result = config.getList(Integer.class, KEY_PREFIX);
676         assertEquals(expected, result);
677     }
678 
679     /**
680      * Tests getList() for single non-string values.
681      */
682     @Test
683     void testGetListNonString() {
684         checkGetListScalar(Integer.valueOf(42));
685         checkGetListScalar(Long.valueOf(42));
686         checkGetListScalar(Short.valueOf((short) 42));
687         checkGetListScalar(Byte.valueOf((byte) 42));
688         checkGetListScalar(Float.valueOf(42));
689         checkGetListScalar(Double.valueOf(42));
690         checkGetListScalar(Boolean.TRUE);
691     }
692 
693     /**
694      * Tests a conversion to a list if the property is unknown and no default value is provided.
695      */
696     @Test
697     void testGetListUnknownNoDefault() {
698         final PropertiesConfiguration config = new PropertiesConfiguration();
699         assertNull(config.getList(Integer.class, KEY_PREFIX));
700     }
701 
702     /**
703      * Tests a conversion to a list if the property is unknown and a default list is provided.
704      */
705     @Test
706     void testGetListUnknownWithDefault() {
707         final PropertiesConfiguration config = new PropertiesConfiguration();
708         final List<Integer> defValue = Arrays.asList(1, 2, 3);
709         assertEquals(defValue, config.getList(Integer.class, KEY_PREFIX, defValue));
710     }
711 
712     /**
713      * Tests getStringArray() for single son-string values.
714      */
715     @Test
716     void testGetStringArrayNonString() {
717         checkGetStringArrayScalar(Integer.valueOf(42));
718         checkGetStringArrayScalar(Long.valueOf(42));
719         checkGetStringArrayScalar(Short.valueOf((short) 42));
720         checkGetStringArrayScalar(Byte.valueOf((byte) 42));
721         checkGetStringArrayScalar(Float.valueOf(42));
722         checkGetStringArrayScalar(Double.valueOf(42));
723         checkGetStringArrayScalar(Boolean.TRUE);
724     }
725 
726     /**
727      * Tests getStringArray() if the key cannot be found.
728      */
729     @Test
730     void testGetStringArrayUnknown() {
731         final BaseConfiguration config = new BaseConfiguration();
732         final String[] array = config.getStringArray(KEY_PREFIX);
733         assertEquals(0, array.length);
734     }
735 
736     /**
737      * Tests get() for an unknown property if no default value is provided.
738      */
739     @Test
740     void testGetUnknownNoDefaultValue() {
741         final PropertiesConfiguration config = new PropertiesConfiguration();
742         assertNull(config.get(Integer.class, KEY_PREFIX));
743     }
744 
745     /**
746      * Tests get() for an unknown property if a default value is provided.
747      */
748     @Test
749     void testGetUnknownWithDefaultValue() {
750         final PropertiesConfiguration config = new PropertiesConfiguration();
751         final Integer defaultValue = 2121;
752         assertEquals(defaultValue, config.get(Integer.class, KEY_PREFIX, defaultValue));
753     }
754 
755     /**
756      * Tests get() for an unknown property if the throwExceptionOnMissing flag is set.
757      */
758     @Test
759     void testGetUnknownWithThrowExceptionOnMissing() {
760         final PropertiesConfiguration config = new PropertiesConfiguration();
761         config.setThrowExceptionOnMissing(true);
762         assertThrows(NoSuchElementException.class, () -> config.get(Integer.class, KEY_PREFIX));
763     }
764 
765     /**
766      * Tests get() for an unknown property with a default value and the throwExceptionOnMissing flag. Because of the default
767      * value no exception should be thrown.
768      */
769     @Test
770     void testGetUnownWithDefaultValueThrowExceptionOnMissing() {
771         final PropertiesConfiguration config = new PropertiesConfiguration();
772         config.setThrowExceptionOnMissing(true);
773         final Integer defaultValue = 2121;
774         assertEquals(defaultValue, config.get(Integer.class, KEY_PREFIX, defaultValue));
775     }
776 
777     /**
778      * Tests whether a new {@code ConfigurationInterpolator} can be installed without providing custom lookups.
779      */
780     @Test
781     void testInstallInterpolatorNull() {
782         final AbstractConfiguration config = new TestConfigurationImpl(new PropertiesConfiguration());
783         config.installInterpolator(null, null);
784         assertTrue(config.getInterpolator().getLookups().isEmpty());
785         final List<Lookup> defLookups = config.getInterpolator().getDefaultLookups();
786         assertEquals(1, defLookups.size());
787         assertInstanceOf(ConfigurationLookup.class, defLookups.get(0));
788     }
789 
790     /**
791      * Tests whether a property can reference an array using interpolation. This is related to CONFIGURATION-633.
792      */
793     @Test
794     void testInterpolateArray() {
795         final PropertiesConfiguration config = new PropertiesConfiguration();
796         final String[] values = {"some", "test", "values"};
797         final String keyArray = "testArray";
798         config.addProperty(keyArray, values);
799         config.addProperty(KEY_PREFIX, "${" + keyArray + "}");
800 
801         assertArrayEquals(values, config.getStringArray(KEY_PREFIX));
802     }
803 
804     /**
805      * Tests whether environment variables can be interpolated.
806      */
807     @Test
808     void testInterpolateEnvironmentVariables() {
809         final AbstractConfiguration config = new TestConfigurationImpl(new PropertiesConfiguration());
810         InterpolationTestHelper.testInterpolationEnvironment(config);
811     }
812 
813     /**
814      * Tests escaping the variable marker, so that no interpolation will be performed.
815      */
816     @Test
817     void testInterpolateEscape() {
818         final AbstractConfiguration config = new TestConfigurationImpl(new PropertiesConfiguration());
819         config.setListDelimiterHandler(new DefaultListDelimiterHandler(','));
820         config.addProperty("mypath", "$${DB2UNIVERSAL_JDBC_DRIVER_PATH}/db2jcc.jar\\,$${DB2UNIVERSAL_JDBC_DRIVER_PATH}/db2jcc_license_cu.jar");
821         assertEquals("${DB2UNIVERSAL_JDBC_DRIVER_PATH}/db2jcc.jar,${DB2UNIVERSAL_JDBC_DRIVER_PATH}/db2jcc_license_cu.jar", config.getString("mypath"));
822     }
823 
824     /**
825      * Tests whether a property can reference a list using interpolation. This is related to CONFIGURATION-633.
826      */
827     @Test
828     void testInterpolateList() {
829         final PropertiesConfiguration config = new PropertiesConfiguration();
830         final List<String> values = Arrays.asList("some", "test", "values");
831         final String keyList = "testList";
832         config.addProperty(keyList, values);
833         config.addProperty(KEY_PREFIX, "${" + keyList + "}");
834 
835         assertEquals(values, config.getList(String.class, KEY_PREFIX));
836     }
837 
838     /**
839      * Tests complex interpolation where the variables' values contain in turn other variables.
840      */
841     @Test
842     void testInterpolateRecursive() {
843         final PropertiesConfiguration config = new PropertiesConfiguration();
844         config.addProperty("animal", "${animal_attr} fox");
845         config.addProperty("target", "${target_attr} dog");
846         config.addProperty("animal_attr", "quick brown");
847         config.addProperty("target_attr", "lazy");
848         config.addProperty(KEY_PREFIX, SUBST_TXT);
849         assertEquals("The quick brown fox jumps over the lazy dog.", config.getString(KEY_PREFIX));
850     }
851 
852     /**
853      * Tests the interpolation features.
854      */
855     @Test
856     void testInterpolateString() {
857         final PropertiesConfiguration config = new PropertiesConfiguration();
858         config.addProperty("animal", "quick brown fox");
859         config.addProperty("target", "lazy dog");
860         config.addProperty(KEY_PREFIX, SUBST_TXT);
861         assertEquals("The quick brown fox jumps over the lazy dog.", config.getString(KEY_PREFIX));
862     }
863 
864     /**
865      * Tests that variables with list values in interpolated string are resolved with the first element
866      * in the list.
867      */
868     @Test
869     void testInterpolateStringWithListVariable() {
870         final PropertiesConfiguration config = new PropertiesConfiguration();
871         final List<String> values = Arrays.asList("some", "test", "values");
872         final String keyList = "testList";
873         config.addProperty(keyList, values);
874         config.addProperty(KEY_PREFIX, "result = ${" + keyList + "}");
875 
876         assertEquals("result = some", config.getString(KEY_PREFIX));
877     }
878 
879     /**
880      * Tests interpolate() if the configuration does not have a {@code ConfigurationInterpolator}.
881      */
882     @Test
883     void testInterpolationNoInterpolator() {
884         final PropertiesConfiguration config = new PropertiesConfiguration();
885         config.addProperty("animal", "quick brown fox");
886         config.addProperty("target", "lazy dog");
887         config.addProperty(KEY_PREFIX, SUBST_TXT);
888         config.setInterpolator(null);
889         assertEquals(SUBST_TXT, config.getString(KEY_PREFIX));
890     }
891 
892     /**
893      * Tests interpolation if a variable is unknown. Then the variable won't be substituted.
894      */
895     @Test
896     void testInterpolationUnknownVariable() {
897         final PropertiesConfiguration config = new PropertiesConfiguration();
898         config.addProperty("animal", "quick brown fox");
899         config.addProperty(KEY_PREFIX, SUBST_TXT);
900         assertEquals("The quick brown fox jumps over the ${target}.", config.getString(KEY_PREFIX));
901     }
902 
903     /**
904      * Tests whether interpolation works in variable names.
905      */
906     @Test
907     void testNestedVariableInterpolation() {
908         final BaseConfiguration config = new BaseConfiguration();
909         config.getInterpolator().setEnableSubstitutionInVariables(true);
910         config.addProperty("java.version", "1.4");
911         config.addProperty("jre-1.4", "C:\\java\\1.4");
912         config.addProperty("jre.path", "${jre-${java.version}}");
913         assertEquals("C:\\java\\1.4", config.getString("jre.path"));
914     }
915 
916     /**
917      * Tests whether the conversion handler can be changed.
918      */
919     @Test
920     void testSetDefaultConversionHandler() {
921         final PropertiesConfiguration config = new PropertiesConfiguration();
922         final ConversionHandler handler = new DefaultConversionHandler();
923         config.setConversionHandler(handler);
924         assertSame(handler, config.getConversionHandler());
925     }
926 
927     /**
928      * Tries to set a null value for the conversion handler.
929      */
930     @Test
931     void testSetDefaultConversionHandlerNull() {
932         final PropertiesConfiguration configuration = new PropertiesConfiguration();
933         assertThrows(IllegalArgumentException.class, () -> configuration.setConversionHandler(null));
934     }
935 
936     /**
937      * Tests whether default lookups can be added to an already existing {@code ConfigurationInterpolator}.
938      */
939     @Test
940     void testSetDefaultLookupsExistingInterpolator() {
941         final Lookup look = mock(Lookup.class);
942         final AbstractConfiguration config = new TestConfigurationImpl(new PropertiesConfiguration());
943         config.getInterpolator().addDefaultLookup(new ConfigurationLookup(new PropertiesConfiguration()));
944         config.setDefaultLookups(Collections.singleton(look));
945         final List<Lookup> lookups = config.getInterpolator().getDefaultLookups();
946         assertEquals(3, lookups.size());
947         assertSame(look, lookups.get(1));
948         assertInstanceOf(ConfigurationLookup.class, lookups.get(2));
949     }
950 
951     /**
952      * Tests whether default lookups can be added if not {@code ConfigurationInterpolator} exists yet.
953      */
954     @Test
955     void testSetDefaultLookupsNoInterpolator() {
956         final Lookup look = mock(Lookup.class);
957         final AbstractConfiguration config = new TestConfigurationImpl(new PropertiesConfiguration());
958         config.setInterpolator(null);
959         config.setDefaultLookups(Collections.singleton(look));
960         final List<Lookup> lookups = config.getInterpolator().getDefaultLookups();
961         assertEquals(2, lookups.size());
962         assertSame(look, lookups.get(0));
963         assertInstanceOf(ConfigurationLookup.class, lookups.get(1));
964     }
965 
966     /**
967      * Tries to set a null list delimiter handler.
968      */
969     @Test
970     void testSetListDelimiterHandlerNull() {
971         final BaseConfiguration config = new BaseConfiguration();
972         assertThrows(IllegalArgumentException.class, () -> config.setListDelimiterHandler(null));
973     }
974 
975     /**
976      * Tests whether a parent {@code ConfigurationInterpolator} can be set if already a {@code ConfigurationInterpolator} is
977      * available.
978      */
979     @Test
980     void testSetParentInterpolatorExistingInterpolator() {
981         final ConfigurationInterpolator parent = mock(ConfigurationInterpolator.class);
982         final AbstractConfiguration config = new TestConfigurationImpl(new PropertiesConfiguration());
983         final ConfigurationInterpolator ci = config.getInterpolator();
984         config.setParentInterpolator(parent);
985         assertSame(parent, config.getInterpolator().getParentInterpolator());
986         assertSame(ci, config.getInterpolator());
987     }
988 
989     /**
990      * Tests whether a parent {@code ConfigurationInterpolator} can be set if currently no {@code ConfigurationInterpolator}
991      * is available.
992      */
993     @Test
994     void testSetParentInterpolatorNoInterpolator() {
995         final ConfigurationInterpolator parent = mock(ConfigurationInterpolator.class);
996         final AbstractConfiguration config = new TestConfigurationImpl(new PropertiesConfiguration());
997         config.setInterpolator(null);
998         config.setParentInterpolator(parent);
999         assertSame(parent, config.getInterpolator().getParentInterpolator());
1000     }
1001 
1002     /**
1003      * Tests whether prefix lookups can be added to an existing {@code ConfigurationInterpolator}.
1004      */
1005     @Test
1006     void testSetPrefixLookupsExistingInterpolator() {
1007         final Lookup look = mock(Lookup.class);
1008         final AbstractConfiguration config = new TestConfigurationImpl(new PropertiesConfiguration());
1009         final int count = config.getInterpolator().getLookups().size();
1010         final Map<String, Lookup> lookups = new HashMap<>();
1011         lookups.put("test", look);
1012         config.setPrefixLookups(lookups);
1013         final Map<String, Lookup> lookups2 = config.getInterpolator().getLookups();
1014         assertEquals(count + 1, lookups2.size());
1015         assertSame(look, lookups2.get("test"));
1016     }
1017 
1018     /**
1019      * Tests whether prefix lookups can be added if no {@code ConfigurationInterpolator} exists yet.
1020      */
1021     @Test
1022     void testSetPrefixLookupsNoInterpolator() {
1023         final Lookup look = mock(Lookup.class);
1024         final AbstractConfiguration config = new TestConfigurationImpl(new PropertiesConfiguration());
1025         config.setInterpolator(null);
1026         config.setPrefixLookups(Collections.singletonMap("test", look));
1027         final Map<String, Lookup> lookups = config.getInterpolator().getLookups();
1028         assertEquals(1, lookups.size());
1029         assertSame(look, lookups.get("test"));
1030     }
1031 
1032     /**
1033      * Tests the default implementation of sizeInternal().
1034      */
1035     @Test
1036     void testSizeInternal() {
1037         final AbstractConfiguration config = new TestConfigurationImpl(new PropertiesConfiguration());
1038         for (int i = 0; i < PROP_COUNT; i++) {
1039             config.addProperty(KEY_PREFIX + i, "value" + i);
1040         }
1041         assertEquals(PROP_COUNT, config.size());
1042     }
1043 }