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.configuration2; 019 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.Iterator; 023import java.util.List; 024import java.util.Map; 025import java.util.Properties; 026 027import org.apache.commons.configuration2.ex.ConfigurationRuntimeException; 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 * The handling of list delimiters is a bit different for this configuration 059 * implementation: When a property of type String is queried, it is passed to 060 * the current {@link org.apache.commons.configuration2.convert.ListDelimiterHandler 061 * ListDelimiterHandler} which may generate multiple values. 062 * Note that per default a list delimiter handler is set which does not do any 063 * list splitting, so this feature is disabled. It can be enabled by setting 064 * a properly configured {@code ListDelimiterHandler} implementation, e.g. a 065 * {@link org.apache.commons.configuration2.convert.DefaultListDelimiterHandler 066 * DefaultListDelimiterHandler} object. 067 * </p> 068 * <p> 069 * Notice that list splitting is only performed for single string values. If a 070 * property has multiple values, the single values are not split even if they 071 * contain the list delimiter character. 072 * </p> 073 * <p> 074 * As the underlying {@code Map} is directly used as store of the property 075 * values, the thread-safety of this {@code Configuration} implementation 076 * depends on the map passed to the constructor. 077 * </p> 078 * <p> 079 * Notes about type safety: For properties with multiple values this implementation 080 * creates lists of type {@code Object} and stores them. If a property is assigned 081 * another value, the value is added to the list. This can cause problems if the 082 * map passed to the constructor already contains lists of other types. This 083 * should be avoided, otherwise it cannot be guaranteed that the application 084 * might throw {@code ClassCastException} exceptions later. 085 * </p> 086 * 087 * @author Emmanuel Bourg 088 * @since 1.1 089 */ 090public 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(final Map<String, ?> map) 106 { 107 this.map = (Map<String, Object>) map; 108 } 109 110 /** 111 * Creates a new instance of {@code MapConfiguration} which uses the 112 * specified {@code Properties} object as its data store. All changes of 113 * this configuration affect the given {@code Properties} object and 114 * vice versa. Note that while {@code Properties} actually 115 * implements {@code Map<Object, Object>}, we expect it to contain only 116 * string keys. Other key types will lead to {@code ClassCastException} 117 * exceptions on certain methods. 118 * 119 * @param props the {@code Properties} object defining the content of this 120 * configuration 121 * @since 1.8 122 */ 123 public MapConfiguration(final Properties props) 124 { 125 map = convertPropertiesToMap(props); 126 } 127 128 /** 129 * Return the Map decorated by this configuration. 130 * 131 * @return the map this configuration is based onto 132 */ 133 public Map<String, Object> getMap() 134 { 135 return map; 136 } 137 138 /** 139 * Returns the flag whether trimming of property values is disabled. 140 * 141 * @return <b>true</b> if trimming of property values is disabled; 142 * <b>false</b> otherwise 143 * @since 1.7 144 */ 145 public boolean isTrimmingDisabled() 146 { 147 return trimmingDisabled; 148 } 149 150 /** 151 * Sets a flag whether trimming of property values is disabled. This flag is 152 * only evaluated if list splitting is enabled. Refer to the header comment 153 * for more information about list splitting and trimming. 154 * 155 * @param trimmingDisabled a flag whether trimming of property values should 156 * be disabled 157 * @since 1.7 158 */ 159 public void setTrimmingDisabled(final boolean trimmingDisabled) 160 { 161 this.trimmingDisabled = trimmingDisabled; 162 } 163 164 @Override 165 protected Object getPropertyInternal(final String key) 166 { 167 final Object value = map.get(key); 168 if (value instanceof String) 169 { 170 final Collection<String> list = getListDelimiterHandler().split((String) value, !isTrimmingDisabled()); 171 return list.size() > 1 ? list : list.iterator().next(); 172 } 173 return value; 174 } 175 176 @Override 177 protected void addPropertyDirect(final String key, final Object value) 178 { 179 final Object previousValue = getProperty(key); 180 181 if (previousValue == null) 182 { 183 map.put(key, value); 184 } 185 else if (previousValue instanceof List) 186 { 187 // the value is added to the existing list 188 // Note: This is problematic. See header comment! 189 ((List<Object>) previousValue).add(value); 190 } 191 else 192 { 193 // the previous value is replaced by a list containing the previous value and the new value 194 final List<Object> list = new ArrayList<>(); 195 list.add(previousValue); 196 list.add(value); 197 198 map.put(key, list); 199 } 200 } 201 202 @Override 203 protected boolean isEmptyInternal() 204 { 205 return map.isEmpty(); 206 } 207 208 @Override 209 protected boolean containsKeyInternal(final String key) 210 { 211 return map.containsKey(key); 212 } 213 214 @Override 215 protected void clearPropertyDirect(final String key) 216 { 217 map.remove(key); 218 } 219 220 @Override 221 protected Iterator<String> getKeysInternal() 222 { 223 return map.keySet().iterator(); 224 } 225 226 @Override 227 protected int sizeInternal() 228 { 229 return map.size(); 230 } 231 232 /** 233 * Returns a copy of this object. The returned configuration will contain 234 * the same properties as the original. Event listeners are not cloned. 235 * 236 * @return the copy 237 * @since 1.3 238 */ 239 @Override 240 public Object clone() 241 { 242 try 243 { 244 final MapConfiguration copy = (MapConfiguration) super.clone(); 245 // Safe because ConfigurationUtils returns a map of the same types. 246 @SuppressWarnings("unchecked") 247 final 248 Map<String, Object> clonedMap = (Map<String, Object>) ConfigurationUtils.clone(map); 249 copy.map = clonedMap; 250 copy.cloneInterpolator(this); 251 return copy; 252 } 253 catch (final CloneNotSupportedException cex) 254 { 255 // cannot happen 256 throw new ConfigurationRuntimeException(cex); 257 } 258 } 259 260 /** 261 * Helper method for converting the type of the {@code Properties} object 262 * to a supported map type. As stated by the comment of the constructor, 263 * we expect the {@code Properties} object to contain only String key; 264 * therefore, it is safe to do this cast. 265 * 266 * @param props the {@code Properties} to be copied 267 * @return a newly created map with all string keys of the properties 268 */ 269 @SuppressWarnings("unchecked") 270 private static Map<String, Object> convertPropertiesToMap(final Properties props) 271 { 272 @SuppressWarnings("rawtypes") 273 final 274 Map map = props; 275 return map; 276 } 277 278 /** 279 * Converts this object to a String suitable for debugging and logging. 280 * 281 * @since 2.3 282 */ 283 @Override 284 public String toString() 285 { 286 return getClass().getSimpleName() + " [map=" + map + ", trimmingDisabled=" + trimmingDisabled + "]"; 287 } 288}