    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}