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