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  package org.apache.commons.configuration2;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertThrows;
21  import static org.junit.jupiter.api.Assertions.assertTrue;
22  
23  import java.io.StringReader;
24  import java.io.StringWriter;
25  import java.util.Arrays;
26  import java.util.List;
27  
28  import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
29  import org.apache.commons.configuration2.convert.DisabledListDelimiterHandler;
30  import org.apache.commons.configuration2.ex.ConfigurationException;
31  import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
32  import org.apache.commons.configuration2.io.FileHandler;
33  import org.apache.commons.lang3.StringUtils;
34  import org.junit.jupiter.api.BeforeEach;
35  import org.junit.jupiter.api.Test;
36  
37  /**
38   * Test class to test the handling of list structures in XMLConfiguration.
39   */
40  public class TestXMLListHandling {
41      /** XML to be loaded by the configuration. */
42      private static final String SOURCE = "<config><values>a,b,c</values><split><value>1</value><value>2</value></split>"
43              + "<mixed><values>foo,blah</values><values>bar,baz</values></mixed></config>";
44  
45      /** Key for the string property with multiple values. */
46      private static final String KEY_VALUES = "values";
47  
48      /** Key for the split list property. */
49      private static final String KEY_SPLIT = "split.value";
50  
51      /** The XML element name for the single values of the split list. */
52      private static final String ELEM_SPLIT = "value";
53  
54      /**
55       * Checks whether the specified XML contains a list of values as a single, comma-separated string.
56       *
57       * @param xml the XML
58       * @param key the key
59       * @param values the expected values
60       */
61      private static void checkCommaSeparated(final String xml, final String key, final String... values) {
62          final String strValues = StringUtils.join(values, ',');
63          final String element = element(key, strValues);
64          assertTrue(xml.contains(element));
65      }
66  
67      /**
68       * Checks whether the specified XML contains a list of values as multiple XML elements.
69       *
70       * @param xml the XML
71       * @param key the key
72       * @param values the expected values
73       */
74      private static void checkSplit(final String xml, final String key, final String... values) {
75          for (final String v : values) {
76              assertTrue(xml.contains(element(key, v)));
77          }
78      }
79  
80      /**
81       * Generates an XML element with the specified value as body.
82       *
83       * @param key the key
84       * @param value the value
85       * @return the string representation of this element
86       */
87      private static String element(final String key, final String value) {
88          return "<" + key + '>' + value + "</" + key + '>';
89      }
90  
91      /**
92       * Parses the specified string into an XML configuration.
93       *
94       * @param xml the XML to be parsed
95       * @return the resulting configuration
96       */
97      private static XMLConfiguration readFromString(final String xml) throws ConfigurationException {
98          final XMLConfiguration config = new XMLConfiguration();
99          config.setListDelimiterHandler(new DefaultListDelimiterHandler(','));
100         final FileHandler handler = new FileHandler(config);
101         handler.load(new StringReader(xml));
102         return config;
103     }
104 
105     /** Configuration to be tested. */
106     private XMLConfiguration config;
107 
108     /**
109      * Saves the test configuration into a string.
110      *
111      * @return the resulting string
112      */
113     private String saveToString() throws ConfigurationException {
114         final StringWriter writer = new StringWriter(4096);
115         final FileHandler handler = new FileHandler(config);
116         handler.save(writer);
117         return writer.toString();
118     }
119 
120     @BeforeEach
121     public void setUp() throws Exception {
122         config = readFromString(SOURCE);
123     }
124 
125     /**
126      * Tests that a list item can be added without affecting the format.
127      */
128     @Test
129     void testAddListItem() throws ConfigurationException {
130         config.addProperty(KEY_VALUES, "d");
131         config.addProperty(KEY_SPLIT, "3");
132         final String xml = saveToString();
133 
134         checkSplit(xml, ELEM_SPLIT, "1", "2", "3");
135         checkCommaSeparated(xml, KEY_VALUES, "a", "b", "c", "d");
136     }
137 
138     /**
139      * Tries to save the configuration with a different list delimiter handler which does not support escaping of lists.
140      * This should fail with a meaningful exception message.
141      */
142     @Test
143     void testIncompatibleListDelimiterOnSaving() {
144         config.setListDelimiterHandler(DisabledListDelimiterHandler.INSTANCE);
145         assertThrows(ConfigurationRuntimeException.class, this::saveToString);
146     }
147 
148     /**
149      * Tests whether a list consisting of multiple elements where some elements define multiple values is handled correctly.
150      */
151     @Test
152     void testMixedList() throws ConfigurationException {
153         final List<String> expected = Arrays.asList("foo", "blah", "bar", "baz");
154         assertEquals(expected, config.getList("mixed.values"));
155         final String xml = saveToString();
156 
157         final XMLConfiguration c2 = readFromString(xml);
158         assertEquals(expected, c2.getList("mixed.values"));
159     }
160 
161     /**
162      * Tests that a list item can be removed without affecting the format.
163      */
164     @Test
165     void testRemoveListItem() throws ConfigurationException {
166         config.clearProperty(KEY_VALUES + "(2)");
167         config.clearProperty(KEY_SPLIT + "(1)");
168         final String xml = saveToString();
169 
170         checkSplit(xml, ELEM_SPLIT, "1");
171         checkCommaSeparated(xml, KEY_VALUES, "a", "b");
172     }
173 
174     /**
175      * Tests that the list format is kept if properties are not touched,
176      */
177     @Test
178     void testSaveNoChanges() throws ConfigurationException {
179         final String xml = saveToString();
180 
181         checkSplit(xml, ELEM_SPLIT, "1", "2");
182         checkCommaSeparated(xml, KEY_VALUES, "a", "b", "c");
183     }
184 }