View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration2;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
21  import static org.junit.jupiter.api.Assertions.assertNull;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  
25  import java.util.ArrayList;
26  import java.util.Collection;
27  import java.util.List;
28  
29  import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
30  import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
31  import org.apache.commons.configuration2.tree.DefaultConfigurationKey;
32  import org.apache.commons.configuration2.tree.DefaultExpressionEngine;
33  import org.apache.commons.configuration2.tree.ImmutableNode;
34  import org.apache.commons.configuration2.tree.NodeStructureHelper;
35  import org.junit.jupiter.api.BeforeEach;
36  import org.junit.jupiter.api.Test;
37  
38  /**
39   * Test class for {@code BaseHierarchicalConfiguration}.
40   */
41  public class TestHierarchicalConfiguration {
42  
43      /** Constant for a changed name. */
44      private static final String NEW_NAME = "alteredName";
45  
46      /**
47       * Creates a {@code DefaultConfigurationKey} object.
48       *
49       * @return the new key object
50       */
51      private static DefaultConfigurationKey createConfigurationKey() {
52          return new DefaultConfigurationKey(DefaultExpressionEngine.INSTANCE);
53      }
54  
55      /** The configuration to be tested. */
56      private BaseHierarchicalConfiguration config;
57  
58      /**
59       * Tests access to sub configurations as children of a defined node.
60       *
61       * @param withUpdates the updates flag
62       * @param expectedName the expected table name when reading a property
63       */
64      private void checkChildConfigurationsAtWithUpdates(final boolean withUpdates, final String expectedName) {
65          final String key = "tables.table(0)";
66          final List<HierarchicalConfiguration<ImmutableNode>> children = withUpdates ? config.childConfigurationsAt(key, true)
67              : config.childConfigurationsAt(key);
68          assertEquals(2, children.size());
69          final HierarchicalConfiguration<ImmutableNode> sub = children.get(0);
70          sub.setProperty(null, NEW_NAME);
71          assertEquals(expectedName, config.getString(key + ".name"));
72      }
73  
74      /**
75       * Checks configurationAt() if the passed in key selects an attribute.
76       *
77       * @param withUpdates the updates flag
78       */
79      private void checkConfigurationAtAttributeNode(final boolean withUpdates) {
80          final String key = "tables.table(0)[@type]";
81          config.addProperty(key, "system");
82          assertThrows(ConfigurationRuntimeException.class, () -> config.configurationAt(key, withUpdates));
83      }
84  
85      /**
86       * Helper method for checking a configurationsAt() method. It is also tested whether the configuration is connected to
87       * its parent.
88       *
89       * @param withUpdates the updates flag
90       * @param expName the expected name in the parent configuration
91       */
92      private void checkConfigurationsAtWithUpdate(final boolean withUpdates, final String expName) {
93          final String key = "tables.table(1).fields.field";
94          final List<HierarchicalConfiguration<ImmutableNode>> lstFlds = withUpdates ? config.configurationsAt(key, true) : config.configurationsAt(key);
95          checkSubConfigurations(lstFlds);
96          lstFlds.get(0).setProperty("name", NEW_NAME);
97          assertEquals(expName, config.getString("tables.table(1).fields.field(0).name"));
98      }
99  
100     /**
101      * Checks the content of the passed in configuration object. Used by some tests that copy a configuration.
102      *
103      * @param c the configuration to check
104      */
105     private void checkContent(final Configuration c) {
106         for (int i = 0; i < NodeStructureHelper.tablesLength(); i++) {
107             assertEquals(NodeStructureHelper.table(i), c.getString("tables.table(" + i + ").name"));
108             for (int j = 0; j < NodeStructureHelper.fieldsLength(i); j++) {
109                 assertEquals(NodeStructureHelper.field(i, j), c.getString("tables.table(" + i + ").fields.field(" + j + ").name"));
110             }
111         }
112     }
113 
114     /**
115      * Helper method for checking a list of sub configurations pointing to the single fields of the table configuration.
116      *
117      * @param lstFlds the list with sub configurations
118      */
119     private void checkSubConfigurations(final List<? extends ImmutableConfiguration> lstFlds) {
120         assertEquals(NodeStructureHelper.fieldsLength(1), lstFlds.size());
121         for (int i = 0; i < NodeStructureHelper.fieldsLength(1); i++) {
122             final ImmutableConfiguration sub = lstFlds.get(i);
123             assertEquals(NodeStructureHelper.field(1, i), sub.getString("name"), "Wrong field at position " + i);
124         }
125     }
126 
127     @BeforeEach
128     public void setUp() throws Exception {
129         final ImmutableNode root = new ImmutableNode.Builder(1).addChild(NodeStructureHelper.ROOT_TABLES_TREE).create();
130         config = new BaseHierarchicalConfiguration();
131         config.getNodeModel().setRootNode(root);
132     }
133 
134     /**
135      * Tests the result of childConfigurationsAt() if the key does not point to an existing node.
136      */
137     @Test
138     void testChildConfigurationsAtNotFound() {
139         assertTrue(config.childConfigurationsAt("not.existing.key").isEmpty());
140     }
141 
142     /**
143      * Tests the result of childConfigurationsAt() if the key selects multiple nodes.
144      */
145     @Test
146     void testChildConfigurationsAtNoUniqueKey() {
147         assertTrue(config.childConfigurationsAt("tables.table").isEmpty());
148     }
149 
150     /**
151      * Tests whether sub configurations for the children of a given node can be queried if no updates are propagated.
152      */
153     @Test
154     void testChildConfigurationsAtNoUpdates() {
155         checkChildConfigurationsAtWithUpdates(false, NodeStructureHelper.table(0));
156     }
157 
158     /**
159      * Tests whether sub configurations for the children of a given node can be queried that support updates.
160      */
161     @Test
162     void testChildConfigurationsAtWithUpdates() {
163         checkChildConfigurationsAtWithUpdates(true, NEW_NAME);
164     }
165 
166     @Test
167     void testClone() {
168         final Configuration copy = (Configuration) config.clone();
169         assertInstanceOf(BaseHierarchicalConfiguration.class, copy);
170         config.setProperty("tables.table(0).name", "changed table name");
171         checkContent(copy);
172     }
173 
174     /**
175      * Tests configurationAt() if the passed in key selects an attribute result.
176      */
177     @Test
178     void testConfigurationAtAttributeNode() {
179         checkConfigurationAtAttributeNode(false);
180     }
181 
182     /**
183      * Tests configurationAt() if the passed in key selects an attribute result and the updates flag is set.
184      */
185     @Test
186     void testConfigurationAtAttributeNodeWithUpdates() {
187         checkConfigurationAtAttributeNode(true);
188     }
189 
190     /**
191      * Tests whether a {@code SubnodeConfiguration} can be cleared and its root node can be removed from its parent
192      * configuration.
193      */
194     @Test
195     void testConfigurationAtClearAndDetach() {
196         config.addProperty("test.sub.test", "success");
197         config.addProperty("test.other", "check");
198         final HierarchicalConfiguration<ImmutableNode> sub = config.configurationAt("test.sub", true);
199         sub.clear();
200         assertTrue(sub.isEmpty());
201         assertNull(config.getString("test.sub.test"));
202         sub.setProperty("test", "failure!");
203         assertNull(config.getString("test.sub.test"));
204     }
205 
206     /**
207      * Tests the configurationAt() method if the passed in key selects multiple nodes. This should cause an exception.
208      */
209     @Test
210     void testConfigurationAtMultipleNodes() {
211         assertThrows(ConfigurationRuntimeException.class, () -> config.configurationAt("tables.table.name"));
212     }
213 
214     /**
215      * Tests configurationAt() if the passed in key selects multiple nodes and the update flag is set.
216      */
217     @Test
218     void testConfigurationAtMultipleNodesWithUpdates() {
219         assertThrows(ConfigurationRuntimeException.class, () -> config.configurationAt("tables.table.name", true));
220     }
221 
222     /**
223      * Tests whether a configuration obtained via configurationAt() contains the expected properties.
224      */
225     @Test
226     void testConfigurationAtReadAccess() {
227         final HierarchicalConfiguration<ImmutableNode> subConfig = config.configurationAt("tables.table(1)");
228         assertEquals(NodeStructureHelper.table(1), subConfig.getString("name"));
229         final List<Object> lstFlds = subConfig.getList("fields.field.name");
230 
231         final List<String> expected = new ArrayList<>();
232         for (int i = 0; i < NodeStructureHelper.fieldsLength(1); i++) {
233             expected.add(NodeStructureHelper.field(1, i));
234         }
235         assertEquals(expected, lstFlds);
236     }
237 
238     /**
239      * Tests the configurationAt() method if the passed in key does not exist.
240      */
241     @Test
242     void testConfigurationAtUnknownSubTree() {
243         assertThrows(ConfigurationRuntimeException.class, () -> config.configurationAt("non.existing.key"));
244     }
245 
246     /**
247      * Tests configurationAt() for a non existing key if the update flag is set.
248      */
249     @Test
250     void testConfigurationAtUnknownSubTreeWithUpdates() {
251         assertThrows(ConfigurationRuntimeException.class, () -> config.configurationAt("non.existing.key", true));
252     }
253 
254     /**
255      * Tests an update operation on a parent configuration if the sub configuration is connected.
256      */
257     @Test
258     void testConfigurationAtUpdateParentConnected() {
259         final HierarchicalConfiguration<ImmutableNode> subConfig = config.configurationAt("tables.table(1)", true);
260         config.setProperty("tables.table(1).fields.field(2).name", "testField");
261         assertEquals("testField", subConfig.getString("fields.field(2).name"));
262     }
263 
264     /**
265      * Tests an update operation on a parent configuration if the sub configuration is independent.
266      */
267     @Test
268     void testConfigurationAtUpdateParentIndependent() {
269         final HierarchicalConfiguration<ImmutableNode> subConfig = config.configurationAt("tables.table(1)");
270         config.setProperty("tables.table(1).fields.field(2).name", "testField");
271         assertEquals(NodeStructureHelper.field(1, 2), subConfig.getString("fields.field(2).name"));
272     }
273 
274     /**
275      * Tests an update operation on a sub configuration which is connected to its parent.
276      */
277     @Test
278     void testConfigurationAtUpdateSubConfigConnected() {
279         final HierarchicalConfiguration<ImmutableNode> subConfig = config.configurationAt("tables.table(1)", true);
280         subConfig.setProperty("name", "testTable");
281         assertEquals("testTable", config.getString("tables.table(1).name"));
282     }
283 
284     /**
285      * Tests an update operation on a sub configuration which is independent on its parent.
286      */
287     @Test
288     void testConfigurationAtUpdateSubConfigIndependent() {
289         final HierarchicalConfiguration<ImmutableNode> subConfig = config.configurationAt("tables.table(1)");
290         subConfig.setProperty("name", "testTable");
291         assertEquals("testTable", subConfig.getString("name"));
292         assertEquals(NodeStructureHelper.table(1), config.getString("tables.table(1).name"));
293     }
294 
295     /**
296      * Tests whether a connected configuration is correctly initialized with properties of its parent.
297      */
298     @Test
299     void testConfigurationAtWithUpdateInitialized() {
300         final String key = "tables.table";
301         config.setListDelimiterHandler(new DefaultListDelimiterHandler(';'));
302         config.setThrowExceptionOnMissing(true);
303         final List<HierarchicalConfiguration<ImmutableNode>> subs = config.configurationsAt(key, true);
304         final BaseHierarchicalConfiguration sub = (BaseHierarchicalConfiguration) subs.get(0);
305         assertEquals(config.getListDelimiterHandler(), sub.getListDelimiterHandler());
306         assertTrue(sub.isThrowExceptionOnMissing());
307     }
308 
309     /**
310      * Tests configurationsAt() if an attribute key is passed in.
311      */
312     @Test
313     void testConfigurationsAtAttributeKey() {
314         final String attrKey = "tables.table(0)[@type]";
315         config.addProperty(attrKey, "user");
316         assertTrue(config.configurationsAt(attrKey).isEmpty());
317     }
318 
319     /**
320      * Tests the configurationsAt() method when the passed in key does not select any sub nodes.
321      */
322     @Test
323     void testConfigurationsAtEmpty() {
324         assertTrue(config.configurationsAt("unknown.key").isEmpty());
325     }
326 
327     /**
328      * Tests the configurationsAt() method if the sub configurations are not connected.
329      */
330     @Test
331     void testConfigurationsAtNoUpdate() {
332         checkConfigurationsAtWithUpdate(false, NodeStructureHelper.field(1, 0));
333     }
334 
335     /**
336      * Tests configurationsAt() if the sub configurations are connected.
337      */
338     @Test
339     void testConfigurationsAtWithUpdates() {
340         checkConfigurationsAtWithUpdate(true, NEW_NAME);
341     }
342 
343     /**
344      * Tests whether immutable configurations for the children of a given node can be queried.
345      */
346     @Test
347     void testImmutableChildConfigurationsAt() {
348         final List<ImmutableHierarchicalConfiguration> children = config.immutableChildConfigurationsAt("tables.table(0)");
349         assertEquals(2, children.size());
350         final ImmutableHierarchicalConfiguration c1 = children.get(0);
351         assertEquals("name", c1.getRootElementName());
352         assertEquals(NodeStructureHelper.table(0), c1.getString(null));
353         final ImmutableHierarchicalConfiguration c2 = children.get(1);
354         assertEquals("fields", c2.getRootElementName());
355         assertEquals(NodeStructureHelper.field(0, 0), c2.getString("field(0).name"));
356     }
357 
358     /**
359      * Tests whether an immutable configuration for a sub tree can be obtained.
360      */
361     @Test
362     void testImmutableConfigurationAt() {
363         final ImmutableHierarchicalConfiguration subConfig = config.immutableConfigurationAt("tables.table(1)");
364         assertEquals(NodeStructureHelper.table(1), subConfig.getString("name"));
365         final List<Object> lstFlds = subConfig.getList("fields.field.name");
366 
367         final List<String> expected = new ArrayList<>();
368         for (int i = 0; i < NodeStructureHelper.fieldsLength(1); i++) {
369             expected.add(NodeStructureHelper.field(1, i));
370         }
371         assertEquals(expected, lstFlds);
372     }
373 
374     /**
375      * Tests whether the support updates flag is taken into account when creating an immutable sub configuration.
376      */
377     @Test
378     void testImmutableConfigurationAtSupportUpdates() {
379         final String newTableName = NodeStructureHelper.table(1) + "_other";
380         final ImmutableHierarchicalConfiguration subConfig = config.immutableConfigurationAt("tables.table(1)", true);
381         config.addProperty("tables.table(-1).name", newTableName);
382         config.clearTree("tables.table(1)");
383         assertEquals(newTableName, subConfig.getString("name"));
384     }
385 
386     /**
387      * Tests whether a list of immutable sub configurations can be queried.
388      */
389     @Test
390     void testImmutableConfigurationsAt() {
391         final List<ImmutableHierarchicalConfiguration> lstFlds = config.immutableConfigurationsAt("tables.table(1).fields.field");
392         checkSubConfigurations(lstFlds);
393     }
394 
395     /**
396      * Tests the copy constructor.
397      */
398     @Test
399     void testInitCopy() {
400         final BaseHierarchicalConfiguration copy = new BaseHierarchicalConfiguration(config);
401         checkContent(copy);
402     }
403 
404     /**
405      * Tests the copy constructor when a null reference is passed.
406      */
407     @Test
408     void testInitCopyNull() {
409         final BaseHierarchicalConfiguration copy = new BaseHierarchicalConfiguration((HierarchicalConfiguration<ImmutableNode>) null);
410         assertTrue(copy.isEmpty());
411     }
412 
413     /**
414      * Tests whether the nodes of a copied configuration are independent from the source configuration.
415      */
416     @Test
417     void testInitCopyUpdate() {
418         final BaseHierarchicalConfiguration copy = new BaseHierarchicalConfiguration(config);
419         config.setProperty("tables.table(0).name", "NewTable");
420         checkContent(copy);
421     }
422 
423     /**
424      * Tests obtaining a configuration with all variables substituted.
425      */
426     @Test
427     void testInterpolatedConfiguration() {
428         config.setListDelimiterHandler(new DefaultListDelimiterHandler(','));
429         final BaseHierarchicalConfiguration c = (BaseHierarchicalConfiguration) InterpolationTestHelper.testInterpolatedConfiguration(config);
430 
431         // tests whether the hierarchical structure has been maintained
432         checkContent(c);
433     }
434 
435     /**
436      * Tests whether interpolation works on an empty configuration.
437      */
438     @Test
439     void testInterpolatedConfigurationEmpty() {
440         config = new BaseHierarchicalConfiguration();
441         assertTrue(config.interpolatedConfiguration().isEmpty());
442     }
443 
444     /**
445      * Tests interpolation with a subset.
446      */
447     @Test
448     void testInterpolationSubset() {
449         InterpolationTestHelper.testInterpolationSubset(config);
450     }
451 
452     /**
453      * Tests whether interpolation with a subset configuration works over multiple layers.
454      */
455     @Test
456     void testInterpolationSubsetMultipleLayers() {
457         config.clear();
458         config.addProperty("var", "value");
459         config.addProperty("prop2.prop[@attr]", "${var}");
460         final Configuration sub1 = config.subset("prop2");
461         final Configuration sub2 = sub1.subset("prop");
462         assertEquals("value", sub2.getString("[@attr]"));
463     }
464 
465     @Test
466     void testSubset() {
467         // test the subset on the first table
468         Configuration subset = config.subset("tables.table(0)");
469         assertEquals(NodeStructureHelper.table(0), subset.getProperty("name"));
470 
471         Object prop = subset.getProperty("fields.field.name");
472         Collection<?> collection = assertInstanceOf(Collection.class, prop);
473         assertEquals(5, collection.size());
474 
475         for (int i = 0; i < NodeStructureHelper.fieldsLength(0); i++) {
476             final DefaultConfigurationKey key = createConfigurationKey();
477             key.append("fields").append("field").appendIndex(i);
478             key.append("name");
479             assertEquals(NodeStructureHelper.field(0, i), subset.getProperty(key.toString()));
480         }
481 
482         // test the subset on the second table
483         assertTrue(config.subset("tables.table(2)").isEmpty());
484 
485         // test the subset on the fields
486         subset = config.subset("tables.table.fields.field");
487         prop = subset.getProperty("name");
488         collection = assertInstanceOf(Collection.class, prop);
489         int expectedFieldCount = 0;
490         for (int i = 0; i < NodeStructureHelper.tablesLength(); i++) {
491             expectedFieldCount += NodeStructureHelper.fieldsLength(i);
492         }
493         assertEquals(expectedFieldCount, collection.size());
494 
495         assertEquals(NodeStructureHelper.field(0, 0), subset.getProperty("name(0)"));
496 
497         // test the subset on the field names
498         subset = config.subset("tables.table.fields.field.name");
499         assertTrue(subset.isEmpty());
500     }
501 
502     /**
503      * Tests subset() if the passed in key selects an attribute.
504      */
505     @Test
506     void testSubsetAttributeResult() {
507         final String key = "tables.table(0)[@type]";
508         config.addProperty(key, "system");
509         final BaseHierarchicalConfiguration subset = (BaseHierarchicalConfiguration) config.subset(key);
510         assertTrue(subset.getModel().getNodeHandler().getRootNode().getChildren().isEmpty());
511         assertEquals("system", subset.getString("[@type]"));
512     }
513 
514     /**
515      * Tests the subset() method if the specified key selects multiple keys. The resulting root node should have a value
516      * only if exactly one of the selected nodes has a value. Related to CONFIGURATION-295.
517      */
518     @Test
519     void testSubsetMultipleNodesWithValues() {
520         config.setProperty("tables.table(0).fields", "My fields");
521         Configuration subset = config.subset("tables.table.fields");
522         assertEquals("My fields", subset.getString(""));
523         config.setProperty("tables.table(1).fields", "My other fields");
524         subset = config.subset("tables.table.fields");
525         assertNull(subset.getString(""));
526     }
527 
528     /**
529      * Tests the subset() method if the specified node has a value. This value must be available in the subset, too. Related
530      * to CONFIGURATION-295.
531      */
532     @Test
533     void testSubsetNodeWithValue() {
534         config.setProperty("tables.table(0).fields", "My fields");
535         final Configuration subset = config.subset("tables.table(0).fields");
536         assertEquals(NodeStructureHelper.field(0, 0), subset.getString("field(0).name"));
537         assertEquals("My fields", subset.getString(""));
538     }
539 }