    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}