    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 * @version $Id: BaseConfiguration.java 1842194 2018-09-27 22:24:23Z ggregory $
    051 */
    052public class BaseConfiguration extends AbstractConfiguration implements Cloneable
    053{
    054    /** stores the configuration key-value pairs */
    055    private Map<String, Object> store = new LinkedHashMap<>();
    056
    057    /**
    058     * Adds a key/value pair to the map.  This routine does no magic morphing.
    059     * It ensures the keylist is maintained
    060     *
    061     * @param key key to use for mapping
    062     * @param value object to store
    063     */
    064    @Override
    065    protected void addPropertyDirect(final String key, final Object value)
    066    {
    067        final Object previousValue = getPropertyInternal(key);
    068
    069        if (previousValue == null)
    070        {
    071            store.put(key, value);
    072        }
    073        else if (previousValue instanceof List)
    074        {
    075            // safe to case because we have created the lists ourselves
    076            @SuppressWarnings("unchecked")
    077            final
    078            List<Object> valueList = (List<Object>) previousValue;
    079            // the value is added to the existing list
    080            valueList.add(value);
    081        }
    082        else
    083        {
    084            // the previous value is replaced by a list containing the previous value and the new value
    085            final List<Object> list = new ArrayList<>();
    086            list.add(previousValue);
    087            list.add(value);
    088
    089            store.put(key, list);
    090        }
    091    }
    092
    093    /**
    094     * Read property from underlying map.
    095     *
    096     * @param key key to use for mapping
    097     *
    098     * @return object associated with the given configuration key.
    099     */
    100    @Override
    101    protected Object getPropertyInternal(final String key)
    102    {
    103        return store.get(key);
    104    }
    105
    106    /**
    107     * Check if the configuration is empty
    108     *
    109     * @return {@code true} if Configuration is empty,
    110     * {@code false} otherwise.
    111     */
    112    @Override
    113    protected boolean isEmptyInternal()
    114    {
    115        return store.isEmpty();
    116    }
    117
    118    /**
    119     * check if the configuration contains the key
    120     *
    121     * @param key the configuration key
    122     *
    123     * @return {@code true} if Configuration contain given key,
    124     * {@code false} otherwise.
    125     */
    126    @Override
    127    protected boolean containsKeyInternal(final String key)
    128    {
    129        return store.containsKey(key);
    130    }
    131
    132    /**
    133     * Clear a property in the configuration.
    134     *
    135     * @param key the key to remove along with corresponding value.
    136     */
    137    @Override
    138    protected void clearPropertyDirect(final String key)
    139    {
    140        store.remove(key);
    141    }
    142
    143    @Override
    144    protected void clearInternal()
    145    {
    146        store.clear();
    147    }
    148
    149    /**
    150     * {@inheritDoc} This implementation obtains the size directly from the map
    151     * used as data store. So this is a rather efficient implementation.
    152     */
    153    @Override
    154    protected int sizeInternal()
    155    {
    156        return store.size();
    157    }
    158
    159    /**
    160     * Get the list of the keys contained in the configuration
    161     * repository.
    162     *
    163     * @return An Iterator.
    164     */
    165    @Override
    166    protected Iterator<String> getKeysInternal()
    167    {
    168        return store.keySet().iterator();
    169    }
    170
    171    /**
    172     * Creates a copy of this object. This implementation will create a deep
    173     * clone, i.e. the map that stores the properties is cloned, too. So changes
    174     * performed at the copy won't affect the original and vice versa.
    175     *
    176     * @return the copy
    177     * @since 1.3
    178     */
    179    @Override
    180    public Object clone()
    181    {
    182        try
    183        {
    184            final BaseConfiguration copy = (BaseConfiguration) super.clone();
    185            cloneStore(copy);
    186            copy.cloneInterpolator(this);
    187
    188            return copy;
    189        }
    190        catch (final CloneNotSupportedException cex)
    191        {
    192            // should not happen
    193            throw new ConfigurationRuntimeException(cex);
    194        }
    195    }
    196
    197    /**
    198     * Clones the internal map with the data of this configuration.
    199     *
    200     * @param copy the copy created by the {@code clone()} method
    201     * @throws CloneNotSupportedException if the map cannot be cloned
    202     */
    203    private void cloneStore(final BaseConfiguration copy)
    204            throws CloneNotSupportedException
    205    {
    206        // This is safe because the type of the map is known
    207        @SuppressWarnings("unchecked")
    208        final
    209        Map<String, Object> clonedStore = (Map<String, Object>) ConfigurationUtils.clone(store);
    210        copy.store = clonedStore;
    211
    212        // Handle collections in the map; they have to be cloned, too
    213        for (final Map.Entry<String, Object> e : store.entrySet())
    214        {
    215            if (e.getValue() instanceof Collection)
    216            {
    217                // This is safe because the collections were created by ourselves
    218                @SuppressWarnings("unchecked")
    219                final
    220                Collection<String> strList = (Collection<String>) e.getValue();
    221                copy.store.put(e.getKey(), new ArrayList<>(strList));
    222            }
    223        }
    224    }
    225}