    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    /**
    080     * Helper method for converting the type of the {@code Properties} object to a supported map type. As stated by the
    081     * comment of the constructor, we expect the {@code Properties} object to contain only String key; therefore, it is safe
    082     * to do this cast.
    083     *
    084     * @param props the {@code Properties} to be copied.
    085     * @return a newly created map with all string keys of the properties.
    086     */
    087    @SuppressWarnings("unchecked")
    088    private static Map<String, Object> toMap(final Properties props) {
    089        @SuppressWarnings("rawtypes")
    090        final Map map = props;
    091        return map;
    092    }
    093
    094    /** The Map decorated by this configuration. */
    095    protected Map<String, Object> map;
    096
    097    /** A flag whether trimming of property values should be disabled. */
    098    private boolean trimmingDisabled;
    099
    100    /**
    101     * Create a Configuration decorator around the specified Map. The map is used to store the configuration properties, any
    102     * change will also affect the Map.
    103     *
    104     * @param map the map.
    105     */
    106    public MapConfiguration(final Map<String, ?> map) {
    107        this.map = (Map<String, Object>) Objects.requireNonNull(map, "map");
    108    }
    109
    110    /**
    111     * Creates a new instance of {@code MapConfiguration} which uses the specified {@code Properties} object as its data
    112     * store. All changes of this configuration affect the given {@code Properties} object and vice versa. Note that while
    113     * {@code Properties} actually implements {@code Map<Object, Object>}, we expect it to contain only string keys. Other
    114     * key types will lead to {@code ClassCastException} exceptions on certain methods.
    115     *
    116     * @param props the {@code Properties} object defining the content of this configuration.
    117     * @since 1.8
    118     */
    119    public MapConfiguration(final Properties props) {
    120        map = toMap(Objects.requireNonNull(props));
    121    }
    122
    123    @Override
    124    protected void addPropertyDirect(final String key, final Object value) {
    125        final Object previousValue = getProperty(key);
    126
    127        if (previousValue == null) {
    128            map.put(key, value);
    129        } else if (previousValue instanceof List) {
    130            // the value is added to the existing list
    131            // Note: This is problematic. See header comment!
    132            ((List<Object>) previousValue).add(value);
    133        } else {
    134            // the previous value is replaced by a list containing the previous value and the new value
    135            final List<Object> list = new ArrayList<>();
    136            list.add(previousValue);
    137            list.add(value);
    138
    139            map.put(key, list);
    140        }
    141    }
    142
    143    @Override
    144    protected void clearPropertyDirect(final String key) {
    145        map.remove(key);
    146    }
    147
    148    /**
    149     * Returns a copy of this object. The returned configuration will contain the same properties as the original. Event 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            copy.map = ConfigurationUtils.clone(map);
    159            copy.cloneInterpolator(this);
    160            return copy;
    161        } catch (final CloneNotSupportedException cex) {
    162            // cannot happen
    163            throw new ConfigurationRuntimeException(cex);
    164        }
    165    }
    166
    167    @Override
    168    protected boolean containsKeyInternal(final String key) {
    169        return map.containsKey(key);
    170    }
    171
    172    /**
    173     * Tests whether this configuration contains one or more matches to this value. This operation stops at first match
    174     * but may be more expensive than the containsKey method.
    175     *
    176     * @since 2.11.0
    177     */
    178    @Override
    179    protected boolean containsValueInternal(final Object value) {
    180        return value != null && map.containsValue(value);
    181    }
    182
    183    @Override
    184    protected Iterator<String> getKeysInternal() {
    185        return map.keySet().iterator();
    186    }
    187
    188    /**
    189     * Gets the Map decorated by this configuration.
    190     *
    191     * @return the map this configuration is based onto.
    192     */
    193    public Map<String, Object> getMap() {
    194        return map;
    195    }
    196
    197    @Override
    198    protected Object getPropertyInternal(final String key) {
    199        final Object value = map.get(key);
    200        if (value instanceof String) {
    201            final Collection<String> list = getListDelimiterHandler().split((String) value, !isTrimmingDisabled());
    202            return list.size() > 1 ? list : list.iterator().next();
    203        }
    204        return value;
    205    }
    206
    207    @Override
    208    protected boolean isEmptyInternal() {
    209        return map.isEmpty();
    210    }
    211
    212    /**
    213     * Tests whether the flag whether trimming of property values is disabled.
    214     *
    215     * @return <strong>true</strong> if trimming of property values is disabled; <strong>false</strong> otherwise.
    216     * @since 1.7
    217     */
    218    public boolean isTrimmingDisabled() {
    219        return trimmingDisabled;
    220    }
    221
    222    /**
    223     * Sets a flag whether trimming of property values is disabled. This flag is only evaluated if list splitting is
    224     * enabled. Refer to the header comment for more information about list splitting and trimming.
    225     *
    226     * @param trimmingDisabled a flag whether trimming of property values should be disabled.
    227     * @since 1.7
    228     */
    229    public void setTrimmingDisabled(final boolean trimmingDisabled) {
    230        this.trimmingDisabled = trimmingDisabled;
    231    }
    232
    233    @Override
    234    protected int sizeInternal() {
    235        return map.size();
    236    }
    237
    238    /**
    239     * Converts this object to a String suitable for debugging and logging.
    240     *
    241     * @since 2.3
    242     */
    243    @Override
    244    public String toString() {
    245        return getClass().getSimpleName() + " [map=" + map + ", trimmingDisabled=" + trimmingDisabled + "]";
    246    }
    247}