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}