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