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