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