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.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 }