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 testGetKeys() {
513         final List<String> keys = new ArrayList<>();
514         for (final Iterator<String> it = config.getKeys(); it.hasNext();) {
515             keys.add(it.next());
516         }
517 
518         assertEquals(Arrays.asList("tables.table.name", "tables.table.fields.field.name"), keys);
519     }
520 
521     /**
522      * Tests whether attribute keys are contained in the iteration of keys.
523      */
524     @Test
525     public void testGetKeysAttribute() {
526         config.addProperty("tables.table(0)[@type]", "system");
527         final Set<String> keys = new HashSet<>();
528         for (final Iterator<String> it = config.getKeys(); it.hasNext();) {
529             keys.add(it.next());
530         }
531         assertTrue(keys.contains("tables.table[@type]"));
532     }
533 
534     /**
535      * Tests whether a prefix that points to an attribute is correctly handled.
536      */
537     @Test
538     public void testGetKeysAttributePrefix() {
539         config.addProperty("tables.table(0)[@type]", "system");
540         final Iterator<String> itKeys = config.getKeys("tables.table[@type]");
541         assertEquals("tables.table[@type]", itKeys.next());
542         assertFalse(itKeys.hasNext());
543     }
544 
545     /**
546      * Tests whether keys are returned in a defined order.
547      */
548     @Test
549     public void testGetKeysOrder() {
550         config.addProperty("order.key1", "value1");
551         config.addProperty("order.key2", "value2");
552         config.addProperty("order.key3", "value3");
553 
554         final Iterator<String> it = config.getKeys("order");
555         assertEquals("order.key1", it.next());
556         assertEquals("order.key2", it.next());
557         assertEquals("order.key3", it.next());
558     }
559 
560     @Test
561     public void testGetKeysString() {
562         // add some more properties to make it more interesting
563         config.addProperty("tables.table(0).fields.field(1).type", "VARCHAR");
564         config.addProperty("tables.table(0)[@type]", "system");
565         config.addProperty("tables.table(0).size", "42");
566         config.addProperty("tables.table(0).fields.field(0).size", "128");
567         config.addProperty("connections.connection.param.url", "url1");
568         config.addProperty("connections.connection.param.user", "me");
569         config.addProperty("connections.connection.param.pwd", "secret");
570         config.addProperty("connections.connection(-1).param.url", "url2");
571         config.addProperty("connections.connection(1).param.user", "guest");
572 
573         checkKeys("tables.table(1)", new String[] {"name", "fields.field.name"});
574         checkKeys("tables.table(0)", new String[] {"name", "fields.field.name", "tables.table(0)[@type]", "size", "fields.field.type", "fields.field.size"});
575         checkKeys("connections.connection(0).param", new String[] {"url", "user", "pwd"});
576         checkKeys("connections.connection(1).param", new String[] {"url", "user"});
577     }
578 
579     /**
580      * Tests getKeys() with a prefix when the prefix matches exactly a key.
581      */
582     @Test
583     public void testGetKeysWithKeyAsPrefix() {
584         config.addProperty("order.key1", "value1");
585         config.addProperty("order.key2", "value2");
586         final Iterator<String> it = config.getKeys("order.key1");
587         assertTrue(it.hasNext());
588         assertEquals("order.key1", it.next());
589         assertFalse(it.hasNext());
590     }
591 
592     /**
593      * Tests getKeys() with a prefix when the prefix matches exactly a key, and there are multiple keys starting with this
594      * prefix.
595      */
596     @Test
597     public void testGetKeysWithKeyAsPrefixMultiple() {
598         config.addProperty("order.key1", "value1");
599         config.addProperty("order.key1.test", "value2");
600         config.addProperty("order.key1.test.complex", "value2");
601         final Iterator<String> it = config.getKeys("order.key1");
602         assertEquals("order.key1", it.next());
603         assertEquals("order.key1.test", it.next());
604         assertEquals("order.key1.test.complex", it.next());
605         assertFalse(it.hasNext());
606     }
607 
608     @Test
609     public void testGetMaxIndex() {
610         assertEquals(NodeStructureHelper.fieldsLength(0) - 1, config.getMaxIndex("tables.table(0).fields.field"));
611         assertEquals(NodeStructureHelper.fieldsLength(1) - 1, config.getMaxIndex("tables.table(1).fields.field"));
612         assertEquals(1, config.getMaxIndex("tables.table"));
613         assertEquals(1, config.getMaxIndex("tables.table.name"));
614         assertEquals(0, config.getMaxIndex("tables.table(0).name"));
615         assertEquals(0, config.getMaxIndex("tables.table(1).fields.field(1)"));
616         assertEquals(-1, config.getMaxIndex("tables.table(2).fields"));
617 
618         final int maxIdx = config.getMaxIndex("tables.table(0).fields.field.name");
619         for (int i = 0; i <= maxIdx; i++) {
620             final DefaultConfigurationKey key = new DefaultConfigurationKey(DefaultExpressionEngine.INSTANCE, "tables.table(0).fields");
621             key.append("field").appendIndex(i).append("name");
622             assertNotNull(config.getProperty(key.toString()));
623         }
624     }
625 
626     /**
627      * Tests whether the configuration's node model can be correctly accessed.
628      */
629     @Test
630     public void testGetNodeModel() {
631         final SynchronizerTestImpl sync = new SynchronizerTestImpl();
632         config.setSynchronizer(sync);
633         final NodeModel<ImmutableNode> model = config.getNodeModel();
634 
635         assertInstanceOf(InMemoryNodeModel.class, model);
636         final ImmutableNode rootNode = model.getNodeHandler().getRootNode();
637         assertEquals(1, rootNode.getChildren().size());
638         assertTrue(rootNode.getChildren().contains(NodeStructureHelper.ROOT_TABLES_TREE));
639         sync.verify(SynchronizerTestImpl.Methods.BEGIN_READ, SynchronizerTestImpl.Methods.END_READ);
640     }
641 
642     @Test
643     public void testGetProperty() {
644         checkGetProperty(config);
645     }
646 
647     /**
648      * Tests whether keys that contains brackets can be used.
649      */
650     @Test
651     public void testGetPropertyKeyWithBrackets() {
652         final String key = "test.directory.platform(x86)";
653         config.addProperty(key, "C:\\Temp");
654         assertEquals("C:\\Temp", config.getString(key));
655     }
656 
657     /**
658      * Tests the copy constructor when a null reference is passed.
659      */
660     @Test
661     public void testInitCopyNull() {
662         final BaseHierarchicalConfiguration copy = new BaseHierarchicalConfiguration((BaseHierarchicalConfiguration) null);
663         assertTrue(copy.isEmpty());
664     }
665 
666     /**
667      * Tests obtaining a configuration with all variables substituted.
668      */
669     @Test
670     public void testInterpolatedConfiguration() {
671         config.setListDelimiterHandler(new DefaultListDelimiterHandler(','));
672         final AbstractHierarchicalConfiguration<?> c = (AbstractHierarchicalConfiguration<?>) InterpolationTestHelper.testInterpolatedConfiguration(config);
673 
674         // tests whether the hierarchical structure has been maintained
675         checkGetProperty(c);
676     }
677 
678     /**
679      * Tests interpolation facilities.
680      */
681     @Test
682     public void testInterpolation() {
683         config.addProperty("base.dir", "/home/foo");
684         config.addProperty("test.absolute.dir.dir1", "${base.dir}/path1");
685         config.addProperty("test.absolute.dir.dir2", "${base.dir}/path2");
686         config.addProperty("test.absolute.dir.dir3", "${base.dir}/path3");
687         final Configuration sub = config.subset("test.absolute.dir");
688         for (int i = 1; i < 4; i++) {
689             assertEquals("/home/foo/path" + i, config.getString("test.absolute.dir.dir" + i));
690             assertEquals("/home/foo/path" + i, sub.getString("dir" + i));
691         }
692     }
693 
694     /**
695      * Basic interpolation tests.
696      */
697     @Test
698     public void testInterpolationBasic() {
699         InterpolationTestHelper.testInterpolation(config);
700     }
701 
702     /**
703      * Tests interpolation with constant values.
704      */
705     @Test
706     public void testInterpolationConstants() {
707         InterpolationTestHelper.testInterpolationConstants(config);
708     }
709 
710     /**
711      * Tests escaping variables.
712      */
713     @Test
714     public void testInterpolationEscaped() {
715         InterpolationTestHelper.testInterpolationEscaped(config);
716     }
717 
718     /**
719      * Tests interpolation with localhost values.
720      */
721     @Test
722     public void testInterpolationLocalhost() {
723         InterpolationTestHelper.testInterpolationLocalhost(config);
724     }
725 
726     /**
727      * Tests an invalid interpolation that causes an endless loop.
728      */
729     @Test
730     public void testInterpolationLoop() {
731         InterpolationTestHelper.testInterpolationLoop(config);
732     }
733 
734     /**
735      * Tests multiple levels of interpolation.
736      */
737     @Test
738     public void testInterpolationMultipleLevels() {
739         InterpolationTestHelper.testMultipleInterpolation(config);
740     }
741 
742     /**
743      * Tests interpolation with a subset.
744      */
745     @Test
746     public void testInterpolationSubset() {
747         InterpolationTestHelper.testInterpolationSubset(config);
748     }
749 
750     /**
751      * Tests whether interpolation with a subset configuration works over multiple layers.
752      */
753     @Test
754     public void testInterpolationSubsetMultipleLayers() {
755         config.clear();
756         config.addProperty("var", "value");
757         config.addProperty("prop2.prop[@attr]", "${var}");
758         final Configuration sub1 = config.subset("prop2");
759         final Configuration sub2 = sub1.subset("prop");
760         assertEquals("value", sub2.getString("[@attr]"));
761     }
762 
763     /**
764      * Tests interpolation with system properties.
765      */
766     @Test
767     public void testInterpolationSystemProperties() {
768         InterpolationTestHelper.testInterpolationSystemProperties(config);
769     }
770 
771     /**
772      * Tests interpolation of a variable, which cannot be resolved.
773      */
774     @Test
775     public void testInterpolationUnknownProperty() {
776         InterpolationTestHelper.testInterpolationUnknownProperty(config);
777     }
778 
779     /**
780      * Tests manipulating the interpolator.
781      */
782     @Test
783     public void testInterpolator() {
784         InterpolationTestHelper.testGetInterpolator(config);
785     }
786 
787     @Test
788     public void testIsEmptyFalse() {
789         assertFalse(config.isEmpty());
790     }
791 
792     /**
793      * Tests isEmpty() if the structure contains some nodes without values.
794      */
795     @Test
796     public void testIsEmptyNodesWithNoValues() {
797         final ImmutableNode.Builder rootBuilder = new ImmutableNode.Builder(1);
798         final ImmutableNode.Builder nodeBuilder = new ImmutableNode.Builder(1);
799         nodeBuilder.addChild(NodeStructureHelper.createNode("child", null));
800         rootBuilder.addChild(nodeBuilder.create());
801         config = new AbstractHierarchicalConfigurationTestImpl(new InMemoryNodeModel(rootBuilder.create()));
802         assertTrue(config.isEmpty());
803     }
804 
805     /**
806      * Tests isEmpty() if only the root node exists.
807      */
808     @Test
809     public void testIsEmptyRootOnly() {
810         config = new AbstractHierarchicalConfigurationTestImpl(new InMemoryNodeModel());
811         assertTrue(config.isEmpty());
812     }
813 
814     /**
815      * Tests nodeKey() if the key is directly found in the cache.
816      */
817     @Test
818     public void testNodeKeyCacheHit() {
819         final Map<ImmutableNode, String> cache = new HashMap<>();
820         final String key = "someResultKey";
821         cache.put(getRootNode(), key);
822         assertEquals(key, config.nodeKey(getRootNode(), cache, config.getModel().getNodeHandler()));
823     }
824 
825     /**
826      * Tests whether the cache map is filled while generating node keys.
827      */
828     @Test
829     public void testNodeKeyCachePopulated() {
830         final Map<ImmutableNode, String> cache = new HashMap<>();
831         final ImmutableNode nodeTabName = NodeStructureHelper.nodeForKey(getRootNode(), "tables/table(0)/name");
832         final NodeHandler<ImmutableNode> handler = config.getModel().getNodeHandler();
833         config.nodeKey(nodeTabName, cache, handler);
834         assertEquals(4, cache.size());
835         assertEquals("tables(0).table(0).name(0)", cache.get(nodeTabName));
836         assertEquals("tables(0).table(0)", cache.get(handler.getParent(nodeTabName)));
837         assertEquals("tables(0)", cache.get(handler.getParent(handler.getParent(nodeTabName))));
838         assertEquals("", cache.get(getRootNode()));
839     }
840 
841     /**
842      * Tests whether the cache is used by nodeKey().
843      */
844     @Test
845     public void testNodeKeyCacheUsage() {
846         final Map<ImmutableNode, String> cache = new HashMap<>();
847         final ImmutableNode nodeTabName = NodeStructureHelper.nodeForKey(getRootNode(), "tables/table(0)/name");
848         final NodeHandler<ImmutableNode> handler = config.getModel().getNodeHandler();
849         cache.put(handler.getParent(nodeTabName), "somePrefix");
850         assertEquals("somePrefix.name(0)", config.nodeKey(nodeTabName, cache, handler));
851     }
852 
853     /**
854      * Tests whether a correct node key is generated if no data is contained in the cache.
855      */
856     @Test
857     public void testNodeKeyEmptyCache() {
858         final Map<ImmutableNode, String> cache = new HashMap<>();
859         final ImmutableNode nodeTabName = NodeStructureHelper.nodeForKey(getRootNode(), "tables/table(0)/name");
860         final ImmutableNode nodeFldName = NodeStructureHelper.nodeForKey(getRootNode(), "tables/table(0)/fields/field(1)/name");
861         assertEquals("tables(0).table(0).name(0)", config.nodeKey(nodeTabName, cache, config.getModel().getNodeHandler()));
862         assertEquals("tables(0).table(0).fields(0).field(1).name(0)", config.nodeKey(nodeFldName, cache, config.getModel().getNodeHandler()));
863     }
864 
865     /**
866      * Tests whether a node key for the root node can be generated.
867      */
868     @Test
869     public void testNodeKeyRootNode() {
870         final Map<ImmutableNode, String> cache = new HashMap<>();
871         assertEquals("", config.nodeKey(getRootNode(), cache, config.getModel().getNodeHandler()));
872     }
873 
874     /**
875      * Tests whether node keys can be resolved.
876      */
877     @Test
878     public void testResolveNodeKey() {
879         final List<ImmutableNode> nodes = config.resolveNodeKey(getRootNode(), "tables.table.name", config.getModel().getNodeHandler());
880         assertEquals(NodeStructureHelper.tablesLength(), nodes.size());
881         for (int i = 0; i < NodeStructureHelper.tablesLength(); i++) {
882             assertEquals(NodeStructureHelper.table(i), nodes.get(i).getValue(), "Wrong node value at " + i);
883         }
884     }
885 
886     /**
887      * Tests whether attribute keys are filtered out when resolving node keys.
888      */
889     @Test
890     public void testResolveNodeKeyAttribute() {
891         final String attrKey = "tables.table(0)[@type]";
892         config.addProperty(attrKey, "system");
893         assertTrue(config.resolveNodeKey(getRootNode(), attrKey, config.getModel().getNodeHandler()).isEmpty());
894     }
895 
896     /**
897      * Tests setting a custom expression engine, which uses a slightly different syntax.
898      */
899     @Test
900     public void testSetExpressionEngine() {
901         config.setExpressionEngine(null);
902         assertNotNull(config.getExpressionEngine());
903         assertSame(DefaultExpressionEngine.INSTANCE, config.getExpressionEngine());
904 
905         config.setExpressionEngine(createAlternativeExpressionEngine());
906         checkAlternativeSyntax();
907     }
908 
909     @Test
910     public void testSetProperty() {
911         config.setListDelimiterHandler(new DefaultListDelimiterHandler(','));
912         config.setProperty("tables.table(0).name", "resources");
913         assertEquals("resources", config.getString("tables.table(0).name"));
914         config.setProperty("tables.table.name", "tab1,tab2");
915         assertEquals("tab1", config.getString("tables.table(0).name"));
916         assertEquals("tab2", config.getString("tables.table(1).name"));
917 
918         config.setProperty("test.items.item", new int[] {2, 4, 8, 16});
919         assertEquals(3, config.getMaxIndex("test.items.item"));
920         assertEquals(8, config.getInt("test.items.item(2)"));
921         config.setProperty("test.items.item(2)", Integer.valueOf(6));
922         assertEquals(6, config.getInt("test.items.item(2)"));
923         config.setProperty("test.items.item(2)", new int[] {7, 9, 11});
924         assertEquals(5, config.getMaxIndex("test.items.item"));
925 
926         config.setProperty("test", Boolean.TRUE);
927         config.setProperty("test.items", "01/01/05");
928         assertEquals(5, config.getMaxIndex("test.items.item"));
929         assertTrue(config.getBoolean("test"));
930         assertEquals("01/01/05", config.getProperty("test.items"));
931 
932         config.setProperty("test.items.item", Integer.valueOf(42));
933         assertEquals(0, config.getMaxIndex("test.items.item"));
934         assertEquals(42, config.getInt("test.items.item"));
935     }
936 
937     /**
938      * Tests whether the correct size is calculated.
939      */
940     @Test
941     public void testSize() {
942         assertEquals(2, config.size());
943     }
944 }