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.plist;
19  
20  import static org.apache.commons.configuration2.TempDirUtils.newFile;
21  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
22  import static org.junit.jupiter.api.Assertions.assertEquals;
23  import static org.junit.jupiter.api.Assertions.assertFalse;
24  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
25  import static org.junit.jupiter.api.Assertions.assertNotNull;
26  import static org.junit.jupiter.api.Assertions.assertNull;
27  import static org.junit.jupiter.api.Assertions.assertThrows;
28  import static org.junit.jupiter.api.Assertions.assertTrue;
29  
30  import java.io.File;
31  import java.io.FileWriter;
32  import java.io.IOException;
33  import java.io.StringWriter;
34  import java.io.Writer;
35  import java.nio.charset.StandardCharsets;
36  import java.util.Arrays;
37  import java.util.Calendar;
38  import java.util.Iterator;
39  import java.util.List;
40  import java.util.TimeZone;
41  
42  import org.apache.commons.configuration2.Configuration;
43  import org.apache.commons.configuration2.ConfigurationAssert;
44  import org.apache.commons.configuration2.ConfigurationComparator;
45  import org.apache.commons.configuration2.StrictConfigurationComparator;
46  import org.apache.commons.configuration2.ex.ConfigurationException;
47  import org.apache.commons.configuration2.io.FileHandler;
48  import org.junit.jupiter.api.BeforeEach;
49  import org.junit.jupiter.api.Test;
50  import org.junit.jupiter.api.io.TempDir;
51  
52  /**
53   */
54  public class TestXMLPropertyListConfiguration {
55  
56      /**
57       * Loads a test configuration.
58       *
59       * @param c the configuration object to be loaded
60       * @param file the test file to be loaded
61       * @throws ConfigurationException if an error occurs
62       */
63      private static void load(final XMLPropertyListConfiguration c, final File file) throws ConfigurationException {
64          new FileHandler(c).load(file);
65      }
66  
67      /** A folder for temporary files. */
68      @TempDir
69      public File tempFolder;
70  
71      /** The test configuration. */
72      private XMLPropertyListConfiguration config;
73  
74      /**
75       * Checks whether the test configuration contains a key with an array value.
76       *
77       * @param expectedValues the expected values
78       */
79      private void checkArrayProperty(final List<?> expectedValues) throws ConfigurationException {
80          final StringWriter out = new StringWriter();
81          new FileHandler(config).save(out);
82          final StringBuilder values = new StringBuilder();
83          for (final Object v : expectedValues) {
84              values.append("<string>").append(v).append("</string>");
85          }
86          final String content = out.toString().replaceAll("[ \n\r]", "");
87          assertTrue(content.contains(String.format("<key>array</key><array>%s</array>", values)));
88      }
89  
90      /**
91       * Saves the test configuration to the specified file.
92       *
93       * @param file the target file
94       * @throws ConfigurationException if an error occurs
95       */
96      private void save(final File file) throws ConfigurationException {
97          new FileHandler(config).save(file);
98      }
99  
100     @BeforeEach
101     public void setUp() throws Exception {
102         config = new XMLPropertyListConfiguration();
103         load(config, ConfigurationAssert.getTestFile("test.plist.xml"));
104     }
105 
106     /**
107      * Tests whether an array can be added correctly. This test is related to CONFIGURATION-427.
108      */
109     @Test
110     void testAddArray() throws ConfigurationException {
111         final Object[] elems = {"arrayElem1", "arrayElem2", "arrayElem3"};
112         config = new XMLPropertyListConfiguration();
113         config.addProperty("array", elems);
114 
115         checkArrayProperty(Arrays.asList(elems));
116     }
117 
118     /**
119      * Ensure that addProperty doesn't alter an array of byte
120      */
121     @Test
122     void testAddDataProperty() throws Exception {
123         final File savedFile = newFile(tempFolder);
124         final byte[] expected = {1, 2, 3, 4};
125         config = new XMLPropertyListConfiguration();
126         config.addProperty("foo", expected);
127         save(savedFile);
128 
129         final XMLPropertyListConfiguration config2 = new XMLPropertyListConfiguration();
130         load(config2, savedFile);
131         final Object array = config2.getProperty("foo");
132 
133         assertNotNull(array);
134         assertEquals(byte[].class, array.getClass());
135         assertArrayEquals(expected, (byte[]) array);
136     }
137 
138     /**
139      * Tests whether a list can be added correctly. This test is related to CONFIGURATION-427.
140      */
141     @Test
142     void testAddList() throws ConfigurationException {
143         final List<String> elems = Arrays.asList("element1", "element2", "anotherElement");
144         config = new XMLPropertyListConfiguration();
145         config.addProperty("array", elems);
146 
147         checkArrayProperty(elems);
148     }
149 
150     @Test
151     void testArray() {
152         final Object array = config.getProperty("array");
153 
154         assertInstanceOf(List.class, array);
155         final List<?> list = config.getList("array");
156         assertEquals(Arrays.asList("value1", "value2", "value3"), list);
157     }
158 
159     @Test
160     void testBoolean() throws Exception {
161         assertTrue(config.getBoolean("boolean1"));
162         assertFalse(config.getBoolean("boolean2"));
163     }
164 
165     @Test
166     void testDate() throws Exception {
167         final Calendar calendar = Calendar.getInstance();
168         calendar.clear();
169         calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
170         calendar.set(2005, Calendar.JANUARY, 1, 12, 0, 0);
171 
172         assertEquals(calendar.getTime(), config.getProperty("date"));
173 
174         calendar.setTimeZone(TimeZone.getTimeZone("CET"));
175         calendar.set(2002, Calendar.MARCH, 22, 11, 30, 0);
176 
177         assertEquals(calendar.getTime(), config.getProperty("date-gnustep"));
178     }
179 
180     @Test
181     void testDictionary() {
182         assertEquals("value1", config.getProperty("dictionary.key1"));
183         assertEquals("value2", config.getProperty("dictionary.key2"));
184         assertEquals("value3", config.getProperty("dictionary.key3"));
185     }
186 
187     @Test
188     void testDictionaryArray() {
189         final String key = "dictionary-array";
190 
191         final Object array = config.getProperty(key);
192 
193         // root array
194         assertNotNull(array);
195         assertInstanceOf(List.class, array);
196         final List<?> list = config.getList(key);
197 
198         assertEquals(2, list.size());
199 
200         // 1st dictionary
201         final Configuration conf1 = assertInstanceOf(Configuration.class, list.get(0));
202         assertFalse(conf1.isEmpty());
203         assertEquals("bar", conf1.getProperty("foo"));
204 
205         // 2nd dictionary
206         final Configuration conf2 = assertInstanceOf(Configuration.class, list.get(1));
207         assertFalse(conf2.isEmpty());
208         assertEquals("value", conf2.getProperty("key"));
209     }
210 
211     @Test
212     void testInitCopy() {
213         final XMLPropertyListConfiguration copy = new XMLPropertyListConfiguration(config);
214         final StrictConfigurationComparator comp = new StrictConfigurationComparator();
215         assertTrue(comp.compare(config, copy));
216     }
217 
218     @Test
219     void testInteger() throws Exception {
220         assertEquals(12345678900L, config.getLong("integer"));
221     }
222 
223     /**
224      * Tests whether a configuration can be loaded that does not start with a {@code dict} element. This test case is
225      * related to CONFIGURATION-405.
226      */
227     @Test
228     void testLoadNoDict() throws ConfigurationException {
229         final XMLPropertyListConfiguration plist = new XMLPropertyListConfiguration();
230         load(plist, ConfigurationAssert.getTestFile("test2.plist.xml"));
231         assertFalse(plist.isEmpty());
232     }
233 
234     /**
235      * Tests whether a configuration that does not start with a {@code dict} element can be loaded from a constructor. This
236      * test case is related to CONFIGURATION-405.
237      */
238     @Test
239     void testLoadNoDictConstr() throws ConfigurationException {
240         final XMLPropertyListConfiguration plist = new XMLPropertyListConfiguration();
241         load(plist, ConfigurationAssert.getTestFile("test2.plist.xml"));
242         assertFalse(plist.isEmpty());
243     }
244 
245     @Test
246     void testNested() {
247         assertEquals("value", config.getString("nested.node1.node2.node3"));
248     }
249 
250     @Test
251     void testNestedArray() {
252         final String key = "nested-array";
253 
254         final Object array = config.getProperty(key);
255 
256         // root array
257         assertNotNull(array);
258         assertInstanceOf(List.class, array);
259         final List<?> list = config.getList(key);
260 
261         assertEquals(2, list.size());
262 
263         // 1st array
264         final List<?> list1 = assertInstanceOf(List.class, list.get(0));
265         assertEquals(Arrays.asList("a", "b"), list1);
266 
267         // 2nd array
268         final List<?> list2 = assertInstanceOf(List.class, list.get(1));
269         assertEquals(Arrays.asList("c", "d"), list2);
270     }
271 
272     @Test
273     void testReal() throws Exception {
274         assertEquals(-12.345, config.getDouble("real"), 0);
275     }
276 
277     @Test
278     void testSave() throws Exception {
279         final File savedFile = newFile(tempFolder);
280 
281         // add an array of strings to the configuration
282         /*
283          * config.addProperty("string", "value1"); List list = new ArrayList(); for (int i = 1; i < 5; i++) { list.add("value" +
284          * i); } config.addProperty("newarray", list);
285          */
286         // todo : investigate why the array structure of 'newarray' is lost in the saved file
287 
288         // add a map of strings
289         /*
290          * Map map = new HashMap(); map.put("foo", "bar"); map.put("int", new Integer(123)); config.addProperty("newmap", map);
291          */
292         // todo : a Map added to a HierarchicalConfiguration should be decomposed as list of nodes
293 
294         // save the configuration
295         save(savedFile);
296         assertTrue(savedFile.exists());
297 
298         // read the configuration and compare the properties
299         final XMLPropertyListConfiguration checkConfig = new XMLPropertyListConfiguration();
300         load(checkConfig, savedFile);
301 
302         final Iterator<String> it = config.getKeys();
303         while (it.hasNext()) {
304             final String key = it.next();
305             assertTrue(checkConfig.containsKey(key), "The saved configuration doesn't contain the key '" + key + "'");
306 
307             final Object value = checkConfig.getProperty(key);
308             if (value instanceof byte[]) {
309                 final byte[] array = (byte[]) value;
310                 assertArrayEquals((byte[]) config.getProperty(key), array, "Value of the '" + key + "' property");
311             } else if (value instanceof List) {
312                 final List<?> list1 = (List<?>) config.getProperty(key);
313                 final List<?> list2 = (List<?>) value;
314 
315                 assertEquals(list1.size(), list2.size(), "The size of the list for the key '" + key + "' doesn't match");
316 
317                 for (int i = 0; i < list2.size(); i++) {
318                     final Object value1 = list1.get(i);
319                     final Object value2 = list2.get(i);
320 
321                     if (value1 instanceof Configuration) {
322                         final ConfigurationComparator comparator = new StrictConfigurationComparator();
323                         assertTrue(comparator.compare((Configuration) value1, (Configuration) value2),
324                                 "The dictionnary at index " + i + " for the key '" + key + "' doesn't match");
325                     } else {
326                         assertEquals(value1, value2, "Element at index " + i + " for the key '" + key + "'");
327                     }
328                 }
329 
330                 assertEquals(config.getProperty(key), list1, "Value of the '" + key + "' property");
331             } else {
332                 assertEquals(config.getProperty(key), checkConfig.getProperty(key), "Value of the '" + key + "' property");
333             }
334 
335         }
336     }
337 
338     @Test
339     void testSaveEmptyDictionary() throws Exception {
340         final File savedFile = newFile(tempFolder);
341 
342         // save the configuration
343         save(savedFile);
344         assertTrue(savedFile.exists());
345 
346         // read the configuration and compare the properties
347         final XMLPropertyListConfiguration checkConfig = new XMLPropertyListConfiguration();
348         load(checkConfig, savedFile);
349 
350         assertNull(config.getProperty("empty-dictionary"));
351         assertNull(checkConfig.getProperty("empty-dictionary"));
352     }
353 
354     /**
355      * Tests the header of a saved file if no encoding is specified.
356      */
357     @Test
358     void testSaveNoEncoding() throws ConfigurationException {
359         final StringWriter writer = new StringWriter();
360         new FileHandler(config).save(writer);
361         assertTrue(writer.toString().contains("<?xml version=\"1.0\"?>"));
362     }
363 
364     /**
365      * Tests whether the encoding is written when saving a configuration.
366      */
367     @Test
368     void testSaveWithEncoding() throws ConfigurationException {
369         final String encoding = StandardCharsets.UTF_8.name();
370         final FileHandler handler = new FileHandler(config);
371         handler.setEncoding(encoding);
372         final StringWriter writer = new StringWriter();
373         handler.save(writer);
374         assertTrue(writer.toString().contains("<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>"));
375     }
376 
377     /**
378      * Tests whether an array can be set correctly. This test is related to CONFIGURATION-750.
379      */
380     @Test
381     void testSetArray() throws ConfigurationException {
382         final Object[] elems = {"arrayElem1", "arrayElem2", "arrayElem3"};
383         config = new XMLPropertyListConfiguration();
384         config.setProperty("array", elems);
385 
386         checkArrayProperty(Arrays.asList(elems));
387     }
388 
389     /**
390      * Ensure that setProperty doesn't alter an array of byte since it's a first class type in plist file
391      */
392     @Test
393     void testSetDataProperty() throws Exception {
394         final File savedFile = newFile(tempFolder);
395         final byte[] expected = {1, 2, 3, 4};
396         config = new XMLPropertyListConfiguration();
397         config.setProperty("foo", expected);
398         save(savedFile);
399 
400         final XMLPropertyListConfiguration config2 = new XMLPropertyListConfiguration();
401         load(config2, savedFile);
402         final Object array = config2.getProperty("foo");
403 
404         assertNotNull(array);
405         assertEquals(byte[].class, array.getClass());
406         assertArrayEquals(expected, (byte[]) array);
407     }
408 
409     /**
410      * Tests a configuration file which contains an invalid date property value. This test is related to CONFIGURATION-501.
411      */
412     @Test
413     void testSetDatePropertyInvalid() throws ConfigurationException {
414         config.clear();
415         load(config, ConfigurationAssert.getTestFile("test_invalid_date.plist.xml"));
416         assertEquals("value1", config.getString("string"));
417         assertFalse(config.containsKey("date"));
418     }
419 
420     /**
421      * Tests whether a list can be set correctly. This test is related to CONFIGURATION-750.
422      */
423     @Test
424     void testSetList() throws ConfigurationException {
425         final List<String> elems = Arrays.asList("element1", "element2", "anotherElement");
426         config = new XMLPropertyListConfiguration();
427         config.setProperty("array", elems);
428 
429         checkArrayProperty(elems);
430     }
431 
432     @Test
433     void testString() throws Exception {
434         assertEquals("value1", config.getString("string"));
435     }
436 
437     @Test
438     void testSubset() {
439         final Configuration subset = config.subset("dictionary");
440         final Iterator<String> keys = subset.getKeys();
441 
442         String key = keys.next();
443         assertEquals("key1", key);
444         assertEquals("value1", subset.getString(key));
445 
446         key = keys.next();
447         assertEquals("key2", key);
448         assertEquals("value2", subset.getString(key));
449 
450         key = keys.next();
451         assertEquals("key3", key);
452         assertEquals("value3", subset.getString(key));
453 
454         assertFalse(keys.hasNext());
455     }
456 
457     /**
458      * Tests a direct invocation of the write() method. This test is related to CONFIGURATION-641.
459      */
460     @Test
461     void testWriteCalledDirectly() throws IOException {
462         config = new XMLPropertyListConfiguration();
463         config.addProperty("foo", "bar");
464 
465         try (Writer out = new FileWriter(newFile(tempFolder))) {
466             final ConfigurationException e = assertThrows(ConfigurationException.class, () -> config.write(out));
467             assertTrue(e.getMessage().contains("FileHandler"));
468         }
469     }
470 }