    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 initialized with a {@code java.util.Map}. The methods
    035 * of the {@code Configuration} interface are implemented on top of the content of this map. The following storage
    036 * scheme is used:
    037 * </p>
    038 * <p>
    039 * Property keys are directly mapped to map keys, i.e. the {@code getProperty()} method directly performs a
    040 * {@code get()} on the map. Analogously, {@code setProperty()} or {@code addProperty()} operations write new data into
    041 * the map. If a value is added to an existing property, a {@code java.util.List} is created, which stores the values of
    042 * this property.
    043 * </p>
    044 * <p>
    045 * An important use case of this class is to treat a map as a {@code Configuration} allowing access to its data through
    046 * the richer interface. This can be a bit problematic in some cases because the map may contain values that need not
    047 * adhere to the default storage scheme used by typical configuration implementations, e.g. regarding lists. In such
    048 * cases care must be taken when manipulating the data through the {@code Configuration} interface, e.g. by calling
    049 * {@code addProperty()}; results may be different than expected.
    050 * </p>
    051 * <p>
    052 * The handling of list delimiters is a bit different for this configuration implementation: When a property of type
    053 * String is queried, it is passed to the current {@link org.apache.commons.configuration2.convert.ListDelimiterHandler
    054 * ListDelimiterHandler} which may generate multiple values. Note that per default a list delimiter handler is set which
    055 * does not do any list splitting, so this feature is disabled. It can be enabled by setting a properly configured
    056 * {@code ListDelimiterHandler} implementation, e.g. a
    057 * {@link org.apache.commons.configuration2.convert.DefaultListDelimiterHandler DefaultListDelimiterHandler} object.
    058 * </p>
    059 * <p>
    060 * Notice that list splitting is only performed for single string values. If a property has multiple values, the single
    061 * values are not split even if they contain the list delimiter character.
    062 * </p>
    063 * <p>
    064 * As the underlying {@code Map} is directly used as store of the property values, the thread-safety of this
    065 * {@code Configuration} implementation depends on the map passed to the constructor.
    066 * </p>
    067 * <p>
    068 * Notes about type safety: For properties with multiple values this implementation creates lists of type {@code Object}
    069 * and stores them. If a property is assigned another value, the value is added to the list. This can cause problems if
    070 * the map passed to the constructor already contains lists of other types. This should be avoided, otherwise it cannot
    071 * be guaranteed that the application might throw {@code ClassCastException} exceptions later.
    072 * </p>
    073 *
    074 * @since 1.1
    075 */
    076public class MapConfiguration extends AbstractConfiguration implements Cloneable {
    077    /** The Map decorated by this configuration. */
    078    protected Map<String, Object> map;
    079
    080    /** A flag whether trimming of property values should be disabled. */
    081    private boolean trimmingDisabled;
    082
    083    /**
    084     * Create a Configuration decorator around the specified Map. The map is used to store the configuration properties, any
    085     * change will also affect the Map.
    086     *
    087     * @param map the map
    088     */
    089    public MapConfiguration(final Map<String, ?> map) {
    090        this.map = (Map<String, Object>) map;
    091    }
    092
    093    /**
    094     * Creates a new instance of {@code MapConfiguration} which uses the specified {@code Properties} object as its data
    095     * store. All changes of this configuration affect the given {@code Properties} object and vice versa. Note that while
    096     * {@code Properties} actually implements {@code Map<Object, Object>}, we expect it to contain only string keys. Other
    097     * key types will lead to {@code ClassCastException} exceptions on certain methods.
    098     *
    099     * @param props the {@code Properties} object defining the content of this configuration
    100     * @since 1.8
    101     */
    102    public MapConfiguration(final Properties props) {
    103        map = convertPropertiesToMap(props);
    104    }
    105
    106    /**
    107     * Return the Map decorated by this configuration.
    108     *
    109     * @return the map this configuration is based onto
    110     */
    111    public Map<String, Object> getMap() {
    112        return map;
    113    }
    114
    115    /**
    116     * Returns the flag whether trimming of property values is disabled.
    117     *
    118     * @return <b>true</b> if trimming of property values is disabled; <b>false</b> otherwise
    119     * @since 1.7
    120     */
    121    public boolean isTrimmingDisabled() {
    122        return trimmingDisabled;
    123    }
    124
    125    /**
    126     * Sets a flag whether trimming of property values is disabled. This flag is only evaluated if list splitting is
    127     * enabled. Refer to the header comment for more information about list splitting and trimming.
    128     *
    129     * @param trimmingDisabled a flag whether trimming of property values should be disabled
    130     * @since 1.7
    131     */
    132    public void setTrimmingDisabled(final boolean trimmingDisabled) {
    133        this.trimmingDisabled = trimmingDisabled;
    134    }
    135
    136    @Override
    137    protected Object getPropertyInternal(final String key) {
    138        final Object value = map.get(key);
    139        if (value instanceof String) {
    140            final Collection<String> list = getListDelimiterHandler().split((String) value, !isTrimmingDisabled());
    141            return list.size() > 1 ? list : list.iterator().next();
    142        }
    143        return value;
    144    }
    145
    146    @Override
    147    protected void addPropertyDirect(final String key, final Object value) {
    148        final Object previousValue = getProperty(key);
    149
    150        if (previousValue == null) {
    151            map.put(key, value);
    152        } else if (previousValue instanceof List) {
    153            // the value is added to the existing list
    154            // Note: This is problematic. See header comment!
    155            ((List<Object>) previousValue).add(value);
    156        } else {
    157            // the previous value is replaced by a list containing the previous value and the new value
    158            final List<Object> list = new ArrayList<>();
    159            list.add(previousValue);
    160            list.add(value);
    161
    162            map.put(key, list);
    163        }
    164    }
    165
    166    @Override
    167    protected boolean isEmptyInternal() {
    168        return map.isEmpty();
    169    }
    170
    171    @Override
    172    protected boolean containsKeyInternal(final String key) {
    173        return map.containsKey(key);
    174    }
    175
    176    @Override
    177    protected void clearPropertyDirect(final String key) {
    178        map.remove(key);
    179    }
    180
    181    @Override
    182    protected Iterator<String> getKeysInternal() {
    183        return map.keySet().iterator();
    184    }
    185
    186    @Override
    187    protected int sizeInternal() {
    188        return map.size();
    189    }
    190
    191    /**
    192     * Returns a copy of this object. The returned configuration will contain the same properties as the original. Event
    193     * listeners are not cloned.
    194     *
    195     * @return the copy
    196     * @since 1.3
    197     */
    198    @Override
    199    public Object clone() {
    200        try {
    201            final MapConfiguration copy = (MapConfiguration) super.clone();
    202            // Safe because ConfigurationUtils returns a map of the same types.
    203            @SuppressWarnings("unchecked")
    204            final Map<String, Object> clonedMap = (Map<String, Object>) ConfigurationUtils.clone(map);
    205            copy.map = clonedMap;
    206            copy.cloneInterpolator(this);
    207            return copy;
    208        } catch (final CloneNotSupportedException cex) {
    209            // cannot happen
    210            throw new ConfigurationRuntimeException(cex);
    211        }
    212    }
    213
    214    /**
    215     * Helper method for converting the type of the {@code Properties} object to a supported map type. As stated by the
    216     * comment of the constructor, we expect the {@code Properties} object to contain only String key; therefore, it is safe
    217     * to do this cast.
    218     *
    219     * @param props the {@code Properties} to be copied
    220     * @return a newly created map with all string keys of the properties
    221     */
    222    @SuppressWarnings("unchecked")
    223    private static Map<String, Object> convertPropertiesToMap(final Properties props) {
    224        @SuppressWarnings("rawtypes")
    225        final Map map = props;
    226        return map;
    227    }
    228
    229    /**
    230     * Converts this object to a String suitable for debugging and logging.
    231     *
    232     * @since 2.3
    233     */
    234    @Override
    235    public String toString() {
    236        return getClass().getSimpleName() + " [map=" + map + ", trimmingDisabled=" + trimmingDisabled + "]";
    237    }
    238}