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.configuration;
19
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Properties;
26
27 /**
28 * <p>
29 * A Map based Configuration.
30 * </p>
31 * <p>
32 * This implementation of the {@code Configuration} interface is
33 * initialized with a {@code java.util.Map}. The methods of the
34 * {@code Configuration} interface are implemented on top of the content of
35 * this map. The following storage scheme is used:
36 * </p>
37 * <p>
38 * Property keys are directly mapped to map keys, i.e. the
39 * {@code getProperty()} method directly performs a {@code get()} on
40 * the map. Analogously, {@code setProperty()} or
41 * {@code addProperty()} operations write new data into the map. If a value
42 * is added to an existing property, a {@code java.util.List} is created,
43 * which stores the values of this property.
44 * </p>
45 * <p>
46 * An important use case of this class is to treat a map as a
47 * {@code Configuration} allowing access to its data through the richer
48 * interface. This can be a bit problematic in some cases because the map may
49 * contain values that need not adhere to the default storage scheme used by
50 * typical configuration implementations, e.g. regarding lists. In such cases
51 * care must be taken when manipulating the data through the
52 * {@code Configuration} interface, e.g. by calling
53 * {@code addProperty()}; results may be different than expected.
54 * </p>
55 * <p>
56 * An important point is the handling of list delimiters: If delimiter parsing
57 * is enabled (which it is per default), {@code getProperty()} checks
58 * whether the value of a property is a string and whether it contains the list
59 * delimiter character. If this is the case, the value is split at the delimiter
60 * resulting in a list. This split operation typically also involves trimming
61 * the single values as the list delimiter character may be surrounded by
62 * whitespace. Trimming can be disabled with the
63 * {@link #setTrimmingDisabled(boolean)} method. The whole list splitting
64 * behavior can be disabled using the
65 * {@link #setDelimiterParsingDisabled(boolean)} method.
66 * </p>
67 * <p>
68 * Notice that list splitting is only performed for single string values. If a
69 * property has multiple values, the single values are not split even if they
70 * contain the list delimiter character.
71 * </p>
72 * <p>
73 * As the underlying {@code Map} is directly used as store of the property
74 * values, the thread-safety of this {@code Configuration} implementation
75 * depends on the map passed to the constructor.
76 * </p>
77 * <p>
78 * Notes about type safety: For properties with multiple values this implementation
79 * creates lists of type {@code Object} and stores them. If a property is assigned
80 * another value, the value is added to the list. This can cause problems if the
81 * map passed to the constructor already contains lists of other types. This
82 * should be avoided, otherwise it cannot be guaranteed that the application
83 * might throw {@code ClassCastException} exceptions later.
84 * </p>
85 *
86 * @author Emmanuel Bourg
87 * @version $Id: MapConfiguration.java 1210171 2011-12-04 18:32:07Z oheger $
88 * @since 1.1
89 */
90 public class MapConfiguration extends AbstractConfiguration implements Cloneable
91 {
92 /** The Map decorated by this configuration. */
93 protected Map<String, Object> map;
94
95 /** A flag whether trimming of property values should be disabled.*/
96 private boolean trimmingDisabled;
97
98 /**
99 * Create a Configuration decorator around the specified Map. The map is
100 * used to store the configuration properties, any change will also affect
101 * the Map.
102 *
103 * @param map the map
104 */
105 public MapConfiguration(Map<String, Object> map)
106 {
107 this.map = map;
108 }
109
110 /**
111 * Creates a new instance of {@code MapConfiguration} and initializes its
112 * content from the specified {@code Properties} object. The resulting
113 * configuration is not connected to the {@code Properties} object, but all
114 * keys which are strings are copied (keys of other types are ignored).
115 *
116 * @param props the {@code Properties} object defining the content of this
117 * configuration
118 * @throws NullPointerException if the {@code Properties} object is
119 * <b>null</b>
120 * @since 1.8
121 */
122 public MapConfiguration(Properties props)
123 {
124 map = convertPropertiesToMap(props);
125 }
126
127 /**
128 * Return the Map decorated by this configuration.
129 *
130 * @return the map this configuration is based onto
131 */
132 public Map<String, Object> getMap()
133 {
134 return map;
135 }
136
137 /**
138 * Returns the flag whether trimming of property values is disabled.
139 *
140 * @return <b>true</b> if trimming of property values is disabled;
141 * <b>false</b> otherwise
142 * @since 1.7
143 */
144 public boolean isTrimmingDisabled()
145 {
146 return trimmingDisabled;
147 }
148
149 /**
150 * Sets a flag whether trimming of property values is disabled. This flag is
151 * only evaluated if list splitting is enabled. Refer to the header comment
152 * for more information about list splitting and trimming.
153 *
154 * @param trimmingDisabled a flag whether trimming of property values should
155 * be disabled
156 * @since 1.7
157 */
158 public void setTrimmingDisabled(boolean trimmingDisabled)
159 {
160 this.trimmingDisabled = trimmingDisabled;
161 }
162
163 public Object getProperty(String key)
164 {
165 Object value = map.get(key);
166 if ((value instanceof String) && (!isDelimiterParsingDisabled()))
167 {
168 List<String> list = PropertyConverter.split((String) value, getListDelimiter(), !isTrimmingDisabled());
169 return list.size() > 1 ? list : list.get(0);
170 }
171 else
172 {
173 return value;
174 }
175 }
176
177 @Override
178 protected void addPropertyDirect(String key, Object value)
179 {
180 Object previousValue = getProperty(key);
181
182 if (previousValue == null)
183 {
184 map.put(key, value);
185 }
186 else if (previousValue instanceof List)
187 {
188 // the value is added to the existing list
189 // Note: This is problematic. See header comment!
190 ((List<Object>) previousValue).add(value);
191 }
192 else
193 {
194 // the previous value is replaced by a list containing the previous value and the new value
195 List<Object> list = new ArrayList<Object>();
196 list.add(previousValue);
197 list.add(value);
198
199 map.put(key, list);
200 }
201 }
202
203 public boolean isEmpty()
204 {
205 return map.isEmpty();
206 }
207
208 public boolean containsKey(String key)
209 {
210 return map.containsKey(key);
211 }
212
213 @Override
214 protected void clearPropertyDirect(String key)
215 {
216 map.remove(key);
217 }
218
219 public Iterator<String> getKeys()
220 {
221 return map.keySet().iterator();
222 }
223
224 /**
225 * Returns a copy of this object. The returned configuration will contain
226 * the same properties as the original. Event listeners are not cloned.
227 *
228 * @return the copy
229 * @since 1.3
230 */
231 @Override
232 public Object clone()
233 {
234 try
235 {
236 MapConfiguration copy = (MapConfiguration) super.clone();
237 copy.clearConfigurationListeners();
238 // Safe because ConfigurationUtils returns a map of the same types.
239 @SuppressWarnings("unchecked")
240 Map<String, Object> clonedMap = (Map<String, Object>) ConfigurationUtils.clone(map);
241 copy.map = clonedMap;
242 return copy;
243 }
244 catch (CloneNotSupportedException cex)
245 {
246 // cannot happen
247 throw new ConfigurationRuntimeException(cex);
248 }
249 }
250
251 /**
252 * Helper method for copying all string keys from the given
253 * {@code Properties} object to a newly created map.
254 *
255 * @param props the {@code Properties} to be copied
256 * @return a newly created map with all string keys of the properties
257 */
258 private static Map<String, Object> convertPropertiesToMap(Properties props)
259 {
260 Map<String, Object> map = new HashMap<String, Object>();
261 for (Map.Entry<Object, Object> e : props.entrySet())
262 {
263 if (e.getKey() instanceof String)
264 {
265 map.put((String) e.getKey(), e.getValue());
266 }
267 }
268 return map;
269 }
270 }