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 * @since 1.1 088 */ 089public class MapConfiguration extends AbstractConfiguration implements Cloneable 090{ 091 /** The Map decorated by this configuration. */ 092 protected Map<String, Object> map; 093 094 /** A flag whether trimming of property values should be disabled.*/ 095 private boolean trimmingDisabled; 096 097 /** 098 * Create a Configuration decorator around the specified Map. The map is 099 * used to store the configuration properties, any change will also affect 100 * the Map. 101 * 102 * @param map the map 103 */ 104 public MapConfiguration(final Map<String, ?> map) 105 { 106 this.map = (Map<String, Object>) map; 107 } 108 109 /** 110 * Creates a new instance of {@code MapConfiguration} which uses the 111 * specified {@code Properties} object as its data store. All changes of 112 * this configuration affect the given {@code Properties} object and 113 * vice versa. Note that while {@code Properties} actually 114 * implements {@code Map<Object, Object>}, we expect it to contain only 115 * string keys. Other key types will lead to {@code ClassCastException} 116 * exceptions on certain methods. 117 * 118 * @param props the {@code Properties} object defining the content of this 119 * configuration 120 * @since 1.8 121 */ 122 public MapConfiguration(final 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(final boolean trimmingDisabled) 159 { 160 this.trimmingDisabled = trimmingDisabled; 161 } 162 163 @Override 164 protected Object getPropertyInternal(final String key) 165 { 166 final Object value = map.get(key); 167 if (value instanceof String) 168 { 169 final Collection<String> list = getListDelimiterHandler().split((String) value, !isTrimmingDisabled()); 170 return list.size() > 1 ? list : list.iterator().next(); 171 } 172 return value; 173 } 174 175 @Override 176 protected void addPropertyDirect(final String key, final Object value) 177 { 178 final Object previousValue = getProperty(key); 179 180 if (previousValue == null) 181 { 182 map.put(key, value); 183 } 184 else if (previousValue instanceof List) 185 { 186 // the value is added to the existing list 187 // Note: This is problematic. See header comment! 188 ((List<Object>) previousValue).add(value); 189 } 190 else 191 { 192 // the previous value is replaced by a list containing the previous value and the new value 193 final List<Object> list = new ArrayList<>(); 194 list.add(previousValue); 195 list.add(value); 196 197 map.put(key, list); 198 } 199 } 200 201 @Override 202 protected boolean isEmptyInternal() 203 { 204 return map.isEmpty(); 205 } 206 207 @Override 208 protected boolean containsKeyInternal(final String key) 209 { 210 return map.containsKey(key); 211 } 212 213 @Override 214 protected void clearPropertyDirect(final String key) 215 { 216 map.remove(key); 217 } 218 219 @Override 220 protected Iterator<String> getKeysInternal() 221 { 222 return map.keySet().iterator(); 223 } 224 225 @Override 226 protected int sizeInternal() 227 { 228 return map.size(); 229 } 230 231 /** 232 * Returns a copy of this object. The returned configuration will contain 233 * the same properties as the original. Event listeners are not cloned. 234 * 235 * @return the copy 236 * @since 1.3 237 */ 238 @Override 239 public Object clone() 240 { 241 try 242 { 243 final MapConfiguration copy = (MapConfiguration) super.clone(); 244 // Safe because ConfigurationUtils returns a map of the same types. 245 @SuppressWarnings("unchecked") 246 final 247 Map<String, Object> clonedMap = (Map<String, Object>) ConfigurationUtils.clone(map); 248 copy.map = clonedMap; 249 copy.cloneInterpolator(this); 250 return copy; 251 } 252 catch (final CloneNotSupportedException cex) 253 { 254 // cannot happen 255 throw new ConfigurationRuntimeException(cex); 256 } 257 } 258 259 /** 260 * Helper method for converting the type of the {@code Properties} object 261 * to a supported map type. As stated by the comment of the constructor, 262 * we expect the {@code Properties} object to contain only String key; 263 * therefore, it is safe to do this cast. 264 * 265 * @param props the {@code Properties} to be copied 266 * @return a newly created map with all string keys of the properties 267 */ 268 @SuppressWarnings("unchecked") 269 private static Map<String, Object> convertPropertiesToMap(final Properties props) 270 { 271 @SuppressWarnings("rawtypes") 272 final 273 Map map = props; 274 return map; 275 } 276 277 /** 278 * Converts this object to a String suitable for debugging and logging. 279 * 280 * @since 2.3 281 */ 282 @Override 283 public String toString() 284 { 285 return getClass().getSimpleName() + " [map=" + map + ", trimmingDisabled=" + trimmingDisabled + "]"; 286 } 287}