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