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.HashMap;
022    import java.util.Iterator;
023    import java.util.List;
024    import java.util.Map;
025    import java.util.Properties;
026    
027    /**
028     * <p>
029     * A Map based Configuration.
030     * </p>
031     * <p>
032     * This implementation of the {@code Configuration} interface is
033     * initialized with a {@code java.util.Map}. The methods of the
034     * {@code Configuration} interface are implemented on top of the content of
035     * this map. The following storage scheme is used:
036     * </p>
037     * <p>
038     * Property keys are directly mapped to map keys, i.e. the
039     * {@code getProperty()} method directly performs a {@code get()} on
040     * the map. Analogously, {@code setProperty()} or
041     * {@code addProperty()} operations write new data into the map. If a value
042     * is added to an existing property, a {@code java.util.List} is created,
043     * which stores the values of this property.
044     * </p>
045     * <p>
046     * An important use case of this class is to treat a map as a
047     * {@code Configuration} allowing access to its data through the richer
048     * interface. This can be a bit problematic in some cases because the map may
049     * contain values that need not adhere to the default storage scheme used by
050     * typical configuration implementations, e.g. regarding lists. In such cases
051     * care must be taken when manipulating the data through the
052     * {@code Configuration} interface, e.g. by calling
053     * {@code addProperty()}; results may be different than expected.
054     * </p>
055     * <p>
056     * An important point is the handling of list delimiters: If delimiter parsing
057     * is enabled (which it is per default), {@code getProperty()} checks
058     * whether the value of a property is a string and whether it contains the list
059     * delimiter character. If this is the case, the value is split at the delimiter
060     * resulting in a list. This split operation typically also involves trimming
061     * the single values as the list delimiter character may be surrounded by
062     * whitespace. Trimming can be disabled with the
063     * {@link #setTrimmingDisabled(boolean)} method. The whole list splitting
064     * behavior can be disabled using the
065     * {@link #setDelimiterParsingDisabled(boolean)} method.
066     * </p>
067     * <p>
068     * Notice that list splitting is only performed for single string values. If a
069     * property has multiple values, the single values are not split even if they
070     * contain the list delimiter character.
071     * </p>
072     * <p>
073     * As the underlying {@code Map} is directly used as store of the property
074     * values, the thread-safety of this {@code Configuration} implementation
075     * depends on the map passed to the constructor.
076     * </p>
077     * <p>
078     * Notes about type safety: For properties with multiple values this implementation
079     * creates lists of type {@code Object} and stores them. If a property is assigned
080     * another value, the value is added to the list. This can cause problems if the
081     * map passed to the constructor already contains lists of other types. This
082     * should be avoided, otherwise it cannot be guaranteed that the application
083     * might throw {@code ClassCastException} exceptions later.
084     * </p>
085     *
086     * @author Emmanuel Bourg
087     * @version $Id: MapConfiguration.java 1210171 2011-12-04 18:32:07Z oheger $
088     * @since 1.1
089     */
090    public class MapConfiguration extends AbstractConfiguration implements Cloneable
091    {
092        /** The Map decorated by this configuration. */
093        protected Map<String, Object> map;
094    
095        /** A flag whether trimming of property values should be disabled.*/
096        private boolean trimmingDisabled;
097    
098        /**
099         * Create a Configuration decorator around the specified Map. The map is
100         * used to store the configuration properties, any change will also affect
101         * the Map.
102         *
103         * @param map the map
104         */
105        public MapConfiguration(Map<String, Object> map)
106        {
107            this.map = map;
108        }
109    
110        /**
111         * Creates a new instance of {@code MapConfiguration} and initializes its
112         * content from the specified {@code Properties} object. The resulting
113         * configuration is not connected to the {@code Properties} object, but all
114         * keys which are strings are copied (keys of other types are ignored).
115         *
116         * @param props the {@code Properties} object defining the content of this
117         *        configuration
118         * @throws NullPointerException if the {@code Properties} object is
119         *         <b>null</b>
120         * @since 1.8
121         */
122        public MapConfiguration(Properties props)
123        {
124            map = convertPropertiesToMap(props);
125        }
126    
127        /**
128         * Return the Map decorated by this configuration.
129         *
130         * @return the map this configuration is based onto
131         */
132        public Map<String, Object> getMap()
133        {
134            return map;
135        }
136    
137        /**
138         * Returns the flag whether trimming of property values is disabled.
139         *
140         * @return <b>true</b> if trimming of property values is disabled;
141         *         <b>false</b> otherwise
142         * @since 1.7
143         */
144        public boolean isTrimmingDisabled()
145        {
146            return trimmingDisabled;
147        }
148    
149        /**
150         * Sets a flag whether trimming of property values is disabled. This flag is
151         * only evaluated if list splitting is enabled. Refer to the header comment
152         * for more information about list splitting and trimming.
153         *
154         * @param trimmingDisabled a flag whether trimming of property values should
155         *        be disabled
156         * @since 1.7
157         */
158        public void setTrimmingDisabled(boolean trimmingDisabled)
159        {
160            this.trimmingDisabled = trimmingDisabled;
161        }
162    
163        public Object getProperty(String key)
164        {
165            Object value = map.get(key);
166            if ((value instanceof String) && (!isDelimiterParsingDisabled()))
167            {
168                List<String> list = PropertyConverter.split((String) value, getListDelimiter(), !isTrimmingDisabled());
169                return list.size() > 1 ? list : list.get(0);
170            }
171            else
172            {
173                return value;
174            }
175        }
176    
177        @Override
178        protected void addPropertyDirect(String key, Object value)
179        {
180            Object previousValue = getProperty(key);
181    
182            if (previousValue == null)
183            {
184                map.put(key, value);
185            }
186            else if (previousValue instanceof List)
187            {
188                // the value is added to the existing list
189                // Note: This is problematic. See header comment!
190                ((List<Object>) previousValue).add(value);
191            }
192            else
193            {
194                // the previous value is replaced by a list containing the previous value and the new value
195                List<Object> list = new ArrayList<Object>();
196                list.add(previousValue);
197                list.add(value);
198    
199                map.put(key, list);
200            }
201        }
202    
203        public boolean isEmpty()
204        {
205            return map.isEmpty();
206        }
207    
208        public boolean containsKey(String key)
209        {
210            return map.containsKey(key);
211        }
212    
213        @Override
214        protected void clearPropertyDirect(String key)
215        {
216            map.remove(key);
217        }
218    
219        public Iterator<String> getKeys()
220        {
221            return map.keySet().iterator();
222        }
223    
224        /**
225         * Returns a copy of this object. The returned configuration will contain
226         * the same properties as the original. Event listeners are not cloned.
227         *
228         * @return the copy
229         * @since 1.3
230         */
231        @Override
232        public Object clone()
233        {
234            try
235            {
236                MapConfiguration copy = (MapConfiguration) super.clone();
237                copy.clearConfigurationListeners();
238                // Safe because ConfigurationUtils returns a map of the same types.
239                @SuppressWarnings("unchecked")
240                Map<String, Object> clonedMap = (Map<String, Object>) ConfigurationUtils.clone(map);
241                copy.map = clonedMap;
242                return copy;
243            }
244            catch (CloneNotSupportedException cex)
245            {
246                // cannot happen
247                throw new ConfigurationRuntimeException(cex);
248            }
249        }
250    
251        /**
252         * Helper method for copying all string keys from the given
253         * {@code Properties} object to a newly created map.
254         *
255         * @param props the {@code Properties} to be copied
256         * @return a newly created map with all string keys of the properties
257         */
258        private static Map<String, Object> convertPropertiesToMap(Properties props)
259        {
260            Map<String, Object> map = new HashMap<String, Object>();
261            for (Map.Entry<Object, Object> e : props.entrySet())
262            {
263                if (e.getKey() instanceof String)
264                {
265                    map.put((String) e.getKey(), e.getValue());
266                }
267            }
268            return map;
269        }
270    }