    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.LinkedHashMap;
    024import java.util.List;
    025import java.util.Map;
    026
    027import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
    028
    029/**
    030 * Basic configuration class. Stores the configuration data but does not
    031 * provide any load or save functions. If you want to load your Configuration
    032 * from a file use PropertiesConfiguration or XmlConfiguration.
    033 *
    034 * This class extends normal Java properties by adding the possibility
    035 * to use the same key many times concatenating the value strings
    036 * instead of overwriting them.
    037 *
    038 * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
    039 * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
    040 * @author <a href="mailto:daveb@miceda-data">Dave Bryson</a>
    041 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
    042 * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a>
    043 * @author <a href="mailto:kjohnson@transparent.com">Kent Johnson</a>
    044 * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
    045 * @author <a href="mailto:ipriha@surfeu.fi">Ilkka Priha</a>
    046 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
    047 * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
    048 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
    049 * @author <a href="mailto:ksh@scand.com">Konstantin Shaposhnikov</a>
    050 */
    051public class BaseConfiguration extends AbstractConfiguration implements Cloneable
    052{
    053    /** stores the configuration key-value pairs */
    054    private Map<String, Object> store = new LinkedHashMap<>();
    055
    056    /**
    057     * Adds a key/value pair to the map.  This routine does no magic morphing.
    058     * It ensures the keylist is maintained
    059     *
    060     * @param key key to use for mapping
    061     * @param value object to store
    062     */
    063    @Override
    064    protected void addPropertyDirect(final String key, final Object value)
    065    {
    066        final Object previousValue = getPropertyInternal(key);
    067
    068        if (previousValue == null)
    069        {
    070            store.put(key, value);
    071        }
    072        else if (previousValue instanceof List)
    073        {
    074            // safe to case because we have created the lists ourselves
    075            @SuppressWarnings("unchecked")
    076            final
    077            List<Object> valueList = (List<Object>) previousValue;
    078            // the value is added to the existing list
    079            valueList.add(value);
    080        }
    081        else
    082        {
    083            // the previous value is replaced by a list containing the previous value and the new value
    084            final List<Object> list = new ArrayList<>();
    085            list.add(previousValue);
    086            list.add(value);
    087
    088            store.put(key, list);
    089        }
    090    }
    091
    092    /**
    093     * Read property from underlying map.
    094     *
    095     * @param key key to use for mapping
    096     *
    097     * @return object associated with the given configuration key.
    098     */
    099    @Override
    100    protected Object getPropertyInternal(final String key)
    101    {
    102        return store.get(key);
    103    }
    104
    105    /**
    106     * Check if the configuration is empty
    107     *
    108     * @return {@code true} if Configuration is empty,
    109     * {@code false} otherwise.
    110     */
    111    @Override
    112    protected boolean isEmptyInternal()
    113    {
    114        return store.isEmpty();
    115    }
    116
    117    /**
    118     * check if the configuration contains the key
    119     *
    120     * @param key the configuration key
    121     *
    122     * @return {@code true} if Configuration contain given key,
    123     * {@code false} otherwise.
    124     */
    125    @Override
    126    protected boolean containsKeyInternal(final String key)
    127    {
    128        return store.containsKey(key);
    129    }
    130
    131    /**
    132     * Clear a property in the configuration.
    133     *
    134     * @param key the key to remove along with corresponding value.
    135     */
    136    @Override
    137    protected void clearPropertyDirect(final String key)
    138    {
    139        store.remove(key);
    140    }
    141
    142    @Override
    143    protected void clearInternal()
    144    {
    145        store.clear();
    146    }
    147
    148    /**
    149     * {@inheritDoc} This implementation obtains the size directly from the map
    150     * used as data store. So this is a rather efficient implementation.
    151     */
    152    @Override
    153    protected int sizeInternal()
    154    {
    155        return store.size();
    156    }
    157
    158    /**
    159     * Get the list of the keys contained in the configuration
    160     * repository.
    161     *
    162     * @return An Iterator.
    163     */
    164    @Override
    165    protected Iterator<String> getKeysInternal()
    166    {
    167        return store.keySet().iterator();
    168    }
    169
    170    /**
    171     * Creates a copy of this object. This implementation will create a deep
    172     * clone, i.e. the map that stores the properties is cloned, too. So changes
    173     * performed at the copy won't affect the original and vice versa.
    174     *
    175     * @return the copy
    176     * @since 1.3
    177     */
    178    @Override
    179    public Object clone()
    180    {
    181        try
    182        {
    183            final BaseConfiguration copy = (BaseConfiguration) super.clone();
    184            cloneStore(copy);
    185            copy.cloneInterpolator(this);
    186
    187            return copy;
    188        }
    189        catch (final CloneNotSupportedException cex)
    190        {
    191            // should not happen
    192            throw new ConfigurationRuntimeException(cex);
    193        }
    194    }
    195
    196    /**
    197     * Clones the internal map with the data of this configuration.
    198     *
    199     * @param copy the copy created by the {@code clone()} method
    200     * @throws CloneNotSupportedException if the map cannot be cloned
    201     */
    202    private void cloneStore(final BaseConfiguration copy)
    203            throws CloneNotSupportedException
    204    {
    205        // This is safe because the type of the map is known
    206        @SuppressWarnings("unchecked")
    207        final
    208        Map<String, Object> clonedStore = (Map<String, Object>) ConfigurationUtils.clone(store);
    209        copy.store = clonedStore;
    210
    211        // Handle collections in the map; they have to be cloned, too
    212        for (final Map.Entry<String, Object> e : store.entrySet())
    213        {
    214            if (e.getValue() instanceof Collection)
    215            {
    216                // This is safe because the collections were created by ourselves
    217                @SuppressWarnings("unchecked")
    218                final
    219                Collection<String> strList = (Collection<String>) e.getValue();
    220                copy.store.put(e.getKey(), new ArrayList<>(strList));
    221            }
    222        }
    223    }
    224}