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 * @since 1.1
088 */
089public class MapConfiguration extends AbstractConfiguration implements Cloneable
090{
091    /** The Map decorated by this configuration. */
092    protected Map<String, Object> map;
093
094    /** A flag whether trimming of property values should be disabled.*/
095    private boolean trimmingDisabled;
096
097    /**
098     * Create a Configuration decorator around the specified Map. The map is
099     * used to store the configuration properties, any change will also affect
100     * the Map.
101     *
102     * @param map the map
103     */
104    public MapConfiguration(final Map<String, ?> map)
105    {
106        this.map = (Map<String, Object>) map;
107    }
108
109    /**
110     * Creates a new instance of {@code MapConfiguration} which uses the
111     * specified {@code Properties} object as its data store. All changes of
112     * this configuration affect the given {@code Properties} object and
113     * vice versa. Note that while {@code Properties} actually
114     * implements {@code Map<Object, Object>}, we expect it to contain only
115     * string keys. Other key types will lead to {@code ClassCastException}
116     * exceptions on certain methods.
117     *
118     * @param props the {@code Properties} object defining the content of this
119     *        configuration
120     * @since 1.8
121     */
122    public MapConfiguration(final 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(final boolean trimmingDisabled)
159    {
160        this.trimmingDisabled = trimmingDisabled;
161    }
162
163    @Override
164    protected Object getPropertyInternal(final String key)
165    {
166        final Object value = map.get(key);
167        if (value instanceof String)
168        {
169            final Collection<String> list = getListDelimiterHandler().split((String) value, !isTrimmingDisabled());
170            return list.size() > 1 ? list : list.iterator().next();
171        }
172        return value;
173    }
174
175    @Override
176    protected void addPropertyDirect(final String key, final Object value)
177    {
178        final Object previousValue = getProperty(key);
179
180        if (previousValue == null)
181        {
182            map.put(key, value);
183        }
184        else if (previousValue instanceof List)
185        {
186            // the value is added to the existing list
187            // Note: This is problematic. See header comment!
188            ((List<Object>) previousValue).add(value);
189        }
190        else
191        {
192            // the previous value is replaced by a list containing the previous value and the new value
193            final List<Object> list = new ArrayList<>();
194            list.add(previousValue);
195            list.add(value);
196
197            map.put(key, list);
198        }
199    }
200
201    @Override
202    protected boolean isEmptyInternal()
203    {
204        return map.isEmpty();
205    }
206
207    @Override
208    protected boolean containsKeyInternal(final String key)
209    {
210        return map.containsKey(key);
211    }
212
213    @Override
214    protected void clearPropertyDirect(final String key)
215    {
216        map.remove(key);
217    }
218
219    @Override
220    protected Iterator<String> getKeysInternal()
221    {
222        return map.keySet().iterator();
223    }
224
225    @Override
226    protected int sizeInternal()
227    {
228        return map.size();
229    }
230
231    /**
232     * Returns a copy of this object. The returned configuration will contain
233     * the same properties as the original. Event listeners are not cloned.
234     *
235     * @return the copy
236     * @since 1.3
237     */
238    @Override
239    public Object clone()
240    {
241        try
242        {
243            final MapConfiguration copy = (MapConfiguration) super.clone();
244            // Safe because ConfigurationUtils returns a map of the same types.
245            @SuppressWarnings("unchecked")
246            final
247            Map<String, Object> clonedMap = (Map<String, Object>) ConfigurationUtils.clone(map);
248            copy.map = clonedMap;
249            copy.cloneInterpolator(this);
250            return copy;
251        }
252        catch (final CloneNotSupportedException cex)
253        {
254            // cannot happen
255            throw new ConfigurationRuntimeException(cex);
256        }
257    }
258
259    /**
260     * Helper method for converting the type of the {@code Properties} object
261     * to a supported map type. As stated by the comment of the constructor,
262     * we expect the {@code Properties} object to contain only String key;
263     * therefore, it is safe to do this cast.
264     *
265     * @param props the {@code Properties} to be copied
266     * @return a newly created map with all string keys of the properties
267     */
268    @SuppressWarnings("unchecked")
269    private static Map<String, Object> convertPropertiesToMap(final Properties props)
270    {
271        @SuppressWarnings("rawtypes")
272        final
273        Map map = props;
274        return map;
275    }
276
277    /**
278     * Converts this object to a String suitable for debugging and logging.
279     *
280     * @since 2.3
281     */
282    @Override
283    public String toString()
284    {
285        return getClass().getSimpleName() + " [map=" + map + ", trimmingDisabled=" + trimmingDisabled + "]";
286    }
287}