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