    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
    018    package org.apache.commons.configuration;
    019
    020    import java.util.ArrayList;
    021    import java.util.HashMap;
    022    import java.util.Iterator;
    023    import java.util.List;
    024    import java.util.Map;
    025    import java.util.Properties;
    026
    027    /**
    028     * <p>
    029     * A Map based Configuration.
    030     * </p>
    031     * <p>
    032     * This implementation of the {@code Configuration} interface is
    033     * initialized with a {@code java.util.Map}. The methods of the
    034     * {@code Configuration} interface are implemented on top of the content of
    035     * this map. The following storage scheme is used:
    036     * </p>
    037     * <p>
    038     * Property keys are directly mapped to map keys, i.e. the
    039     * {@code getProperty()} method directly performs a {@code get()} on
    040     * the map. Analogously, {@code setProperty()} or
    041     * {@code addProperty()} operations write new data into the map. If a value
    042     * is added to an existing property, a {@code java.util.List} is created,
    043     * which stores the values of this property.
    044     * </p>
    045     * <p>
    046     * An important use case of this class is to treat a map as a
    047     * {@code Configuration} allowing access to its data through the richer
    048     * interface. This can be a bit problematic in some cases because the map may
    049     * contain values that need not adhere to the default storage scheme used by
    050     * typical configuration implementations, e.g. regarding lists. In such cases
    051     * care must be taken when manipulating the data through the
    052     * {@code Configuration} interface, e.g. by calling
    053     * {@code addProperty()}; results may be different than expected.
    054     * </p>
    055     * <p>
    056     * An important point is the handling of list delimiters: If delimiter parsing
    057     * is enabled (which it is per default), {@code getProperty()} checks
    058     * whether the value of a property is a string and whether it contains the list
    059     * delimiter character. If this is the case, the value is split at the delimiter
    060     * resulting in a list. This split operation typically also involves trimming
    061     * the single values as the list delimiter character may be surrounded by
    062     * whitespace. Trimming can be disabled with the
    063     * {@link #setTrimmingDisabled(boolean)} method. The whole list splitting
    064     * behavior can be disabled using the
    065     * {@link #setDelimiterParsingDisabled(boolean)} method.
    066     * </p>
    067     * <p>
    068     * Notice that list splitting is only performed for single string values. If a
    069     * property has multiple values, the single values are not split even if they
    070     * contain the list delimiter character.
    071     * </p>
    072     * <p>
    073     * As the underlying {@code Map} is directly used as store of the property
    074     * values, the thread-safety of this {@code Configuration} implementation
    075     * depends on the map passed to the constructor.
    076     * </p>
    077     * <p>
    078     * Notes about type safety: For properties with multiple values this implementation
    079     * creates lists of type {@code Object} and stores them. If a property is assigned
    080     * another value, the value is added to the list. This can cause problems if the
    081     * map passed to the constructor already contains lists of other types. This
    082     * should be avoided, otherwise it cannot be guaranteed that the application
    083     * might throw {@code ClassCastException} exceptions later.
    084     * </p>
    085     *
    086     * @author Emmanuel Bourg
    087     * @version $Id: MapConfiguration.java 1210171 2011-12-04 18:32:07Z oheger $
    088     * @since 1.1
    089     */
    090    public class MapConfiguration extends AbstractConfiguration implements Cloneable
    091    {
    092        /** The Map decorated by this configuration. */
    093        protected Map<String, Object> map;
    094
    095        /** A flag whether trimming of property values should be disabled.*/
    096        private boolean trimmingDisabled;
    097
    098        /**
    099         * Create a Configuration decorator around the specified Map. The map is
    100         * used to store the configuration properties, any change will also affect
    101         * the Map.
    102         *
    103         * @param map the map
    104         */
    105        public MapConfiguration(Map<String, Object> map)
    106        {
    107            this.map = map;
    108        }
    109
    110        /**
    111         * Creates a new instance of {@code MapConfiguration} and initializes its
    112         * content from the specified {@code Properties} object. The resulting
    113         * configuration is not connected to the {@code Properties} object, but all
    114         * keys which are strings are copied (keys of other types are ignored).
    115         *
    116         * @param props the {@code Properties} object defining the content of this
    117         *        configuration
    118         * @throws NullPointerException if the {@code Properties} object is
    119         *         <b>null</b>
    120         * @since 1.8
    121         */
    122        public MapConfiguration(Properties props)
    123        {
    124            map = convertPropertiesToMap(props);
    125        }
    126
    127        /**
    128         * Return the Map decorated by this configuration.
    129         *
    130         * @return the map this configuration is based onto
    131         */
    132        public Map<String, Object> getMap()
    133        {
    134            return map;
    135        }
    136
    137        /**
    138         * Returns the flag whether trimming of property values is disabled.
    139         *
    140         * @return <b>true</b> if trimming of property values is disabled;
    141         *         <b>false</b> otherwise
    142         * @since 1.7
    143         */
    144        public boolean isTrimmingDisabled()
    145        {
    146            return trimmingDisabled;
    147        }
    148
    149        /**
    150         * Sets a flag whether trimming of property values is disabled. This flag is
    151         * only evaluated if list splitting is enabled. Refer to the header comment
    152         * for more information about list splitting and trimming.
    153         *
    154         * @param trimmingDisabled a flag whether trimming of property values should
    155         *        be disabled
    156         * @since 1.7
    157         */
    158        public void setTrimmingDisabled(boolean trimmingDisabled)
    159        {
    160            this.trimmingDisabled = trimmingDisabled;
    161        }
    162
    163        public Object getProperty(String key)
    164        {
    165            Object value = map.get(key);
    166            if ((value instanceof String) && (!isDelimiterParsingDisabled()))
    167            {
    168                List<String> list = PropertyConverter.split((String) value, getListDelimiter(), !isTrimmingDisabled());
    169                return list.size() > 1 ? list : list.get(0);
    170            }
    171            else
    172            {
    173                return value;
    174            }
    175        }
    176
    177        @Override
    178        protected void addPropertyDirect(String key, Object value)
    179        {
    180            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                List<Object> list = new ArrayList<Object>();
    196                list.add(previousValue);
    197                list.add(value);
    198
    199                map.put(key, list);
    200            }
    201        }
    202
    203        public boolean isEmpty()
    204        {
    205            return map.isEmpty();
    206        }
    207
    208        public boolean containsKey(String key)
    209        {
    210            return map.containsKey(key);
    211        }
    212
    213        @Override
    214        protected void clearPropertyDirect(String key)
    215        {
    216            map.remove(key);
    217        }
    218
    219        public Iterator<String> getKeys()
    220        {
    221            return map.keySet().iterator();
    222        }
    223
    224        /**
    225         * Returns a copy of this object. The returned configuration will contain
    226         * the same properties as the original. Event listeners are not cloned.
    227         *
    228         * @return the copy
    229         * @since 1.3
    230         */
    231        @Override
    232        public Object clone()
    233        {
    234            try
    235            {
    236                MapConfiguration copy = (MapConfiguration) super.clone();
    237                copy.clearConfigurationListeners();
    238                // Safe because ConfigurationUtils returns a map of the same types.
    239                @SuppressWarnings("unchecked")
    240                Map<String, Object> clonedMap = (Map<String, Object>) ConfigurationUtils.clone(map);
    241                copy.map = clonedMap;
    242                return copy;
    243            }
    244            catch (CloneNotSupportedException cex)
    245            {
    246                // cannot happen
    247                throw new ConfigurationRuntimeException(cex);
    248            }
    249        }
    250
    251        /**
    252         * Helper method for copying all string keys from the given
    253         * {@code Properties} object to a newly created map.
    254         *
    255         * @param props the {@code Properties} to be copied
    256         * @return a newly created map with all string keys of the properties
    257         */
    258        private static Map<String, Object> convertPropertiesToMap(Properties props)
    259        {
    260            Map<String, Object> map = new HashMap<String, Object>();
    261            for (Map.Entry<Object, Object> e : props.entrySet())
    262            {
    263                if (e.getKey() instanceof String)
    264                {
    265                    map.put((String) e.getKey(), e.getValue());
    266                }
    267            }
    268            return map;
    269        }
    270    }