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 * @version $Id: MapConfiguration.java 1842194 2018-09-27 22:24:23Z ggregory $
089 * @since 1.1
090 */
091public class MapConfiguration extends AbstractConfiguration implements Cloneable
092{
093    /** The Map decorated by this configuration. */
094    protected Map<String, Object> map;
095
096    /** A flag whether trimming of property values should be disabled.*/
097    private boolean trimmingDisabled;
098
099    /**
100     * Create a Configuration decorator around the specified Map. The map is
101     * used to store the configuration properties, any change will also affect
102     * the Map.
103     *
104     * @param map the map
105     */
106    public MapConfiguration(final Map<String, ?> map)
107    {
108        this.map = (Map<String, Object>) map;
109    }
110
111    /**
112     * Creates a new instance of {@code MapConfiguration} which uses the
113     * specified {@code Properties} object as its data store. All changes of
114     * this configuration affect the given {@code Properties} object and
115     * vice versa. Note that while {@code Properties} actually
116     * implements {@code Map<Object, Object>}, we expect it to contain only
117     * string keys. Other key types will lead to {@code ClassCastException}
118     * exceptions on certain methods.
119     *
120     * @param props the {@code Properties} object defining the content of this
121     *        configuration
122     * @since 1.8
123     */
124    public MapConfiguration(final Properties props)
125    {
126        map = convertPropertiesToMap(props);
127    }
128
129    /**
130     * Return the Map decorated by this configuration.
131     *
132     * @return the map this configuration is based onto
133     */
134    public Map<String, Object> getMap()
135    {
136        return map;
137    }
138
139    /**
140     * Returns the flag whether trimming of property values is disabled.
141     *
142     * @return <b>true</b> if trimming of property values is disabled;
143     *         <b>false</b> otherwise
144     * @since 1.7
145     */
146    public boolean isTrimmingDisabled()
147    {
148        return trimmingDisabled;
149    }
150
151    /**
152     * Sets a flag whether trimming of property values is disabled. This flag is
153     * only evaluated if list splitting is enabled. Refer to the header comment
154     * for more information about list splitting and trimming.
155     *
156     * @param trimmingDisabled a flag whether trimming of property values should
157     *        be disabled
158     * @since 1.7
159     */
160    public void setTrimmingDisabled(final boolean trimmingDisabled)
161    {
162        this.trimmingDisabled = trimmingDisabled;
163    }
164
165    @Override
166    protected Object getPropertyInternal(final String key)
167    {
168        final Object value = map.get(key);
169        if (value instanceof String)
170        {
171            final Collection<String> list = getListDelimiterHandler().split((String) value, !isTrimmingDisabled());
172            return list.size() > 1 ? list : list.iterator().next();
173        }
174        return value;
175    }
176
177    @Override
178    protected void addPropertyDirect(final String key, final Object value)
179    {
180        final 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            final List<Object> list = new ArrayList<>();
196            list.add(previousValue);
197            list.add(value);
198
199            map.put(key, list);
200        }
201    }
202
203    @Override
204    protected boolean isEmptyInternal()
205    {
206        return map.isEmpty();
207    }
208
209    @Override
210    protected boolean containsKeyInternal(final String key)
211    {
212        return map.containsKey(key);
213    }
214
215    @Override
216    protected void clearPropertyDirect(final String key)
217    {
218        map.remove(key);
219    }
220
221    @Override
222    protected Iterator<String> getKeysInternal()
223    {
224        return map.keySet().iterator();
225    }
226
227    @Override
228    protected int sizeInternal()
229    {
230        return map.size();
231    }
232
233    /**
234     * Returns a copy of this object. The returned configuration will contain
235     * the same properties as the original. Event listeners are not cloned.
236     *
237     * @return the copy
238     * @since 1.3
239     */
240    @Override
241    public Object clone()
242    {
243        try
244        {
245            final MapConfiguration copy = (MapConfiguration) super.clone();
246            // Safe because ConfigurationUtils returns a map of the same types.
247            @SuppressWarnings("unchecked")
248            final
249            Map<String, Object> clonedMap = (Map<String, Object>) ConfigurationUtils.clone(map);
250            copy.map = clonedMap;
251            copy.cloneInterpolator(this);
252            return copy;
253        }
254        catch (final CloneNotSupportedException cex)
255        {
256            // cannot happen
257            throw new ConfigurationRuntimeException(cex);
258        }
259    }
260
261    /**
262     * Helper method for converting the type of the {@code Properties} object
263     * to a supported map type. As stated by the comment of the constructor,
264     * we expect the {@code Properties} object to contain only String key;
265     * therefore, it is safe to do this cast.
266     *
267     * @param props the {@code Properties} to be copied
268     * @return a newly created map with all string keys of the properties
269     */
270    @SuppressWarnings("unchecked")
271    private static Map<String, Object> convertPropertiesToMap(final Properties props)
272    {
273        @SuppressWarnings("rawtypes")
274        final
275        Map map = props;
276        return map;
277    }
278
279    /**
280     * Converts this object to a String suitable for debugging and logging.
281     *
282     * @since 2.3
283     */
284    @Override
285    public String toString()
286    {
287        return getClass().getSimpleName() + " [map=" + map + ", trimmingDisabled=" + trimmingDisabled + "]";
288    }
289}