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 }