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;
19  
20  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Objects;
26  import java.util.Properties;
27  
28  import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
29  
30  /**
31   * <p>
32   * A Map based Configuration.
33   * </p>
34   * <p>
35   * This implementation of the {@code Configuration} interface is initialized with a {@link java.util.Map}. The methods
36   * of the {@code Configuration} interface are implemented on top of the content of this map. The following storage
37   * scheme is used:
38   * </p>
39   * <p>
40   * Property keys are directly mapped to map keys, i.e. the {@code getProperty()} method directly performs a
41   * {@code get()} on the map. Analogously, {@code setProperty()} or {@code addProperty()} operations write new data into
42   * the map. If a value is added to an existing property, a {@link java.util.List} is created, which stores the values of
43   * this property.
44   * </p>
45   * <p>
46   * An important use case of this class is to treat a map as a {@code Configuration} allowing access to its data through
47   * the richer interface. This can be a bit problematic in some cases because the map may contain values that need not
48   * adhere to the default storage scheme used by typical configuration implementations, for example regarding lists. In such
49   * cases care must be taken when manipulating the data through the {@code Configuration} interface, for example by calling
50   * {@code addProperty()}; results may be different than expected.
51   * </p>
52   * <p>
53   * The handling of list delimiters is a bit different for this configuration implementation: When a property of type
54   * String is queried, it is passed to the current {@link org.apache.commons.configuration2.convert.ListDelimiterHandler
55   * ListDelimiterHandler} which may generate multiple values. Note that per default a list delimiter handler is set which
56   * does not do any list splitting, so this feature is disabled. It can be enabled by setting a properly configured
57   * {@code ListDelimiterHandler} implementation, for example a
58   * {@link org.apache.commons.configuration2.convert.DefaultListDelimiterHandler DefaultListDelimiterHandler} object.
59   * </p>
60   * <p>
61   * Notice that list splitting is only performed for single string values. If a property has multiple values, the single
62   * values are not split even if they contain the list delimiter character.
63   * </p>
64   * <p>
65   * As the underlying {@code Map} is directly used as store of the property values, the thread-safety of this
66   * {@code Configuration} implementation depends on the map passed to the constructor.
67   * </p>
68   * <p>
69   * Notes about type safety: For properties with multiple values this implementation creates lists of type {@code Object}
70   * and stores them. If a property is assigned another value, the value is added to the list. This can cause problems if
71   * the map passed to the constructor already contains lists of other types. This should be avoided, otherwise it cannot
72   * be guaranteed that the application might throw {@code ClassCastException} exceptions later.
73   * </p>
74   *
75   * @since 1.1
76   */
77  public class MapConfiguration extends AbstractConfiguration implements Cloneable {
78      /**
79       * Helper method for converting the type of the {@code Properties} object to a supported map type. As stated by the
80       * comment of the constructor, we expect the {@code Properties} object to contain only String key; therefore, it is safe
81       * to do this cast.
82       *
83       * @param props the {@code Properties} to be copied
84       * @return a newly created map with all string keys of the properties
85       */
86      @SuppressWarnings("unchecked")
87      private static Map<String, Object> toMap(final Properties props) {
88          @SuppressWarnings("rawtypes")
89          final Map map = props;
90          return map;
91      }
92  
93      /** The Map decorated by this configuration. */
94      protected Map<String, Object> map;
95  
96      /** A flag whether trimming of property values should be disabled. */
97      private boolean trimmingDisabled;
98  
99      /**
100      * Create a Configuration decorator around the specified Map. The map is used to store the configuration properties, any
101      * change will also affect the Map.
102      *
103      * @param map the map
104      */
105     public MapConfiguration(final Map<String, ?> map) {
106         this.map = (Map<String, Object>) Objects.requireNonNull(map, "map");
107     }
108 
109     /**
110      * Creates a new instance of {@code MapConfiguration} which uses the specified {@code Properties} object as its data
111      * store. All changes of this configuration affect the given {@code Properties} object and vice versa. Note that while
112      * {@code Properties} actually implements {@code Map<Object, Object>}, we expect it to contain only string keys. Other
113      * key types will lead to {@code ClassCastException} exceptions on certain methods.
114      *
115      * @param props the {@code Properties} object defining the content of this configuration
116      * @since 1.8
117      */
118     public MapConfiguration(final Properties props) {
119         map = toMap(Objects.requireNonNull(props));
120     }
121 
122     @Override
123     protected void addPropertyDirect(final String key, final Object value) {
124         final Object previousValue = getProperty(key);
125 
126         if (previousValue == null) {
127             map.put(key, value);
128         } else if (previousValue instanceof List) {
129             // the value is added to the existing list
130             // Note: This is problematic. See header comment!
131             ((List<Object>) previousValue).add(value);
132         } else {
133             // the previous value is replaced by a list containing the previous value and the new value
134             final List<Object> list = new ArrayList<>();
135             list.add(previousValue);
136             list.add(value);
137 
138             map.put(key, list);
139         }
140     }
141 
142     @Override
143     protected void clearPropertyDirect(final String key) {
144         map.remove(key);
145     }
146 
147     /**
148      * Returns a copy of this object. The returned configuration will contain the same properties as the original. Event
149      * listeners are not cloned.
150      *
151      * @return the copy
152      * @since 1.3
153      */
154     @Override
155     public Object clone() {
156         try {
157             final MapConfiguration copy = (MapConfiguration) super.clone();
158             // Safe because ConfigurationUtils returns a map of the same types.
159             @SuppressWarnings("unchecked")
160             final Map<String, Object> clonedMap = (Map<String, Object>) ConfigurationUtils.clone(map);
161             copy.map = clonedMap;
162             copy.cloneInterpolator(this);
163             return copy;
164         } catch (final CloneNotSupportedException cex) {
165             // cannot happen
166             throw new ConfigurationRuntimeException(cex);
167         }
168     }
169 
170     @Override
171     protected boolean containsKeyInternal(final String key) {
172         return map.containsKey(key);
173     }
174 
175     /**
176      * Tests whether this configuration contains one or more matches to this value. This operation stops at first match
177      * but may be more expensive than the containsKey method.
178      * @since 2.11.0
179      */
180     @Override
181     protected boolean containsValueInternal(final Object value) {
182         return value != null && map.containsValue(value);
183     }
184 
185     @Override
186     protected Iterator<String> getKeysInternal() {
187         return map.keySet().iterator();
188     }
189 
190     /**
191      * Gets the Map decorated by this configuration.
192      *
193      * @return the map this configuration is based onto
194      */
195     public Map<String, Object> getMap() {
196         return map;
197     }
198 
199     @Override
200     protected Object getPropertyInternal(final String key) {
201         final Object value = map.get(key);
202         if (value instanceof String) {
203             final Collection<String> list = getListDelimiterHandler().split((String) value, !isTrimmingDisabled());
204             return list.size() > 1 ? list : list.iterator().next();
205         }
206         return value;
207     }
208 
209     @Override
210     protected boolean isEmptyInternal() {
211         return map.isEmpty();
212     }
213 
214     /**
215      * Returns the flag whether trimming of property values is disabled.
216      *
217      * @return <strong>true</strong> if trimming of property values is disabled; <strong>false</strong> otherwise
218      * @since 1.7
219      */
220     public boolean isTrimmingDisabled() {
221         return trimmingDisabled;
222     }
223 
224     /**
225      * Sets a flag whether trimming of property values is disabled. This flag is only evaluated if list splitting is
226      * enabled. Refer to the header comment for more information about list splitting and trimming.
227      *
228      * @param trimmingDisabled a flag whether trimming of property values should be disabled
229      * @since 1.7
230      */
231     public void setTrimmingDisabled(final boolean trimmingDisabled) {
232         this.trimmingDisabled = trimmingDisabled;
233     }
234 
235     @Override
236     protected int sizeInternal() {
237         return map.size();
238     }
239 
240     /**
241      * Converts this object to a String suitable for debugging and logging.
242      *
243      * @since 2.3
244      */
245     @Override
246     public String toString() {
247         return getClass().getSimpleName() + " [map=" + map + ", trimmingDisabled=" + trimmingDisabled + "]";
248     }
249 }