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