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