001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.configuration2;
019
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.Iterator;
023import java.util.List;
024import java.util.Map;
025import java.util.Properties;
026
027import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
028
029/**
030 * <p>
031 * A Map based Configuration.
032 * </p>
033 * <p>
034 * This implementation of the {@code Configuration} interface is
035 * initialized with a {@code java.util.Map}. The methods of the
036 * {@code Configuration} interface are implemented on top of the content of
037 * this map. The following storage scheme is used:
038 * </p>
039 * <p>
040 * Property keys are directly mapped to map keys, i.e. the
041 * {@code getProperty()} method directly performs a {@code get()} on
042 * the map. Analogously, {@code setProperty()} or
043 * {@code addProperty()} operations write new data into the map. If a value
044 * is added to an existing property, a {@code java.util.List} is created,
045 * which stores the values of this property.
046 * </p>
047 * <p>
048 * An important use case of this class is to treat a map as a
049 * {@code Configuration} allowing access to its data through the richer
050 * interface. This can be a bit problematic in some cases because the map may
051 * contain values that need not adhere to the default storage scheme used by
052 * typical configuration implementations, e.g. regarding lists. In such cases
053 * care must be taken when manipulating the data through the
054 * {@code Configuration} interface, e.g. by calling
055 * {@code addProperty()}; results may be different than expected.
056 * </p>
057 * <p>
058 * The handling of list delimiters is a bit different for this configuration
059 * implementation: When a property of type String is queried, it is passed to
060 * the current {@link org.apache.commons.configuration2.convert.ListDelimiterHandler
061 * ListDelimiterHandler} which may generate multiple values.
062 * Note that per default a list delimiter handler is set which does not do any
063 * list splitting, so this feature is disabled. It can be enabled by setting
064 * a properly configured {@code ListDelimiterHandler} implementation, e.g. a
065 * {@link org.apache.commons.configuration2.convert.DefaultListDelimiterHandler
066 * DefaultListDelimiterHandler} object.
067 * </p>
068 * <p>
069 * Notice that list splitting is only performed for single string values. If a
070 * property has multiple values, the single values are not split even if they
071 * contain the list delimiter character.
072 * </p>
073 * <p>
074 * As the underlying {@code Map} is directly used as store of the property
075 * values, the thread-safety of this {@code Configuration} implementation
076 * depends on the map passed to the constructor.
077 * </p>
078 * <p>
079 * Notes about type safety: For properties with multiple values this implementation
080 * creates lists of type {@code Object} and stores them. If a property is assigned
081 * another value, the value is added to the list. This can cause problems if the
082 * map passed to the constructor already contains lists of other types. This
083 * should be avoided, otherwise it cannot be guaranteed that the application
084 * might throw {@code ClassCastException} exceptions later.
085 * </p>
086 *
087 * @author Emmanuel Bourg
088 * @since 1.1
089 */
090public class MapConfiguration extends AbstractConfiguration implements Cloneable
091{
092    /** The Map decorated by this configuration. */
093    protected Map<String, Object> map;
094
095    /** A flag whether trimming of property values should be disabled.*/
096    private boolean trimmingDisabled;
097
098    /**
099     * 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(final Map<String, ?> map)
106    {
107        this.map = (Map<String, Object>) map;
108    }
109
110    /**
111     * Creates a new instance of {@code MapConfiguration} which uses the
112     * specified {@code Properties} object as its data store. All changes of
113     * this configuration affect the given {@code Properties} object and
114     * vice versa. Note that while {@code Properties} actually
115     * implements {@code Map<Object, Object>}, we expect it to contain only
116     * string keys. Other key types will lead to {@code ClassCastException}
117     * exceptions on certain methods.
118     *
119     * @param props the {@code Properties} object defining the content of this
120     *        configuration
121     * @since 1.8
122     */
123    public MapConfiguration(final Properties props)
124    {
125        map = convertPropertiesToMap(props);
126    }
127
128    /**
129     * Return the Map decorated by this configuration.
130     *
131     * @return the map this configuration is based onto
132     */
133    public Map<String, Object> getMap()
134    {
135        return map;
136    }
137
138    /**
139     * Returns the flag whether trimming of property values is disabled.
140     *
141     * @return <b>true</b> if trimming of property values is disabled;
142     *         <b>false</b> otherwise
143     * @since 1.7
144     */
145    public boolean isTrimmingDisabled()
146    {
147        return trimmingDisabled;
148    }
149
150    /**
151     * Sets a flag whether trimming of property values is disabled. This flag is
152     * only evaluated if list splitting is enabled. Refer to the header comment
153     * for more information about list splitting and trimming.
154     *
155     * @param trimmingDisabled a flag whether trimming of property values should
156     *        be disabled
157     * @since 1.7
158     */
159    public void setTrimmingDisabled(final boolean trimmingDisabled)
160    {
161        this.trimmingDisabled = trimmingDisabled;
162    }
163
164    @Override
165    protected Object getPropertyInternal(final String key)
166    {
167        final Object value = map.get(key);
168        if (value instanceof String)
169        {
170            final Collection<String> list = getListDelimiterHandler().split((String) value, !isTrimmingDisabled());
171            return list.size() > 1 ? list : list.iterator().next();
172        }
173        return value;
174    }
175
176    @Override
177    protected void addPropertyDirect(final String key, final Object value)
178    {
179        final Object previousValue = getProperty(key);
180
181        if (previousValue == null)
182        {
183            map.put(key, value);
184        }
185        else if (previousValue instanceof List)
186        {
187            // the value is added to the existing list
188            // Note: This is problematic. See header comment!
189            ((List<Object>) previousValue).add(value);
190        }
191        else
192        {
193            // the previous value is replaced by a list containing the previous value and the new value
194            final List<Object> list = new ArrayList<>();
195            list.add(previousValue);
196            list.add(value);
197
198            map.put(key, list);
199        }
200    }
201
202    @Override
203    protected boolean isEmptyInternal()
204    {
205        return map.isEmpty();
206    }
207
208    @Override
209    protected boolean containsKeyInternal(final String key)
210    {
211        return map.containsKey(key);
212    }
213
214    @Override
215    protected void clearPropertyDirect(final String key)
216    {
217        map.remove(key);
218    }
219
220    @Override
221    protected Iterator<String> getKeysInternal()
222    {
223        return map.keySet().iterator();
224    }
225
226    @Override
227    protected int sizeInternal()
228    {
229        return map.size();
230    }
231
232    /**
233     * Returns a copy of this object. The returned configuration will contain
234     * the same properties as the original. Event listeners are not cloned.
235     *
236     * @return the copy
237     * @since 1.3
238     */
239    @Override
240    public Object clone()
241    {
242        try
243        {
244            final MapConfiguration copy = (MapConfiguration) super.clone();
245            // Safe because ConfigurationUtils returns a map of the same types.
246            @SuppressWarnings("unchecked")
247            final
248            Map<String, Object> clonedMap = (Map<String, Object>) ConfigurationUtils.clone(map);
249            copy.map = clonedMap;
250            copy.cloneInterpolator(this);
251            return copy;
252        }
253        catch (final CloneNotSupportedException cex)
254        {
255            // cannot happen
256            throw new ConfigurationRuntimeException(cex);
257        }
258    }
259
260    /**
261     * Helper method for converting the type of the {@code Properties} object
262     * to a supported map type. As stated by the comment of the constructor,
263     * we expect the {@code Properties} object to contain only String key;
264     * therefore, it is safe to do this cast.
265     *
266     * @param props the {@code Properties} to be copied
267     * @return a newly created map with all string keys of the properties
268     */
269    @SuppressWarnings("unchecked")
270    private static Map<String, Object> convertPropertiesToMap(final Properties props)
271    {
272        @SuppressWarnings("rawtypes")
273        final
274        Map map = props;
275        return map;
276    }
277
278    /**
279     * Converts this object to a String suitable for debugging and logging.
280     *
281     * @since 2.3
282     */
283    @Override
284    public String toString()
285    {
286        return getClass().getSimpleName() + " [map=" + map + ", trimmingDisabled=" + trimmingDisabled + "]";
287    }
288}