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    
018    package org.apache.commons.configuration;
019    
020    import java.util.ArrayList;
021    import java.util.Iterator;
022    import java.util.List;
023    import java.util.Map;
024    
025    /**
026     * <p>
027     * A Map based Configuration.
028     * </p>
029     * <p>
030     * This implementation of the <code>Configuration</code> interface is
031     * initialized with a <code>java.util.Map</code>. The methods of the
032     * <code>Configuration</code> interface are implemented on top of the content of
033     * this map. The following storage scheme is used:
034     * </p>
035     * <p>
036     * Property keys are directly mapped to map keys, i.e. the
037     * <code>getProperty()</code> method directly performs a <code>get()</code> on
038     * the map. Analogously, <code>setProperty()</code> or
039     * <code>addProperty()</code> operations write new data into the map. If a value
040     * is added to an existing property, a <code>java.util.List</code> is created,
041     * which stores the values of this property.
042     * </p>
043     * <p>
044     * An important use case of this class is to treat a map as a
045     * <code>Configuration</code> allowing access to its data through the richer
046     * interface. This can be a bit problematic in some cases because the map may
047     * contain values that need not adhere to the default storage scheme used by
048     * typical configuration implementations, e.g. regarding lists. In such cases
049     * care must be taken when manipulating the data through the
050     * <code>Configuration</code> interface, e.g. by calling
051     * <code>addProperty()</code>; results may be different than expected.
052     * </p>
053     * <p>
054     * An important point is the handling of list delimiters: If delimiter parsing
055     * is enabled (which it is per default), <code>getProperty()</code> checks
056     * whether the value of a property is a string and whether it contains the list
057     * delimiter character. If this is the case, the value is split at the delimiter
058     * resulting in a list. This split operation typically also involves trimming
059     * the single values as the list delimiter character may be surrounded by
060     * whitespace. Trimming can be disabled with the
061     * {@link #setTrimmingDisabled(boolean)} method. The whole list splitting
062     * behavior can be disabled using the
063     * {@link #setDelimiterParsingDisabled(boolean)} method.
064     * </p>
065     * <p>
066     * Notice that list splitting is only performed for single string values. If a
067     * property has multiple values, the single values are not split even if they
068     * contain the list delimiter character.
069     * </p>
070     * <p>
071     * As the underlying <code>Map</code> is directly used as store of the property
072     * values, the thread-safety of this <code>Configuration</code> implementation
073     * depends on the map passed to the constructor.
074     * </p>
075     *
076     * @author Emmanuel Bourg
077     * @version $Revision: 763367 $, $Date: 2009-04-08 21:56:10 +0200 (Mi, 08. Apr 2009) $
078     * @since 1.1
079     */
080    public class MapConfiguration extends AbstractConfiguration implements Cloneable
081    {
082        /** The Map decorated by this configuration. */
083        protected Map map;
084    
085        /** A flag whether trimming of property values should be disabled.*/
086        private boolean trimmingDisabled;
087    
088        /**
089         * Create a Configuration decorator around the specified Map. The map is
090         * used to store the configuration properties, any change will also affect
091         * the Map.
092         *
093         * @param map the map
094         */
095        public MapConfiguration(Map map)
096        {
097            this.map = map;
098        }
099    
100        /**
101         * Return the Map decorated by this configuration.
102         *
103         * @return the map this configuration is based onto
104         */
105        public Map getMap()
106        {
107            return map;
108        }
109    
110        /**
111         * Returns the flag whether trimming of property values is disabled.
112         *
113         * @return <b>true</b> if trimming of property values is disabled;
114         *         <b>false</b> otherwise
115         * @since 1.7
116         */
117        public boolean isTrimmingDisabled()
118        {
119            return trimmingDisabled;
120        }
121    
122        /**
123         * Sets a flag whether trimming of property values is disabled. This flag is
124         * only evaluated if list splitting is enabled. Refer to the header comment
125         * for more information about list splitting and trimming.
126         *
127         * @param trimmingDisabled a flag whether trimming of property values should
128         *        be disabled
129         * @since 1.7
130         */
131        public void setTrimmingDisabled(boolean trimmingDisabled)
132        {
133            this.trimmingDisabled = trimmingDisabled;
134        }
135    
136        public Object getProperty(String key)
137        {
138            Object value = map.get(key);
139            if ((value instanceof String) && (!isDelimiterParsingDisabled()))
140            {
141                List list = PropertyConverter.split((String) value, getListDelimiter(), !isTrimmingDisabled());
142                return list.size() > 1 ? list : list.get(0);
143            }
144            else
145            {
146                return value;
147            }
148        }
149    
150        protected void addPropertyDirect(String key, Object value)
151        {
152            Object previousValue = getProperty(key);
153    
154            if (previousValue == null)
155            {
156                map.put(key, value);
157            }
158            else if (previousValue instanceof List)
159            {
160                // the value is added to the existing list
161                ((List) previousValue).add(value);
162            }
163            else
164            {
165                // the previous value is replaced by a list containing the previous value and the new value
166                List list = new ArrayList();
167                list.add(previousValue);
168                list.add(value);
169    
170                map.put(key, list);
171            }
172        }
173    
174        public boolean isEmpty()
175        {
176            return map.isEmpty();
177        }
178    
179        public boolean containsKey(String key)
180        {
181            return map.containsKey(key);
182        }
183    
184        protected void clearPropertyDirect(String key)
185        {
186            map.remove(key);
187        }
188    
189        public Iterator getKeys()
190        {
191            return map.keySet().iterator();
192        }
193    
194        /**
195         * Returns a copy of this object. The returned configuration will contain
196         * the same properties as the original. Event listeners are not cloned.
197         *
198         * @return the copy
199         * @since 1.3
200         */
201        public Object clone()
202        {
203            try
204            {
205                MapConfiguration copy = (MapConfiguration) super.clone();
206                copy.clearConfigurationListeners();
207                copy.map = (Map) ConfigurationUtils.clone(map);
208                return copy;
209            }
210            catch (CloneNotSupportedException cex)
211            {
212                // cannot happen
213                throw new ConfigurationRuntimeException(cex);
214            }
215        }
216    }