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 }