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}