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