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