View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.configuration2;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
23  import static org.junit.jupiter.api.Assertions.assertNotEquals;
24  import static org.junit.jupiter.api.Assertions.assertNotNull;
25  import static org.junit.jupiter.api.Assertions.assertNull;
26  import static org.junit.jupiter.api.Assertions.assertSame;
27  import static org.junit.jupiter.api.Assertions.assertThrows;
28  import static org.junit.jupiter.api.Assertions.assertTrue;
29  
30  import java.util.ArrayList;
31  import java.util.Arrays;
32  import java.util.Collection;
33  import java.util.HashMap;
34  import java.util.HashSet;
35  import java.util.Iterator;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.Set;
39  
40  import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
41  import org.apache.commons.configuration2.event.ConfigurationEvent;
42  import org.apache.commons.configuration2.event.EventListener;
43  import org.apache.commons.configuration2.event.EventListenerTestImpl;
44  import org.apache.commons.configuration2.tree.DefaultConfigurationKey;
45  import org.apache.commons.configuration2.tree.DefaultExpressionEngine;
46  import org.apache.commons.configuration2.tree.DefaultExpressionEngineSymbols;
47  import org.apache.commons.configuration2.tree.ExpressionEngine;
48  import org.apache.commons.configuration2.tree.ImmutableNode;
49  import org.apache.commons.configuration2.tree.InMemoryNodeModel;
50  import org.apache.commons.configuration2.tree.NodeHandler;
51  import org.apache.commons.configuration2.tree.NodeModel;
52  import org.apache.commons.configuration2.tree.NodeStructureHelper;
53  import org.junit.jupiter.api.BeforeEach;
54  import org.junit.jupiter.api.Test;
55  
56  /**
57   * Test class for {@code AbstractHierarchicalConfiguration}.
58   */
59  public class TestAbstractHierarchicalConfiguration {
60  
61      /**
62       * A concrete test implementation of {@code AbstractHierarchicalConfiguration}.
63       */
64      private static final class AbstractHierarchicalConfigurationTestImpl extends AbstractHierarchicalConfiguration<ImmutableNode> {
65          public AbstractHierarchicalConfigurationTestImpl(final InMemoryNodeModel model) {
66              super(model);
67          }
68  
69          @Override
70          public List<HierarchicalConfiguration<ImmutableNode>> childConfigurationsAt(final String key) {
71              throw new UnsupportedOperationException("Unexpected method call.");
72          }
73  
74          @Override
75          public List<HierarchicalConfiguration<ImmutableNode>> childConfigurationsAt(final String key, final boolean supportUpdates) {
76              throw new UnsupportedOperationException("Unexpected method call.");
77          }
78  
79          @Override
80          protected NodeModel<ImmutableNode> cloneNodeModel() {
81              return new InMemoryNodeModel(getModel().getNodeHandler().getRootNode());
82          }
83  
84          @Override
85          public SubnodeConfiguration configurationAt(final String key) {
86              throw new UnsupportedOperationException("Unexpected method call.");
87          }
88  
89          @Override
90          public SubnodeConfiguration configurationAt(final String key, final boolean supportUpdates) {
91              throw new UnsupportedOperationException("Unexpected method call.");
92          }
93  
94          @Override
95          public List<HierarchicalConfiguration<ImmutableNode>> configurationsAt(final String key) {
96              throw new UnsupportedOperationException("Unexpected method call.");
97          }
98  
99          @Override
100         public List<HierarchicalConfiguration<ImmutableNode>> configurationsAt(final String key, final boolean supportUpdates) {
101             throw new UnsupportedOperationException("Unexpected method call.");
102         }
103 
104         @Override
105         public List<ImmutableHierarchicalConfiguration> immutableChildConfigurationsAt(final String key) {
106             throw new UnsupportedOperationException("Unexpected method call.");
107         }
108 
109         @Override
110         public ImmutableHierarchicalConfiguration immutableConfigurationAt(final String key) {
111             throw new UnsupportedOperationException("Unexpected method call.");
112         }
113 
114         @Override
115         public ImmutableHierarchicalConfiguration immutableConfigurationAt(final String key, final boolean supportUpdates) {
116             throw new UnsupportedOperationException("Unexpected method call.");
117         }
118 
119         @Override
120         public List<ImmutableHierarchicalConfiguration> immutableConfigurationsAt(final String key) {
121             throw new UnsupportedOperationException("Unexpected method call.");
122         }
123     }
124 
125     /**
126      * Checks the content of the passed in configuration object. Used by some tests that copy a configuration.
127      *
128      * @param c the configuration to check
129      */
130     private static void checkContent(final Configuration c) {
131         for (int i = 0; i < NodeStructureHelper.tablesLength(); i++) {
132             assertEquals(NodeStructureHelper.table(i), c.getString("tables.table(" + i + ").name"));
133             for (int j = 0; j < NodeStructureHelper.fieldsLength(i); j++) {
134                 assertEquals(NodeStructureHelper.field(i, j), c.getString("tables.table(" + i + ").fields.field(" + j + ").name"));
135             }
136         }
137     }
138 
139     private static void checkGetProperty(final AbstractHierarchicalConfiguration<?> testConfig) {
140         assertNull(testConfig.getProperty("tables.table.resultset"));
141         assertNull(testConfig.getProperty("tables.table.fields.field"));
142 
143         Object prop = testConfig.getProperty("tables.table(0).fields.field.name");
144         Collection<?> collection = assertInstanceOf(Collection.class, prop);
145         assertEquals(NodeStructureHelper.fieldsLength(0), collection.size());
146 
147         prop = testConfig.getProperty("tables.table.fields.field.name");
148         collection = assertInstanceOf(Collection.class, prop);
149         assertEquals(totalFieldCount(), collection.size());
150 
151         prop = testConfig.getProperty("tables.table.fields.field(3).name");
152         collection = assertInstanceOf(Collection.class, prop);
153         assertEquals(2, collection.size());
154 
155         prop = testConfig.getProperty("tables.table(1).fields.field(2).name");
156         assertNotNull(prop);
157         assertEquals("creationDate", prop.toString());
158     }
159 
160     /**
161      * Creates a {@code DefaultConfigurationKey} object.
162      *
163      * @return the new key object
164      */
165     private static DefaultConfigurationKey createConfigurationKey() {
166         return new DefaultConfigurationKey(DefaultExpressionEngine.INSTANCE);
167     }
168 
169     /**
170      * Returns the total number of fields in the test data structure.
171      *
172      * @return the total number of fields
173      */
174     private static int totalFieldCount() {
175         int fieldCount = 0;
176         for (int i = 0; i < NodeStructureHelper.tablesLength(); i++) {
177             fieldCount += NodeStructureHelper.fieldsLength(i);
178         }
179         return fieldCount;
180     }
181 
182     /** The test configuration. */
183     private AbstractHierarchicalConfiguration<ImmutableNode> config;
184 
185     /**
186      * Helper method for checking keys using an alternative syntax.
187      */
188     private void checkAlternativeSyntax() {
189         assertNull(config.getProperty("tables/table/resultset"));
190         assertNull(config.getProperty("tables/table/fields/field"));
191 
192         Object prop = config.getProperty("tables/table[0]/fields/field/name");
193         Collection<?> collection = assertInstanceOf(Collection.class, prop);
194         assertEquals(NodeStructureHelper.fieldsLength(0), collection.size());
195 
196         prop = config.getProperty("tables/table/fields/field/name");
197         collection = assertInstanceOf(Collection.class, prop);
198         assertEquals(totalFieldCount(), collection.size());
199 
200         prop = config.getProperty("tables/table/fields/field[3]/name");
201         collection = assertInstanceOf(Collection.class, prop);
202         assertEquals(2, collection.size());
203 
204         prop = config.getProperty("tables/table[1]/fields/field[2]/name");
205         assertNotNull(prop);
206         assertEquals("creationDate", prop.toString());
207 
208         final Set<String> keys = ConfigurationAssert.keysToSet(config);
209         assertEquals(new HashSet<>(Arrays.asList("tables/table/name", "tables/table/fields/field/name")), keys);
210     }
211 
212     /**
213      * Helper method for testing the getKeys(String) method.
214      *
215      * @param prefix the key to pass into getKeys()
216      * @param expected the expected result
217      */
218     private void checkKeys(final String prefix, final String[] expected) {
219         final Set<String> expectedKeys = new HashSet<>();
220         for (final String anExpected : expected) {
221             expectedKeys.add(anExpected.startsWith(prefix) ? anExpected : prefix + "." + anExpected);
222         }
223 
224         final Set<String> keys = new HashSet<>();
225         final Iterator<String> itKeys = config.getKeys(prefix);
226         while (itKeys.hasNext()) {
227             final String key = itKeys.next();
228             keys.add(key);
229         }
230 
231         assertEquals(expectedKeys, keys);
232     }
233 
234     private ExpressionEngine createAlternativeExpressionEngine() {
235         return new DefaultExpressionEngine(new DefaultExpressionEngineSymbols.Builder(DefaultExpressionEngineSymbols.DEFAULT_SYMBOLS).setPropertyDelimiter("/")
236             .setIndexStart("[").setIndexEnd("]").create());
237     }
238 
239     /**
240      * Convenience method for obtaining the root node of the test configuration.
241      *
242      * @return the root node of the test configuration
243      */
244     private ImmutableNode getRootNode() {
245         return config.getModel().getNodeHandler().getRootNode();
246     }
247 
248     @BeforeEach
249     public void setUp() throws Exception {
250         final ImmutableNode root = new ImmutableNode.Builder(1).addChild(NodeStructureHelper.ROOT_TABLES_TREE).create();
251         config = new AbstractHierarchicalConfigurationTestImpl(new InMemoryNodeModel(root));
252     }
253 
254     @Test
255     void testAddNodes() {
256         final Collection<ImmutableNode> nodes = new ArrayList<>();
257         nodes.add(NodeStructureHelper.createFieldNode("birthDate"));
258         nodes.add(NodeStructureHelper.createFieldNode("lastLogin"));
259         nodes.add(NodeStructureHelper.createFieldNode("language"));
260         config.addNodes("tables.table(0).fields", nodes);
261         assertEquals(7, config.getMaxIndex("tables.table(0).fields.field"));
262         assertEquals("birthDate", config.getString("tables.table(0).fields.field(5).name"));
263         assertEquals("lastLogin", config.getString("tables.table(0).fields.field(6).name"));
264         assertEquals("language", config.getString("tables.table(0).fields.field(7).name"));
265     }
266 
267     /**
268      * Tests copying nodes from one configuration to another one.
269      */
270     @Test
271     void testAddNodesCopy() {
272         final AbstractHierarchicalConfigurationTestImpl configDest = new AbstractHierarchicalConfigurationTestImpl(new InMemoryNodeModel());
273         configDest.addProperty("test", "TEST");
274         final Collection<ImmutableNode> nodes = getRootNode().getChildren();
275         assertEquals(1, nodes.size());
276         configDest.addNodes("newNodes", nodes);
277         for (int i = 0; i < NodeStructureHelper.tablesLength(); i++) {
278             final String keyTab = "newNodes.tables.table(" + i + ").";
279             assertEquals(NodeStructureHelper.table(i), configDest.getString(keyTab + "name"), "Table " + i + " not found");
280             for (int j = 0; j < NodeStructureHelper.fieldsLength(i); j++) {
281                 assertEquals(NodeStructureHelper.field(i, j), configDest.getString(keyTab + "fields.field(" + j + ").name"),
282                         "Invalid field " + j + " in table " + i);
283             }
284         }
285     }
286 
287     /**
288      * Tests the addNodes() method if the provided key does not exist. In this case, a new node (or even a completely new
289      * branch) is created.
290      */
291     @Test
292     void testAddNodesForNonExistingKey() {
293         final Collection<ImmutableNode> nodes = new ArrayList<>();
294         final ImmutableNode newNode = new ImmutableNode.Builder().name("usr").value("scott").addAttribute("pwd", "tiger").create();
295         nodes.add(newNode);
296         config.addNodes("database.connection.settings", nodes);
297 
298         assertEquals("scott", config.getString("database.connection.settings.usr"));
299         assertEquals("tiger", config.getString("database.connection.settings.usr[@pwd]"));
300     }
301 
302     /**
303      * Tests the addNodes() method when the new nodes should be added to an attribute node. This is not allowed.
304      */
305     @Test
306     void testAddNodesWithAttributeKey() {
307         final Collection<ImmutableNode> nodes = new ArrayList<>();
308         nodes.add(NodeStructureHelper.createNode("testNode", "yes"));
309         assertThrows(IllegalArgumentException.class, () -> config.addNodes("database.connection[@settings]", nodes));
310     }
311 
312     @Test
313     void testAddProperty() {
314         config.addProperty("tables.table(0).fields.field(-1).name", "phone");
315         Object prop = config.getProperty("tables.table(0).fields.field.name");
316         Collection<?> collection = assertInstanceOf(Collection.class, prop);
317         assertEquals(6, collection.size());
318 
319         config.addProperty("tables.table(0).fields.field.name", "fax");
320         prop = config.getProperty("tables.table.fields.field(5).name");
321         final List<?> list = assertInstanceOf(List.class, prop);
322         assertEquals("phone", list.get(0));
323         assertEquals("fax", list.get(1));
324 
325         config.addProperty("tables.table(-1).name", "config");
326         prop = config.getProperty("tables.table.name");
327         collection = assertInstanceOf(Collection.class, prop);
328         assertEquals(3, collection.size());
329         config.addProperty("tables.table(2).fields.field(0).name", "cid");
330         config.addProperty("tables.table(2).fields.field(-1).name", "confName");
331         prop = config.getProperty("tables.table(2).fields.field.name");
332         collection = assertInstanceOf(Collection.class, prop);
333         assertEquals(2, collection.size());
334         assertEquals("confName", config.getProperty("tables.table(2).fields.field(1).name"));
335 
336         config.addProperty("connection.user", "scott");
337         config.addProperty("connection.passwd", "tiger");
338         assertEquals("tiger", config.getProperty("connection.passwd"));
339 
340         final DefaultConfigurationKey key = createConfigurationKey();
341         key.append("tables").append("table").appendIndex(0);
342         key.appendAttribute("tableType");
343         config.addProperty(key.toString(), "system");
344         assertEquals("system", config.getProperty(key.toString()));
345     }
346 
347     @Test
348     void testAddPropertyInvalidKey() {
349         assertThrows(IllegalArgumentException.class, () -> config.addProperty(".", "InvalidKey"));
350     }
351 
352     /**
353      * Tests whether list handling works correctly when adding properties.
354      */
355     @Test
356     void testAddPropertyWithListHandling() {
357         config.setListDelimiterHandler(new DefaultListDelimiterHandler(','));
358         final String key = "list.delimiter.value";
359         config.addProperty(key + ".escaped", "3\\,1415");
360         config.addProperty(key + ".elements", "3,1415");
361         assertEquals("3,1415", config.getString(key + ".escaped"));
362         assertEquals("3", config.getString(key + ".elements"));
363     }
364 
365     @Test
366     void testClear() {
367         config.setProperty(null, "value");
368         config.addProperty("[@attr]", "defined");
369         config.clear();
370         assertTrue(config.isEmpty());
371     }
372 
373     @Test
374     void testClearProperty() {
375         config.clearProperty("tables.table(0).fields.field(0).name");
376         assertEquals("uname", config.getProperty("tables.table(0).fields.field(0).name"));
377         config.clearProperty("tables.table(0).name");
378         assertFalse(config.containsKey("tables.table(0).name"));
379         assertEquals("firstName", config.getProperty("tables.table(0).fields.field(1).name"));
380         assertEquals("documents", config.getProperty("tables.table.name"));
381         config.clearProperty("tables.table");
382         assertEquals("documents", config.getProperty("tables.table.name"));
383 
384         config.addProperty("test", "first");
385         config.addProperty("test.level", "second");
386         config.clearProperty("test");
387         assertEquals("second", config.getString("test.level"));
388         assertFalse(config.containsKey("test"));
389     }
390 
391     @Test
392     void testClearTree() {
393         Object prop = config.getProperty("tables.table(0).fields.field.name");
394         assertNotNull(prop);
395         config.clearTree("tables.table(0).fields.field(3)");
396         prop = config.getProperty("tables.table(0).fields.field.name");
397         Collection<?> collection = assertInstanceOf(Collection.class, prop);
398         assertEquals(4, collection.size());
399 
400         config.clearTree("tables.table(0).fields");
401         assertNull(config.getProperty("tables.table(0).fields.field.name"));
402         prop = config.getProperty("tables.table.fields.field.name");
403         collection = assertInstanceOf(Collection.class, prop);
404         assertEquals(NodeStructureHelper.fieldsLength(1), collection.size());
405 
406         config.clearTree("tables.table(1)");
407         assertNull(config.getProperty("tables.table.fields.field.name"));
408     }
409 
410     /**
411      * Tests removing more complex node structures.
412      */
413     @Test
414     void testClearTreeComplex() {
415         final int count = 5;
416         // create the structure
417         for (int idx = 0; idx < count; idx++) {
418             config.addProperty("indexList.index(-1)[@default]", Boolean.FALSE);
419             config.addProperty("indexList.index[@name]", "test" + idx);
420             config.addProperty("indexList.index.dir", "testDir" + idx);
421         }
422         assertEquals(count - 1, config.getMaxIndex("indexList.index[@name]"));
423 
424         // Remove a sub tree
425         boolean found = false;
426         for (int idx = 0; true; idx++) {
427             final String name = config.getString("indexList.index(" + idx + ")[@name]");
428             if (name == null) {
429                 break;
430             }
431             if ("test3".equals(name)) {
432                 assertEquals("testDir3", config.getString("indexList.index(" + idx + ").dir"));
433                 config.clearTree("indexList.index(" + idx + ")");
434                 found = true;
435             }
436         }
437         assertTrue(found);
438         assertEquals(count - 2, config.getMaxIndex("indexList.index[@name]"));
439         assertEquals(count - 2, config.getMaxIndex("indexList.index.dir"));
440 
441         // Verify
442         for (int idx = 0; true; idx++) {
443             final String name = config.getString("indexList.index(" + idx + ")[@name]");
444             if (name == null) {
445                 break;
446             }
447             assertNotEquals("test3", name);
448         }
449     }
450 
451     /**
452      * Tests the clearTree() method on a hierarchical structure of nodes. This is a test case for CONFIGURATION-293.
453      */
454     @Test
455     void testClearTreeHierarchy() {
456         config.addProperty("a.b.c", "c");
457         config.addProperty("a.b.c.d", "d");
458         config.addProperty("a.b.c.d.e", "e");
459         config.clearTree("a.b.c");
460         assertFalse(config.containsKey("a.b.c"));
461         assertFalse(config.containsKey("a.b.c.d"));
462     }
463 
464     @Test
465     void testClone() {
466         final Configuration copy = (Configuration) config.clone();
467         assertInstanceOf(AbstractHierarchicalConfiguration.class, copy);
468         checkContent(copy);
469     }
470 
471     /**
472      * Tests whether interpolation works as expected after cloning.
473      */
474     @Test
475     void testCloneInterpolation() {
476         final String keyAnswer = "answer";
477         final String keyValue = "value";
478         config.addProperty(keyAnswer, "The answer is ${" + keyValue + "}.");
479         config.addProperty(keyValue, 42);
480         final Configuration clone = (Configuration) config.clone();
481         clone.setProperty(keyValue, 43);
482         assertEquals("The answer is 42.", config.getString(keyAnswer));
483         assertEquals("The answer is 43.", clone.getString(keyAnswer));
484     }
485 
486     /**
487      * Tests whether registered event handlers are handled correctly when a configuration is cloned. They should not be
488      * registered at the clone.
489      */
490     @Test
491     void testCloneWithEventListeners() {
492         final EventListener<ConfigurationEvent> l = new EventListenerTestImpl(null);
493         config.addEventListener(ConfigurationEvent.ANY, l);
494         final AbstractHierarchicalConfiguration<?> copy = (AbstractHierarchicalConfiguration<?>) config.clone();
495         assertFalse(copy.getEventListeners(ConfigurationEvent.ANY).contains(l));
496     }
497 
498     @Test
499     void testContainsKey() {
500         assertTrue(config.containsKey("tables.table(0).name"));
501         assertTrue(config.containsKey("tables.table(1).name"));
502         assertFalse(config.containsKey("tables.table(2).name"));
503 
504         assertTrue(config.containsKey("tables.table(0).fields.field.name"));
505         assertFalse(config.containsKey("tables.table(0).fields.field"));
506         config.clearTree("tables.table(0).fields");
507         assertFalse(config.containsKey("tables.table(0).fields.field.name"));
508 
509         assertTrue(config.containsKey("tables.table.fields.field.name"));
510     }
511 
512     @Test
513     void testContainsValue() {
514         assertFalse(config.containsValue(null));
515         assertFalse(config.containsValue(""));
516     }
517 
518     @Test
519     void testGetKeys() {
520         final List<String> keys = new ArrayList<>();
521         for (final Iterator<String> it = config.getKeys(); it.hasNext();) {
522             keys.add(it.next());
523         }
524 
525         assertEquals(Arrays.asList("tables.table.name", "tables.table.fields.field.name"), keys);
526     }
527 
528     /**
529      * Tests whether attribute keys are contained in the iteration of keys.
530      */
531     @Test
532     void testGetKeysAttribute() {
533         config.addProperty("tables.table(0)[@type]", "system");
534         final Set<String> keys = new HashSet<>();
535         for (final Iterator<String> it = config.getKeys(); it.hasNext();) {
536             keys.add(it.next());
537         }
538         assertTrue(keys.contains("tables.table[@type]"));
539     }
540 
541     /**
542      * Tests whether a prefix that points to an attribute is correctly handled.
543      */
544     @Test
545     void testGetKeysAttributePrefix() {
546         config.addProperty("tables.table(0)[@type]", "system");
547         final Iterator<String> itKeys = config.getKeys("tables.table[@type]");
548         assertEquals("tables.table[@type]", itKeys.next());
549         assertFalse(itKeys.hasNext());
550     }
551 
552     /**
553      * Tests whether keys are returned in a defined order.
554      */
555     @Test
556     void testGetKeysOrder() {
557         config.addProperty("order.key1", "value1");
558         config.addProperty("order.key2", "value2");
559         config.addProperty("order.key3", "value3");
560 
561         final Iterator<String> it = config.getKeys("order");
562         assertEquals("order.key1", it.next());
563         assertEquals("order.key2", it.next());
564         assertEquals("order.key3", it.next());
565     }
566 
567     @Test
568     void testGetKeysString() {
569         // add some more properties to make it more interesting
570         config.addProperty("tables.table(0).fields.field(1).type", "VARCHAR");
571         config.addProperty("tables.table(0)[@type]", "system");
572         config.addProperty("tables.table(0).size", "42");
573         config.addProperty("tables.table(0).fields.field(0).size", "128");
574         config.addProperty("connections.connection.param.url", "url1");
575         config.addProperty("connections.connection.param.user", "me");
576         config.addProperty("connections.connection.param.pwd", "secret");
577         config.addProperty("connections.connection(-1).param.url", "url2");
578         config.addProperty("connections.connection(1).param.user", "guest");
579 
580         checkKeys("tables.table(1)", new String[] {"name", "fields.field.name"});
581         checkKeys("tables.table(0)", new String[] {"name", "fields.field.name", "tables.table(0)[@type]", "size", "fields.field.type", "fields.field.size"});
582         checkKeys("connections.connection(0).param", new String[] {"url", "user", "pwd"});
583         checkKeys("connections.connection(1).param", new String[] {"url", "user"});
584     }
585 
586     /**
587      * Tests getKeys() with a prefix when the prefix matches exactly a key.
588      */
589     @Test
590     void testGetKeysWithKeyAsPrefix() {
591         config.addProperty("order.key1", "value1");
592         config.addProperty("order.key2", "value2");
593         final Iterator<String> it = config.getKeys("order.key1");
594         assertTrue(it.hasNext());
595         assertEquals("order.key1", it.next());
596         assertFalse(it.hasNext());
597     }
598 
599     /**
600      * Tests getKeys() with a prefix when the prefix matches exactly a key, and there are multiple keys starting with this
601      * prefix.
602      */
603     @Test
604     void testGetKeysWithKeyAsPrefixMultiple() {
605         config.addProperty("order.key1", "value1");
606         config.addProperty("order.key1.test", "value2");
607         config.addProperty("order.key1.test.complex", "value2");
608         final Iterator<String> it = config.getKeys("order.key1");
609         assertEquals("order.key1", it.next());
610         assertEquals("order.key1.test", it.next());
611         assertEquals("order.key1.test.complex", it.next());
612         assertFalse(it.hasNext());
613     }
614 
615     @Test
616     void testGetMaxIndex() {
617         assertEquals(NodeStructureHelper.fieldsLength(0) - 1, config.getMaxIndex("tables.table(0).fields.field"));
618         assertEquals(NodeStructureHelper.fieldsLength(1) - 1, config.getMaxIndex("tables.table(1).fields.field"));
619         assertEquals(1, config.getMaxIndex("tables.table"));
620         assertEquals(1, config.getMaxIndex("tables.table.name"));
621         assertEquals(0, config.getMaxIndex("tables.table(0).name"));
622         assertEquals(0, config.getMaxIndex("tables.table(1).fields.field(1)"));
623         assertEquals(-1, config.getMaxIndex("tables.table(2).fields"));
624 
625         final int maxIdx = config.getMaxIndex("tables.table(0).fields.field.name");
626         for (int i = 0; i <= maxIdx; i++) {
627             final DefaultConfigurationKey key = new DefaultConfigurationKey(DefaultExpressionEngine.INSTANCE, "tables.table(0).fields");
628             key.append("field").appendIndex(i).append("name");
629             assertNotNull(config.getProperty(key.toString()));
630         }
631     }
632 
633     /**
634      * Tests whether the configuration's node model can be correctly accessed.
635      */
636     @Test
637     void testGetNodeModel() {
638         final SynchronizerTestImpl sync = new SynchronizerTestImpl();
639         config.setSynchronizer(sync);
640         final NodeModel<ImmutableNode> model = config.getNodeModel();
641 
642         assertInstanceOf(InMemoryNodeModel.class, model);
643         final ImmutableNode rootNode = model.getNodeHandler().getRootNode();
644         assertEquals(1, rootNode.getChildren().size());
645         assertTrue(rootNode.getChildren().contains(NodeStructureHelper.ROOT_TABLES_TREE));
646         sync.verify(SynchronizerTestImpl.Methods.BEGIN_READ, SynchronizerTestImpl.Methods.END_READ);
647     }
648 
649     @Test
650     void testGetProperty() {
651         checkGetProperty(config);
652     }
653 
654     /**
655      * Tests whether keys that contains brackets can be used.
656      */
657     @Test
658     void testGetPropertyKeyWithBrackets() {
659         final String key = "test.directory.platform(x86)";
660         config.addProperty(key, "C:\\Temp");
661         assertEquals("C:\\Temp", config.getString(key));
662     }
663 
664     /**
665      * Tests the copy constructor when a null reference is passed.
666      */
667     @Test
668     void testInitCopyNull() {
669         final BaseHierarchicalConfiguration copy = new BaseHierarchicalConfiguration((BaseHierarchicalConfiguration) null);
670         assertTrue(copy.isEmpty());
671     }
672 
673     /**
674      * Tests obtaining a configuration with all variables substituted.
675      */
676     @Test
677     void testInterpolatedConfiguration() {
678         config.setListDelimiterHandler(new DefaultListDelimiterHandler(','));
679         final AbstractHierarchicalConfiguration<?> c = (AbstractHierarchicalConfiguration<?>) InterpolationTestHelper.testInterpolatedConfiguration(config);
680 
681         // tests whether the hierarchical structure has been maintained
682         checkGetProperty(c);
683     }
684 
685     /**
686      * Tests interpolation facilities.
687      */
688     @Test
689     void testInterpolation() {
690         config.addProperty("base.dir", "/home/foo");
691         config.addProperty("test.absolute.dir.dir1", "${base.dir}/path1");
692         config.addProperty("test.absolute.dir.dir2", "${base.dir}/path2");
693         config.addProperty("test.absolute.dir.dir3", "${base.dir}/path3");
694         final Configuration sub = config.subset("test.absolute.dir");
695         for (int i = 1; i < 4; i++) {
696             assertEquals("/home/foo/path" + i, config.getString("test.absolute.dir.dir" + i));
697             assertEquals("/home/foo/path" + i, sub.getString("dir" + i));
698         }
699     }
700 
701     /**
702      * Basic interpolation tests.
703      */
704     @Test
705     void testInterpolationBasic() {
706         InterpolationTestHelper.testInterpolation(config);
707     }
708 
709     /**
710      * Tests interpolation with constant values.
711      */
712     @Test
713     void testInterpolationConstants() {
714         InterpolationTestHelper.testInterpolationConstants(config);
715     }
716 
717     /**
718      * Tests escaping variables.
719      */
720     @Test
721     void testInterpolationEscaped() {
722         InterpolationTestHelper.testInterpolationEscaped(config);
723     }
724 
725     /**
726      * Tests interpolation with localhost values.
727      */
728     @Test
729     void testInterpolationLocalhost() {
730         InterpolationTestHelper.testInterpolationLocalhost(config);
731     }
732 
733     /**
734      * Tests an invalid interpolation that causes an endless loop.
735      */
736     @Test
737     void testInterpolationLoop() {
738         InterpolationTestHelper.testInterpolationLoop(config);
739     }
740 
741     /**
742      * Tests multiple levels of interpolation.
743      */
744     @Test
745     void testInterpolationMultipleLevels() {
746         InterpolationTestHelper.testMultipleInterpolation(config);
747     }
748 
749     /**
750      * Tests interpolation with a subset.
751      */
752     @Test
753     void testInterpolationSubset() {
754         InterpolationTestHelper.testInterpolationSubset(config);
755     }
756 
757     /**
758      * Tests whether interpolation with a subset configuration works over multiple layers.
759      */
760     @Test
761     void testInterpolationSubsetMultipleLayers() {
762         config.clear();
763         config.addProperty("var", "value");
764         config.addProperty("prop2.prop[@attr]", "${var}");
765         final Configuration sub1 = config.subset("prop2");
766         final Configuration sub2 = sub1.subset("prop");
767         assertEquals("value", sub2.getString("[@attr]"));
768     }
769 
770     /**
771      * Tests interpolation with system properties.
772      */
773     @Test
774     void testInterpolationSystemProperties() {
775         InterpolationTestHelper.testInterpolationSystemProperties(config);
776     }
777 
778     /**
779      * Tests interpolation of a variable, which cannot be resolved.
780      */
781     @Test
782     void testInterpolationUnknownProperty() {
783         InterpolationTestHelper.testInterpolationUnknownProperty(config);
784     }
785 
786     /**
787      * Tests manipulating the interpolator.
788      */
789     @Test
790     void testInterpolator() {
791         InterpolationTestHelper.testGetInterpolator(config);
792     }
793 
794     @Test
795     void testIsEmptyFalse() {
796         assertFalse(config.isEmpty());
797     }
798 
799     /**
800      * Tests isEmpty() if the structure contains some nodes without values.
801      */
802     @Test
803     void testIsEmptyNodesWithNoValues() {
804         final ImmutableNode.Builder rootBuilder = new ImmutableNode.Builder(1);
805         final ImmutableNode.Builder nodeBuilder = new ImmutableNode.Builder(1);
806         nodeBuilder.addChild(NodeStructureHelper.createNode("child", null));
807         rootBuilder.addChild(nodeBuilder.create());
808         config = new AbstractHierarchicalConfigurationTestImpl(new InMemoryNodeModel(rootBuilder.create()));
809         assertTrue(config.isEmpty());
810     }
811 
812     /**
813      * Tests isEmpty() if only the root node exists.
814      */
815     @Test
816     void testIsEmptyRootOnly() {
817         config = new AbstractHierarchicalConfigurationTestImpl(new InMemoryNodeModel());
818         assertTrue(config.isEmpty());
819     }
820 
821     /**
822      * Tests nodeKey() if the key is directly found in the cache.
823      */
824     @Test
825     void testNodeKeyCacheHit() {
826         final Map<ImmutableNode, String> cache = new HashMap<>();
827         final String key = "someResultKey";
828         cache.put(getRootNode(), key);
829         assertEquals(key, config.nodeKey(getRootNode(), cache, config.getModel().getNodeHandler()));
830     }
831 
832     /**
833      * Tests whether the cache map is filled while generating node keys.
834      */
835     @Test
836     void testNodeKeyCachePopulated() {
837         final Map<ImmutableNode, String> cache = new HashMap<>();
838         final ImmutableNode nodeTabName = NodeStructureHelper.nodeForKey(getRootNode(), "tables/table(0)/name");
839         final NodeHandler<ImmutableNode> handler = config.getModel().getNodeHandler();
840         config.nodeKey(nodeTabName, cache, handler);
841         assertEquals(4, cache.size());
842         assertEquals("tables(0).table(0).name(0)", cache.get(nodeTabName));
843         assertEquals("tables(0).table(0)", cache.get(handler.getParent(nodeTabName)));
844         assertEquals("tables(0)", cache.get(handler.getParent(handler.getParent(nodeTabName))));
845         assertEquals("", cache.get(getRootNode()));
846     }
847 
848     /**
849      * Tests whether the cache is used by nodeKey().
850      */
851     @Test
852     void testNodeKeyCacheUsage() {
853         final Map<ImmutableNode, String> cache = new HashMap<>();
854         final ImmutableNode nodeTabName = NodeStructureHelper.nodeForKey(getRootNode(), "tables/table(0)/name");
855         final NodeHandler<ImmutableNode> handler = config.getModel().getNodeHandler();
856         cache.put(handler.getParent(nodeTabName), "somePrefix");
857         assertEquals("somePrefix.name(0)", config.nodeKey(nodeTabName, cache, handler));
858     }
859 
860     /**
861      * Tests whether a correct node key is generated if no data is contained in the cache.
862      */
863     @Test
864     void testNodeKeyEmptyCache() {
865         final Map<ImmutableNode, String> cache = new HashMap<>();
866         final ImmutableNode nodeTabName = NodeStructureHelper.nodeForKey(getRootNode(), "tables/table(0)/name");
867         final ImmutableNode nodeFldName = NodeStructureHelper.nodeForKey(getRootNode(), "tables/table(0)/fields/field(1)/name");
868         assertEquals("tables(0).table(0).name(0)", config.nodeKey(nodeTabName, cache, config.getModel().getNodeHandler()));
869         assertEquals("tables(0).table(0).fields(0).field(1).name(0)", config.nodeKey(nodeFldName, cache, config.getModel().getNodeHandler()));
870     }
871 
872     /**
873      * Tests whether a node key for the root node can be generated.
874      */
875     @Test
876     void testNodeKeyRootNode() {
877         final Map<ImmutableNode, String> cache = new HashMap<>();
878         assertEquals("", config.nodeKey(getRootNode(), cache, config.getModel().getNodeHandler()));
879     }
880 
881     /**
882      * Tests whether node keys can be resolved.
883      */
884     @Test
885     void testResolveNodeKey() {
886         final List<ImmutableNode> nodes = config.resolveNodeKey(getRootNode(), "tables.table.name", config.getModel().getNodeHandler());
887         assertEquals(NodeStructureHelper.tablesLength(), nodes.size());
888         for (int i = 0; i < NodeStructureHelper.tablesLength(); i++) {
889             assertEquals(NodeStructureHelper.table(i), nodes.get(i).getValue(), "Wrong node value at " + i);
890         }
891     }
892 
893     /**
894      * Tests whether attribute keys are filtered out when resolving node keys.
895      */
896     @Test
897     void testResolveNodeKeyAttribute() {
898         final String attrKey = "tables.table(0)[@type]";
899         config.addProperty(attrKey, "system");
900         assertTrue(config.resolveNodeKey(getRootNode(), attrKey, config.getModel().getNodeHandler()).isEmpty());
901     }
902 
903     /**
904      * Tests setting a custom expression engine, which uses a slightly different syntax.
905      */
906     @Test
907     void testSetExpressionEngine() {
908         config.setExpressionEngine(null);
909         assertNotNull(config.getExpressionEngine());
910         assertSame(DefaultExpressionEngine.INSTANCE, config.getExpressionEngine());
911 
912         config.setExpressionEngine(createAlternativeExpressionEngine());
913         checkAlternativeSyntax();
914     }
915 
916     @Test
917     void testSetProperty() {
918         config.setListDelimiterHandler(new DefaultListDelimiterHandler(','));
919         config.setProperty("tables.table(0).name", "resources");
920         assertEquals("resources", config.getString("tables.table(0).name"));
921         config.setProperty("tables.table.name", "tab1,tab2");
922         assertEquals("tab1", config.getString("tables.table(0).name"));
923         assertEquals("tab2", config.getString("tables.table(1).name"));
924 
925         config.setProperty("test.items.item", new int[] {2, 4, 8, 16});
926         assertEquals(3, config.getMaxIndex("test.items.item"));
927         assertEquals(8, config.getInt("test.items.item(2)"));
928         config.setProperty("test.items.item(2)", Integer.valueOf(6));
929         assertEquals(6, config.getInt("test.items.item(2)"));
930         config.setProperty("test.items.item(2)", new int[] {7, 9, 11});
931         assertEquals(5, config.getMaxIndex("test.items.item"));
932 
933         config.setProperty("test", Boolean.TRUE);
934         config.setProperty("test.items", "01/01/05");
935         assertEquals(5, config.getMaxIndex("test.items.item"));
936         assertTrue(config.getBoolean("test"));
937         assertEquals("01/01/05", config.getProperty("test.items"));
938 
939         config.setProperty("test.items.item", Integer.valueOf(42));
940         assertEquals(0, config.getMaxIndex("test.items.item"));
941         assertEquals(42, config.getInt("test.items.item"));
942     }
943 
944     /**
945      * Tests whether the correct size is calculated.
946      */
947     @Test
948     void testSize() {
949         assertEquals(2, config.size());
950     }
951 }