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 1836914 2018-07-28 14:53:07Z oheger $
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(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(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(boolean trimmingDisabled)
161    {
162        this.trimmingDisabled = trimmingDisabled;
163    }
164
165    @Override
166    protected Object getPropertyInternal(String key)
167    {
168        Object value = map.get(key);
169        if (value instanceof String)
170        {
171            Collection<String> list = getListDelimiterHandler().split((String) value, !isTrimmingDisabled());
172            return list.size() > 1 ? list : list.iterator().next();
173        }
174        else
175        {
176            return value;
177        }
178    }
179
180    @Override
181    protected void addPropertyDirect(String key, Object value)
182    {
183        Object previousValue = getProperty(key);
184
185        if (previousValue == null)
186        {
187            map.put(key, value);
188        }
189        else if (previousValue instanceof List)
190        {
191            // the value is added to the existing list
192            // Note: This is problematic. See header comment!
193            ((List<Object>) previousValue).add(value);
194        }
195        else
196        {
197            // the previous value is replaced by a list containing the previous value and the new value
198            List<Object> list = new ArrayList<>();
199            list.add(previousValue);
200            list.add(value);
201
202            map.put(key, list);
203        }
204    }
205
206    @Override
207    protected boolean isEmptyInternal()
208    {
209        return map.isEmpty();
210    }
211
212    @Override
213    protected boolean containsKeyInternal(String key)
214    {
215        return map.containsKey(key);
216    }
217
218    @Override
219    protected void clearPropertyDirect(String key)
220    {
221        map.remove(key);
222    }
223
224    @Override
225    protected Iterator<String> getKeysInternal()
226    {
227        return map.keySet().iterator();
228    }
229
230    @Override
231    protected int sizeInternal()
232    {
233        return map.size();
234    }
235
236    /**
237     * Returns a copy of this object. The returned configuration will contain
238     * the same properties as the original. Event listeners are not cloned.
239     *
240     * @return the copy
241     * @since 1.3
242     */
243    @Override
244    public Object clone()
245    {
246        try
247        {
248            MapConfiguration copy = (MapConfiguration) super.clone();
249            // Safe because ConfigurationUtils returns a map of the same types.
250            @SuppressWarnings("unchecked")
251            Map<String, Object> clonedMap = (Map<String, Object>) ConfigurationUtils.clone(map);
252            copy.map = clonedMap;
253            copy.cloneInterpolator(this);
254            return copy;
255        }
256        catch (CloneNotSupportedException cex)
257        {
258            // cannot happen
259            throw new ConfigurationRuntimeException(cex);
260        }
261    }
262
263    /**
264     * Helper method for converting the type of the {@code Properties} object
265     * to a supported map type. As stated by the comment of the constructor,
266     * we expect the {@code Properties} object to contain only String key;
267     * therefore, it is safe to do this cast.
268     *
269     * @param props the {@code Properties} to be copied
270     * @return a newly created map with all string keys of the properties
271     */
272    @SuppressWarnings("unchecked")
273    private static Map<String, Object> convertPropertiesToMap(Properties props)
274    {
275        @SuppressWarnings("rawtypes")
276        Map map = props;
277        return map;
278    }
279
280    /**
281     * Converts this object to a String suitable for debugging and logging.
282     *
283     * @since 2.3
284     */
285    @Override
286    public String toString()
287    {
288        return getClass().getSimpleName() + " [map=" + map + ", trimmingDisabled=" + trimmingDisabled + "]";
289    }
290}