    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}