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