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