    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 *     https://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 listeners are not cloned.
    149     *
    150     * @return the copy.
    151     * @since 1.3
    152     */
    153    @Override
    154    public Object clone() {
    155        try {
    156            final MapConfiguration copy = (MapConfiguration) super.clone();
    157            copy.map = ConfigurationUtils.clone(map);
    158            copy.cloneInterpolator(this);
    159            return copy;
    160        } catch (final CloneNotSupportedException cex) {
    161            // cannot happen
    162            throw new ConfigurationRuntimeException(cex);
    163        }
    164    }
    165
    166    @Override
    167    protected boolean containsKeyInternal(final String key) {
    168        return map.containsKey(key);
    169    }
    170
    171    /**
    172     * Tests whether this configuration contains one or more matches to this value. This operation stops at first match
    173     * but may be more expensive than the containsKey method.
    174     *
    175     * @since 2.11.0
    176     */
    177    @Override
    178    protected boolean containsValueInternal(final Object value) {
    179        return value != null && map.containsValue(value);
    180    }
    181
    182    @Override
    183    protected Iterator<String> getKeysInternal() {
    184        return map.keySet().iterator();
    185    }
    186
    187    /**
    188     * Gets the Map decorated by this configuration.
    189     *
    190     * @return the map this configuration is based onto.
    191     */
    192    public Map<String, Object> getMap() {
    193        return map;
    194    }
    195
    196    @Override
    197    protected Object getPropertyInternal(final String key) {
    198        final Object value = map.get(key);
    199        if (value instanceof String) {
    200            final Collection<String> list = getListDelimiterHandler().split((String) value, !isTrimmingDisabled());
    201            return list.size() > 1 ? list : list.iterator().next();
    202        }
    203        return value;
    204    }
    205
    206    @Override
    207    protected boolean isEmptyInternal() {
    208        return map.isEmpty();
    209    }
    210
    211    /**
    212     * Tests whether the flag whether trimming of property values is disabled.
    213     *
    214     * @return <strong>true</strong> if trimming of property values is disabled; <strong>false</strong> otherwise.
    215     * @since 1.7
    216     */
    217    public boolean isTrimmingDisabled() {
    218        return trimmingDisabled;
    219    }
    220
    221    /**
    222     * Sets a flag whether trimming of property values is disabled. This flag is only evaluated if list splitting is
    223     * enabled. Refer to the header comment for more information about list splitting and trimming.
    224     *
    225     * @param trimmingDisabled a flag whether trimming of property values should be disabled.
    226     * @since 1.7
    227     */
    228    public void setTrimmingDisabled(final boolean trimmingDisabled) {
    229        this.trimmingDisabled = trimmingDisabled;
    230    }
    231
    232    @Override
    233    protected int sizeInternal() {
    234        return map.size();
    235    }
    236
    237    /**
    238     * Converts this object to a String suitable for debugging and logging.
    239     *
    240     * @since 2.3
    241     */
    242    @Override
    243    public String toString() {
    244        return getClass().getSimpleName() + " [map=" + map + ", trimmingDisabled=" + trimmingDisabled + "]";
    245    }
    246}