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