    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.Iterator;
    022    import java.util.List;
    023    import java.util.Map;
    024
    025    /**
    026     * <p>
    027     * A Map based Configuration.
    028     * </p>
    029     * <p>
    030     * This implementation of the <code>Configuration</code> interface is
    031     * initialized with a <code>java.util.Map</code>. The methods of the
    032     * <code>Configuration</code> interface are implemented on top of the content of
    033     * this map. The following storage scheme is used:
    034     * </p>
    035     * <p>
    036     * Property keys are directly mapped to map keys, i.e. the
    037     * <code>getProperty()</code> method directly performs a <code>get()</code> on
    038     * the map. Analogously, <code>setProperty()</code> or
    039     * <code>addProperty()</code> operations write new data into the map. If a value
    040     * is added to an existing property, a <code>java.util.List</code> is created,
    041     * which stores the values of this property.
    042     * </p>
    043     * <p>
    044     * An important use case of this class is to treat a map as a
    045     * <code>Configuration</code> allowing access to its data through the richer
    046     * interface. This can be a bit problematic in some cases because the map may
    047     * contain values that need not adhere to the default storage scheme used by
    048     * typical configuration implementations, e.g. regarding lists. In such cases
    049     * care must be taken when manipulating the data through the
    050     * <code>Configuration</code> interface, e.g. by calling
    051     * <code>addProperty()</code>; results may be different than expected.
    052     * </p>
    053     * <p>
    054     * An important point is the handling of list delimiters: If delimiter parsing
    055     * is enabled (which it is per default), <code>getProperty()</code> checks
    056     * whether the value of a property is a string and whether it contains the list
    057     * delimiter character. If this is the case, the value is split at the delimiter
    058     * resulting in a list. This split operation typically also involves trimming
    059     * the single values as the list delimiter character may be surrounded by
    060     * whitespace. Trimming can be disabled with the
    061     * {@link #setTrimmingDisabled(boolean)} method. The whole list splitting
    062     * behavior can be disabled using the
    063     * {@link #setDelimiterParsingDisabled(boolean)} method.
    064     * </p>
    065     * <p>
    066     * Notice that list splitting is only performed for single string values. If a
    067     * property has multiple values, the single values are not split even if they
    068     * contain the list delimiter character.
    069     * </p>
    070     * <p>
    071     * As the underlying <code>Map</code> is directly used as store of the property
    072     * values, the thread-safety of this <code>Configuration</code> implementation
    073     * depends on the map passed to the constructor.
    074     * </p>
    075     *
    076     * @author Emmanuel Bourg
    077     * @version $Revision: 763367 $, $Date: 2009-04-08 21:56:10 +0200 (Mi, 08. Apr 2009) $
    078     * @since 1.1
    079     */
    080    public class MapConfiguration extends AbstractConfiguration implements Cloneable
    081    {
    082        /** The Map decorated by this configuration. */
    083        protected Map map;
    084
    085        /** A flag whether trimming of property values should be disabled.*/
    086        private boolean trimmingDisabled;
    087
    088        /**
    089         * Create a Configuration decorator around the specified Map. The map is
    090         * used to store the configuration properties, any change will also affect
    091         * the Map.
    092         *
    093         * @param map the map
    094         */
    095        public MapConfiguration(Map map)
    096        {
    097            this.map = map;
    098        }
    099
    100        /**
    101         * Return the Map decorated by this configuration.
    102         *
    103         * @return the map this configuration is based onto
    104         */
    105        public Map getMap()
    106        {
    107            return map;
    108        }
    109
    110        /**
    111         * Returns the flag whether trimming of property values is disabled.
    112         *
    113         * @return <b>true</b> if trimming of property values is disabled;
    114         *         <b>false</b> otherwise
    115         * @since 1.7
    116         */
    117        public boolean isTrimmingDisabled()
    118        {
    119            return trimmingDisabled;
    120        }
    121
    122        /**
    123         * Sets a flag whether trimming of property values is disabled. This flag is
    124         * only evaluated if list splitting is enabled. Refer to the header comment
    125         * for more information about list splitting and trimming.
    126         *
    127         * @param trimmingDisabled a flag whether trimming of property values should
    128         *        be disabled
    129         * @since 1.7
    130         */
    131        public void setTrimmingDisabled(boolean trimmingDisabled)
    132        {
    133            this.trimmingDisabled = trimmingDisabled;
    134        }
    135
    136        public Object getProperty(String key)
    137        {
    138            Object value = map.get(key);
    139            if ((value instanceof String) && (!isDelimiterParsingDisabled()))
    140            {
    141                List list = PropertyConverter.split((String) value, getListDelimiter(), !isTrimmingDisabled());
    142                return list.size() > 1 ? list : list.get(0);
    143            }
    144            else
    145            {
    146                return value;
    147            }
    148        }
    149
    150        protected void addPropertyDirect(String key, Object value)
    151        {
    152            Object previousValue = getProperty(key);
    153
    154            if (previousValue == null)
    155            {
    156                map.put(key, value);
    157            }
    158            else if (previousValue instanceof List)
    159            {
    160                // the value is added to the existing list
    161                ((List) previousValue).add(value);
    162            }
    163            else
    164            {
    165                // the previous value is replaced by a list containing the previous value and the new value
    166                List list = new ArrayList();
    167                list.add(previousValue);
    168                list.add(value);
    169
    170                map.put(key, list);
    171            }
    172        }
    173
    174        public boolean isEmpty()
    175        {
    176            return map.isEmpty();
    177        }
    178
    179        public boolean containsKey(String key)
    180        {
    181            return map.containsKey(key);
    182        }
    183
    184        protected void clearPropertyDirect(String key)
    185        {
    186            map.remove(key);
    187        }
    188
    189        public Iterator getKeys()
    190        {
    191            return map.keySet().iterator();
    192        }
    193
    194        /**
    195         * Returns a copy of this object. The returned configuration will contain
    196         * the same properties as the original. Event listeners are not cloned.
    197         *
    198         * @return the copy
    199         * @since 1.3
    200         */
    201        public Object clone()
    202        {
    203            try
    204            {
    205                MapConfiguration copy = (MapConfiguration) super.clone();
    206                copy.clearConfigurationListeners();
    207                copy.map = (Map) ConfigurationUtils.clone(map);
    208                return copy;
    209            }
    210            catch (CloneNotSupportedException cex)
    211            {
    212                // cannot happen
    213                throw new ConfigurationRuntimeException(cex);
    214            }
    215        }
    216    }