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 1842194 2018-09-27 22:24:23Z ggregory $ 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(final 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(final 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(final boolean trimmingDisabled) 161 { 162 this.trimmingDisabled = trimmingDisabled; 163 } 164 165 @Override 166 protected Object getPropertyInternal(final String key) 167 { 168 final Object value = map.get(key); 169 if (value instanceof String) 170 { 171 final Collection<String> list = getListDelimiterHandler().split((String) value, !isTrimmingDisabled()); 172 return list.size() > 1 ? list : list.iterator().next(); 173 } 174 return value; 175 } 176 177 @Override 178 protected void addPropertyDirect(final String key, final Object value) 179 { 180 final 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 final List<Object> list = new ArrayList<>(); 196 list.add(previousValue); 197 list.add(value); 198 199 map.put(key, list); 200 } 201 } 202 203 @Override 204 protected boolean isEmptyInternal() 205 { 206 return map.isEmpty(); 207 } 208 209 @Override 210 protected boolean containsKeyInternal(final String key) 211 { 212 return map.containsKey(key); 213 } 214 215 @Override 216 protected void clearPropertyDirect(final String key) 217 { 218 map.remove(key); 219 } 220 221 @Override 222 protected Iterator<String> getKeysInternal() 223 { 224 return map.keySet().iterator(); 225 } 226 227 @Override 228 protected int sizeInternal() 229 { 230 return map.size(); 231 } 232 233 /** 234 * Returns a copy of this object. The returned configuration will contain 235 * the same properties as the original. Event listeners are not cloned. 236 * 237 * @return the copy 238 * @since 1.3 239 */ 240 @Override 241 public Object clone() 242 { 243 try 244 { 245 final MapConfiguration copy = (MapConfiguration) super.clone(); 246 // Safe because ConfigurationUtils returns a map of the same types. 247 @SuppressWarnings("unchecked") 248 final 249 Map<String, Object> clonedMap = (Map<String, Object>) ConfigurationUtils.clone(map); 250 copy.map = clonedMap; 251 copy.cloneInterpolator(this); 252 return copy; 253 } 254 catch (final CloneNotSupportedException cex) 255 { 256 // cannot happen 257 throw new ConfigurationRuntimeException(cex); 258 } 259 } 260 261 /** 262 * Helper method for converting the type of the {@code Properties} object 263 * to a supported map type. As stated by the comment of the constructor, 264 * we expect the {@code Properties} object to contain only String key; 265 * therefore, it is safe to do this cast. 266 * 267 * @param props the {@code Properties} to be copied 268 * @return a newly created map with all string keys of the properties 269 */ 270 @SuppressWarnings("unchecked") 271 private static Map<String, Object> convertPropertiesToMap(final Properties props) 272 { 273 @SuppressWarnings("rawtypes") 274 final 275 Map map = props; 276 return map; 277 } 278 279 /** 280 * Converts this object to a String suitable for debugging and logging. 281 * 282 * @since 2.3 283 */ 284 @Override 285 public String toString() 286 { 287 return getClass().getSimpleName() + " [map=" + map + ", trimmingDisabled=" + trimmingDisabled + "]"; 288 } 289}