View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.configuration2;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertNotNull;
23  import static org.junit.jupiter.api.Assertions.assertNotSame;
24  import static org.junit.jupiter.api.Assertions.assertNull;
25  import static org.junit.jupiter.api.Assertions.assertSame;
26  import static org.junit.jupiter.api.Assertions.assertThrows;
27  import static org.junit.jupiter.api.Assertions.assertTrue;
28  import static org.mockito.Mockito.mock;
29  
30  import java.util.ArrayList;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Map;
34  
35  import org.apache.commons.configuration2.builder.XMLBuilderParametersImpl;
36  import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
37  import org.apache.commons.configuration2.event.ConfigurationErrorEvent;
38  import org.apache.commons.configuration2.event.ConfigurationEvent;
39  import org.apache.commons.configuration2.event.EventListener;
40  import org.apache.commons.configuration2.event.EventSource;
41  import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
42  import org.apache.commons.configuration2.sync.NoOpSynchronizer;
43  import org.apache.commons.configuration2.tree.DefaultExpressionEngine;
44  import org.apache.commons.configuration2.tree.DefaultExpressionEngineSymbols;
45  import org.apache.commons.configuration2.tree.ExpressionEngine;
46  import org.apache.commons.configuration2.tree.ImmutableNode;
47  import org.junit.jupiter.api.AfterEach;
48  import org.junit.jupiter.api.BeforeEach;
49  import org.junit.jupiter.api.Test;
50  
51  /**
52   * Tests the ConfigurationUtils class
53   */
54  public class TestConfigurationUtils {
55      /**
56       * A test Synchronizer implementation which can be cloned.
57       */
58      private static final class CloneableSynchronizer extends NonCloneableSynchronizer implements Cloneable {
59          /** A flag whether clone() was called. */
60          private final boolean cloned;
61  
62          /**
63           * Creates a new instance of {@code CloneableSynchronizer} and sets the clone flag.
64           *
65           * @param clone the clone flag
66           */
67          public CloneableSynchronizer(final boolean clone) {
68              cloned = clone;
69          }
70  
71          @Override
72          public Object clone() {
73              return new CloneableSynchronizer(true);
74          }
75  
76          /**
77           * Returns a flag whether this object was cloned.
78           *
79           * @return the clone flag
80           */
81          public boolean isCloned() {
82              return cloned;
83          }
84      }
85  
86      /**
87       * A test Synchronizer implementation which cannot be cloned.
88       */
89      private static class NonCloneableSynchronizer extends SynchronizerTestImpl {
90      }
91  
92      /** Constant for the name of a class to be loaded. */
93      private static final String CLS_NAME = "org.apache.commons.configuration2.PropertiesConfiguration";
94  
95      /** Stores the CCL. */
96      private ClassLoader ccl;
97  
98      @BeforeEach
99      public void setUp() throws Exception {
100         ccl = Thread.currentThread().getContextClassLoader();
101     }
102 
103     @AfterEach
104     public void tearDown() throws Exception {
105         Thread.currentThread().setContextClassLoader(ccl);
106     }
107 
108     @Test
109     void testAppend() {
110         // create the source configuration
111         final Configuration conf1 = new BaseConfiguration();
112         conf1.addProperty("key1", "value1");
113         conf1.addProperty("key2", "value2");
114 
115         // create the target configuration
116         final Configuration conf2 = new BaseConfiguration();
117         conf2.addProperty("key1", "value3");
118         conf2.addProperty("key2", "value4");
119 
120         // append the source configuration to the target configuration
121         ConfigurationUtils.append(conf1, conf2);
122 
123         List<Object> expected = new ArrayList<>();
124         expected.add("value3");
125         expected.add("value1");
126         assertEquals(expected, conf2.getList("key1"));
127 
128         expected = new ArrayList<>();
129         expected.add("value4");
130         expected.add("value2");
131         assertEquals(expected, conf2.getList("key2"));
132     }
133 
134     /**
135      * Tests asEventSource() if an exception is expected.
136      */
137     @Test
138     void testAsEventSourceNonSupportedEx() {
139         assertThrows(ConfigurationRuntimeException.class, () -> ConfigurationUtils.asEventSource(this, false));
140     }
141 
142     /**
143      * Tests asEventSource() if the passed in object implements this interface.
144      */
145     @Test
146     void testAsEventSourceSupported() {
147         final XMLConfiguration src = new XMLConfiguration();
148         assertSame(src, ConfigurationUtils.asEventSource(src, true));
149     }
150 
151     /**
152      * Tests asEventSource() if a mock object has to be returned.
153      */
154     @Test
155     void testAsEventSourceUnsupportedMock() {
156         @SuppressWarnings("unchecked")
157         final EventListener<ConfigurationEvent> cl = mock(EventListener.class);
158         final EventSource source = ConfigurationUtils.asEventSource(this, true);
159         source.addEventListener(ConfigurationEvent.ANY, cl);
160         assertFalse(source.removeEventListener(ConfigurationEvent.ANY, cl));
161         source.addEventListener(ConfigurationEvent.ANY, null);
162     }
163 
164     /**
165      * Tests cloning a configuration that supports this operation.
166      */
167     @Test
168     void testCloneConfiguration() {
169         final BaseHierarchicalConfiguration conf = new BaseHierarchicalConfiguration();
170         conf.addProperty("test", "yes");
171         final BaseHierarchicalConfiguration copy = (BaseHierarchicalConfiguration) ConfigurationUtils.cloneConfiguration(conf);
172         assertNotSame(conf, copy);
173         assertEquals("yes", copy.getString("test"));
174     }
175 
176     /**
177      * Tests cloning a configuration that does not support this operation. This should cause an exception.
178      */
179     @Test
180     void testCloneConfigurationNotSupported() {
181         final Configuration myNonCloneableConfig = new NonCloneableConfiguration();
182         assertThrows(ConfigurationRuntimeException.class, () -> ConfigurationUtils.cloneConfiguration(myNonCloneableConfig));
183     }
184 
185     /**
186      * Tests cloning a <strong>null</strong> configuration.
187      */
188     @Test
189     void testCloneConfigurationNull() {
190         assertNull(ConfigurationUtils.cloneConfiguration(null));
191     }
192 
193     /**
194      * Tests whether errors are handled correctly by cloneIfPossible().
195      */
196     @Test
197     void testCloneIfPossibleError() {
198         final XMLBuilderParametersImpl params = new XMLBuilderParametersImpl() {
199             @Override
200             public XMLBuilderParametersImpl clone() {
201                 throw new ConfigurationRuntimeException();
202             }
203         };
204         assertSame(params, ConfigurationUtils.cloneIfPossible(params));
205     }
206 
207     /**
208      * Tests cloneIfPossible() if the passed in object does not support cloning.
209      */
210     @Test
211     void testCloneIfPossibleNotSupported() {
212         final Long value = 20130116221714L;
213         assertSame(value, ConfigurationUtils.cloneIfPossible(value));
214     }
215 
216     /**
217      * Tests whether cloneIfPossible() can handle null parameters.
218      */
219     @Test
220     void testCloneIfPossibleNull() {
221         assertNull(ConfigurationUtils.cloneIfPossible(null));
222     }
223 
224     /**
225      * Tests whether an object can be cloned which supports cloning.
226      */
227     @Test
228     void testCloneIfPossibleSupported() {
229         final XMLBuilderParametersImpl params = new XMLBuilderParametersImpl();
230         params.setPublicID("testID");
231         params.setSchemaValidation(true);
232         final XMLBuilderParametersImpl clone = (XMLBuilderParametersImpl) ConfigurationUtils.cloneIfPossible(params);
233         assertNotSame(params, clone);
234         final Map<String, Object> map = clone.getParameters();
235         for (final Map.Entry<String, Object> e : params.getParameters().entrySet()) {
236             if (!e.getKey().startsWith("config-")) {
237                 assertEquals(e.getValue(), map.get(e.getKey()), "Wrong value for field " + e.getKey());
238             }
239         }
240     }
241 
242     /**
243      * Tests whether a Synchronizer can be cloned using its clone() method.
244      */
245     @Test
246     void testCloneSynchronizerClone() {
247         final CloneableSynchronizer sync = new CloneableSynchronizer(false);
248         final CloneableSynchronizer sync2 = (CloneableSynchronizer) ConfigurationUtils.cloneSynchronizer(sync);
249         assertTrue(sync2.isCloned());
250     }
251 
252     /**
253      * Tests cloneSynchronizer() if the argument cannot be cloned.
254      */
255     @Test
256     void testCloneSynchronizerFailed() {
257         final NonCloneableSynchronizer synchronizer = new NonCloneableSynchronizer();
258         assertThrows(ConfigurationRuntimeException.class, () -> ConfigurationUtils.cloneSynchronizer(synchronizer));
259     }
260 
261     /**
262      * Tests whether a new Synchronizer can be created using reflection.
263      */
264     @Test
265     void testCloneSynchronizerNewInstance() {
266         final SynchronizerTestImpl sync = new SynchronizerTestImpl();
267         final SynchronizerTestImpl sync2 = (SynchronizerTestImpl) ConfigurationUtils.cloneSynchronizer(sync);
268         assertNotNull(sync2);
269         assertNotSame(sync, sync2);
270     }
271 
272     /**
273      * Tests whether the NoOpSyhnchronizer can be cloned.
274      */
275     @Test
276     void testCloneSynchronizerNoOp() {
277         assertSame(NoOpSynchronizer.INSTANCE, ConfigurationUtils.cloneSynchronizer(NoOpSynchronizer.INSTANCE));
278     }
279 
280     /**
281      * Tries to clone a null Synchronizer.
282      */
283     @Test
284     void testCloneSynchronizerNull() {
285         assertThrows(IllegalArgumentException.class, () -> ConfigurationUtils.cloneSynchronizer(null));
286     }
287 
288     /**
289      * Tests converting a configuration into a hierarchical one that is already hierarchical.
290      */
291     @Test
292     void testConvertHierarchicalToHierarchical() {
293         final Configuration conf = new BaseHierarchicalConfiguration();
294         conf.addProperty("test", "yes");
295         assertSame(conf, ConfigurationUtils.convertToHierarchical(conf));
296     }
297 
298     /**
299      * Tests converting an already hierarchical configuration using an expression engine. The new engine should be set.
300      */
301     @Test
302     void testConvertHierarchicalToHierarchicalEngine() {
303         final BaseHierarchicalConfiguration hc = new BaseHierarchicalConfiguration();
304         final ExpressionEngine engine = new DefaultExpressionEngine(DefaultExpressionEngineSymbols.DEFAULT_SYMBOLS);
305         assertSame(hc, ConfigurationUtils.convertToHierarchical(hc, engine));
306         assertSame(engine, hc.getExpressionEngine());
307     }
308 
309     /**
310      * Tests converting an already hierarchical configuration using a null expression engine. In this case the expression
311      * engine of the configuration should not be touched.
312      */
313     @Test
314     void testConvertHierarchicalToHierarchicalNullEngine() {
315         final BaseHierarchicalConfiguration hc = new BaseHierarchicalConfiguration();
316         final ExpressionEngine engine = new DefaultExpressionEngine(DefaultExpressionEngineSymbols.DEFAULT_SYMBOLS);
317         hc.setExpressionEngine(engine);
318         assertSame(hc, ConfigurationUtils.convertToHierarchical(hc, null));
319         assertSame(engine, hc.getExpressionEngine());
320     }
321 
322     /**
323      * Tests converting a null configuration to a hierarchical one. The result should be null, too.
324      */
325     @Test
326     void testConvertNullToHierarchical() {
327         assertNull(ConfigurationUtils.convertToHierarchical(null));
328     }
329 
330     /**
331      * Tests converting a configuration into a hierarchical one.
332      */
333     @Test
334     void testConvertToHierarchical() {
335         final Configuration conf = new BaseConfiguration();
336         for (int i = 0; i < 10; i++) {
337             conf.addProperty("test" + i, "value" + i);
338             conf.addProperty("test.list", "item" + i);
339         }
340         final BaseHierarchicalConfiguration hc = (BaseHierarchicalConfiguration) ConfigurationUtils.convertToHierarchical(conf);
341         for (final Iterator<String> it = conf.getKeys(); it.hasNext();) {
342             final String key = it.next();
343             assertEquals(conf.getProperty(key), hc.getProperty(key), "Wrong value for key " + key);
344         }
345     }
346 
347     /**
348      * Tests converting a configuration into a hierarchical one if some of its properties contain escaped list delimiter
349      * characters.
350      */
351     @Test
352     void testConvertToHierarchicalDelimiters() {
353         final BaseConfiguration conf = new BaseConfiguration();
354         conf.setListDelimiterHandler(new DefaultListDelimiterHandler(','));
355         conf.addProperty("test.key", "1\\,2\\,3");
356         assertEquals("1,2,3", conf.getString("test.key"));
357         final HierarchicalConfiguration<?> hc = ConfigurationUtils.convertToHierarchical(conf);
358         assertEquals("1,2,3", hc.getString("test.key"));
359     }
360 
361     /**
362      * Tests converting a configuration to a hierarchical one using a specific expression engine.
363      */
364     @Test
365     void testConvertToHierarchicalEngine() {
366         final Configuration conf = new BaseConfiguration();
367         conf.addProperty("test(a)", Boolean.TRUE);
368         conf.addProperty("test(b)", Boolean.FALSE);
369         final DefaultExpressionEngine engine = new DefaultExpressionEngine(
370             new DefaultExpressionEngineSymbols.Builder(DefaultExpressionEngineSymbols.DEFAULT_SYMBOLS).setIndexStart("[").setIndexEnd("]").create());
371         final HierarchicalConfiguration<?> hc = ConfigurationUtils.convertToHierarchical(conf, engine);
372         assertTrue(hc.getBoolean("test(a)"));
373         assertFalse(hc.getBoolean("test(b)"));
374     }
375 
376     /**
377      * Tests converting a configuration to a hierarchical one that contains a property with multiple values. This test is
378      * related to CONFIGURATION-346.
379      */
380     @Test
381     void testConvertToHierarchicalMultiValues() {
382         final BaseConfiguration config = new BaseConfiguration();
383         config.setListDelimiterHandler(new DefaultListDelimiterHandler(','));
384         config.addProperty("test", "1,2,3");
385         final HierarchicalConfiguration<?> hc = ConfigurationUtils.convertToHierarchical(config);
386         assertEquals(1, hc.getInt("test(0)"));
387         assertEquals(2, hc.getInt("test(1)"));
388         assertEquals(3, hc.getInt("test(2)"));
389     }
390 
391     /**
392      * Tests that the structure of the resulting hierarchical configuration does not depend on the order of properties in
393      * the source configuration. This test is related to CONFIGURATION-604.
394      */
395     @Test
396     void testConvertToHierarchicalOrderOfProperties() {
397         final PropertiesConfiguration config = new PropertiesConfiguration();
398         config.addProperty("x.y.z", true);
399         config.addProperty("x.y", true);
400         @SuppressWarnings("unchecked")
401         final HierarchicalConfiguration<ImmutableNode> hc = (HierarchicalConfiguration<ImmutableNode>) ConfigurationUtils.convertToHierarchical(config);
402         final ImmutableNode rootNode = hc.getNodeModel().getNodeHandler().getRootNode();
403         final ImmutableNode nodeX = rootNode.getChildren().get(0);
404         assertEquals(1, nodeX.getChildren().size());
405     }
406 
407     @Test
408     void testCopy() {
409         // create the source configuration
410         final Configuration conf1 = new BaseConfiguration();
411         conf1.addProperty("key1", "value1");
412         conf1.addProperty("key2", "value2");
413         // create the target configuration
414         final Configuration conf2 = new BaseConfiguration();
415         conf2.addProperty("key1", "value3");
416         conf2.addProperty("key2", "value4");
417         // copy the source configuration into the target configuration
418         ConfigurationUtils.copy(conf1, conf2);
419         assertEquals("value1", conf2.getProperty("key1"));
420         assertEquals("value2", conf2.getProperty("key2"));
421     }
422 
423     /**
424      * Tests whether runtime exceptions can be enabled.
425      */
426     @Test
427     void testEnableRuntimeExceptions() {
428         final PropertiesConfiguration config = new PropertiesConfiguration() {
429             @Override
430             protected void addPropertyDirect(final String key, final Object value) {
431                 // always simulate an exception
432                 fireError(ConfigurationErrorEvent.WRITE, ConfigurationEvent.ADD_PROPERTY, key, value, new RuntimeException("A faked exception!"));
433             }
434         };
435         config.clearErrorListeners();
436         ConfigurationUtils.enableRuntimeExceptions(config);
437         assertThrows(ConfigurationRuntimeException.class, () -> config.addProperty("test", "testValue"));
438     }
439 
440     /**
441      * Tries to enable runtime exceptions for a configuration that does not inherit from EventSource. This should cause an
442      * exception.
443      */
444     @Test
445     void testEnableRuntimeExceptionsInvalid() {
446         final Configuration c = mock(Configuration.class);
447         assertThrows(IllegalArgumentException.class, () -> ConfigurationUtils.enableRuntimeExceptions(c));
448     }
449 
450     /**
451      * Tries to enable runtime exceptions for a null configuration. This should cause an exception.
452      */
453     @Test
454     void testEnableRuntimeExceptionsNull() {
455         assertThrows(IllegalArgumentException.class, () -> ConfigurationUtils.enableRuntimeExceptions(null));
456     }
457 
458     /**
459      * Tests whether a class can be loaded if it is not found by the CCL.
460      */
461     @Test
462     void testLoadClassCCLNotFound() throws ClassNotFoundException {
463         Thread.currentThread().setContextClassLoader(new ClassLoader() {
464             @Override
465             public Class<?> loadClass(final String name) throws ClassNotFoundException {
466                 throw new ClassNotFoundException(name);
467             }
468         });
469         assertEquals(CLS_NAME, ConfigurationUtils.loadClass(CLS_NAME).getName());
470     }
471 
472     /**
473      * Tests whether a class can be loaded if there is no CCL.
474      */
475     @Test
476     void testLoadClassCCLNull() throws ClassNotFoundException {
477         Thread.currentThread().setContextClassLoader(null);
478         assertEquals(CLS_NAME, ConfigurationUtils.loadClass(CLS_NAME).getName());
479     }
480 
481     /**
482      * Tests whether a class can be loaded from CCL.
483      */
484     @Test
485     void testLoadClassFromCCL() throws ClassNotFoundException {
486         Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
487         assertEquals(CLS_NAME, ConfigurationUtils.loadClass(CLS_NAME).getName());
488     }
489 
490     /**
491      * Tests loadClassNoEx() if the class can be resolved.
492      */
493     @Test
494     void testLoadClassNoExFound() {
495         assertEquals(CLS_NAME, ConfigurationUtils.loadClassNoEx(CLS_NAME).getName());
496     }
497 
498     /**
499      * Tests loadClassNoEx() if the class cannot be resolved.
500      */
501     @Test
502     void testLoadClassNoExNotFound() {
503         assertThrows(ConfigurationRuntimeException.class, () -> ConfigurationUtils.loadClassNoEx("a non existing class!"));
504     }
505 
506     /**
507      * Tests the behavior of loadClass() for a non-existing class.
508      */
509     @Test
510     void testLoadClassNotFound() {
511         assertThrows(ClassNotFoundException.class, () -> ConfigurationUtils.loadClass("a non existing class!"));
512     }
513 
514     @Test
515     void testToString() {
516         final Configuration config = new BaseConfiguration();
517         final String lineSeparator = System.lineSeparator();
518 
519         assertEquals("", ConfigurationUtils.toString(config));
520 
521         config.setProperty("one", "1");
522         assertEquals("one=1", ConfigurationUtils.toString(config));
523 
524         config.setProperty("two", "2");
525         assertEquals("one=1" + lineSeparator + "two=2", ConfigurationUtils.toString(config));
526 
527         config.clearProperty("one");
528         assertEquals("two=2", ConfigurationUtils.toString(config));
529 
530         config.setProperty("one", "1");
531         assertEquals("two=2" + lineSeparator + "one=1", ConfigurationUtils.toString(config));
532     }
533 }