1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package org.apache.commons.configuration2; 19 20 import java.util.ArrayList; 21 import java.util.Collection; 22 import java.util.Iterator; 23 import java.util.List; 24 import java.util.Map; 25 import java.util.Properties; 26 27 import org.apache.commons.configuration2.ex.ConfigurationRuntimeException; 28 29 /** 30 * <p> 31 * A Map based Configuration. 32 * </p> 33 * <p> 34 * This implementation of the {@code Configuration} interface is initialized with a {@link java.util.Map}. The methods 35 * of the {@code Configuration} interface are implemented on top of the content of this map. The following storage 36 * scheme is used: 37 * </p> 38 * <p> 39 * Property keys are directly mapped to map keys, i.e. the {@code getProperty()} method directly performs a 40 * {@code get()} on the map. Analogously, {@code setProperty()} or {@code addProperty()} operations write new data into 41 * the map. If a value is added to an existing property, a {@link java.util.List} is created, which stores the values of 42 * this property. 43 * </p> 44 * <p> 45 * An important use case of this class is to treat a map as a {@code Configuration} allowing access to its data through 46 * the richer interface. This can be a bit problematic in some cases because the map may contain values that need not 47 * adhere to the default storage scheme used by typical configuration implementations, e.g. regarding lists. In such 48 * cases care must be taken when manipulating the data through the {@code Configuration} interface, e.g. by calling 49 * {@code addProperty()}; results may be different than expected. 50 * </p> 51 * <p> 52 * The handling of list delimiters is a bit different for this configuration implementation: When a property of type 53 * String is queried, it is passed to the current {@link org.apache.commons.configuration2.convert.ListDelimiterHandler 54 * ListDelimiterHandler} which may generate multiple values. Note that per default a list delimiter handler is set which 55 * does not do any list splitting, so this feature is disabled. It can be enabled by setting a properly configured 56 * {@code ListDelimiterHandler} implementation, e.g. a 57 * {@link org.apache.commons.configuration2.convert.DefaultListDelimiterHandler DefaultListDelimiterHandler} object. 58 * </p> 59 * <p> 60 * Notice that list splitting is only performed for single string values. If a property has multiple values, the single 61 * values are not split even if they contain the list delimiter character. 62 * </p> 63 * <p> 64 * As the underlying {@code Map} is directly used as store of the property values, the thread-safety of this 65 * {@code Configuration} implementation depends on the map passed to the constructor. 66 * </p> 67 * <p> 68 * Notes about type safety: For properties with multiple values this implementation creates lists of type {@code Object} 69 * and stores them. If a property is assigned another value, the value is added to the list. This can cause problems if 70 * the map passed to the constructor already contains lists of other types. This should be avoided, otherwise it cannot 71 * be guaranteed that the application might throw {@code ClassCastException} exceptions later. 72 * </p> 73 * 74 * @since 1.1 75 */ 76 public class MapConfiguration extends AbstractConfiguration implements Cloneable { 77 /** The Map decorated by this configuration. */ 78 protected Map<String, Object> map; 79 80 /** A flag whether trimming of property values should be disabled. */ 81 private boolean trimmingDisabled; 82 83 /** 84 * Create a Configuration decorator around the specified Map. The map is used to store the configuration properties, any 85 * change will also affect the Map. 86 * 87 * @param map the map 88 */ 89 public MapConfiguration(final Map<String, ?> map) { 90 this.map = (Map<String, Object>) map; 91 } 92 93 /** 94 * Creates a new instance of {@code MapConfiguration} which uses the specified {@code Properties} object as its data 95 * store. All changes of this configuration affect the given {@code Properties} object and vice versa. Note that while 96 * {@code Properties} actually implements {@code Map<Object, Object>}, we expect it to contain only string keys. Other 97 * key types will lead to {@code ClassCastException} exceptions on certain methods. 98 * 99 * @param props the {@code Properties} object defining the content of this configuration 100 * @since 1.8 101 */ 102 public MapConfiguration(final Properties props) { 103 map = toMap(props); 104 } 105 106 /** 107 * Gets the Map decorated by this configuration. 108 * 109 * @return the map this configuration is based onto 110 */ 111 public Map<String, Object> getMap() { 112 return map; 113 } 114 115 /** 116 * Returns the flag whether trimming of property values is disabled. 117 * 118 * @return <b>true</b> if trimming of property values is disabled; <b>false</b> otherwise 119 * @since 1.7 120 */ 121 public boolean isTrimmingDisabled() { 122 return trimmingDisabled; 123 } 124 125 /** 126 * Sets a flag whether trimming of property values is disabled. This flag is only evaluated if list splitting is 127 * enabled. Refer to the header comment for more information about list splitting and trimming. 128 * 129 * @param trimmingDisabled a flag whether trimming of property values should be disabled 130 * @since 1.7 131 */ 132 public void setTrimmingDisabled(final boolean trimmingDisabled) { 133 this.trimmingDisabled = trimmingDisabled; 134 } 135 136 @Override 137 protected Object getPropertyInternal(final String key) { 138 final Object value = map.get(key); 139 if (value instanceof String) { 140 final Collection<String> list = getListDelimiterHandler().split((String) value, !isTrimmingDisabled()); 141 return list.size() > 1 ? list : list.iterator().next(); 142 } 143 return value; 144 } 145 146 @Override 147 protected void addPropertyDirect(final String key, final Object value) { 148 final Object previousValue = getProperty(key); 149 150 if (previousValue == null) { 151 map.put(key, value); 152 } else if (previousValue instanceof List) { 153 // the value is added to the existing list 154 // Note: This is problematic. See header comment! 155 ((List<Object>) previousValue).add(value); 156 } else { 157 // the previous value is replaced by a list containing the previous value and the new value 158 final List<Object> list = new ArrayList<>(); 159 list.add(previousValue); 160 list.add(value); 161 162 map.put(key, list); 163 } 164 } 165 166 @Override 167 protected boolean isEmptyInternal() { 168 return map.isEmpty(); 169 } 170 171 @Override 172 protected boolean containsKeyInternal(final String key) { 173 return map.containsKey(key); 174 } 175 176 @Override 177 protected void clearPropertyDirect(final String key) { 178 map.remove(key); 179 } 180 181 @Override 182 protected Iterator<String> getKeysInternal() { 183 return map.keySet().iterator(); 184 } 185 186 @Override 187 protected int sizeInternal() { 188 return map.size(); 189 } 190 191 /** 192 * Returns a copy of this object. The returned configuration will contain the same properties as the original. Event 193 * listeners are not cloned. 194 * 195 * @return the copy 196 * @since 1.3 197 */ 198 @Override 199 public Object clone() { 200 try { 201 final MapConfiguration copy = (MapConfiguration) super.clone(); 202 // Safe because ConfigurationUtils returns a map of the same types. 203 @SuppressWarnings("unchecked") 204 final Map<String, Object> clonedMap = (Map<String, Object>) ConfigurationUtils.clone(map); 205 copy.map = clonedMap; 206 copy.cloneInterpolator(this); 207 return copy; 208 } catch (final CloneNotSupportedException cex) { 209 // cannot happen 210 throw new ConfigurationRuntimeException(cex); 211 } 212 } 213 214 /** 215 * Helper method for converting the type of the {@code Properties} object to a supported map type. As stated by the 216 * comment of the constructor, we expect the {@code Properties} object to contain only String key; therefore, it is safe 217 * to do this cast. 218 * 219 * @param props the {@code Properties} to be copied 220 * @return a newly created map with all string keys of the properties 221 */ 222 @SuppressWarnings("unchecked") 223 private static Map<String, Object> toMap(final Properties props) { 224 @SuppressWarnings("rawtypes") 225 final Map map = props; 226 return map; 227 } 228 229 /** 230 * Converts this object to a String suitable for debugging and logging. 231 * 232 * @since 2.3 233 */ 234 @Override 235 public String toString() { 236 return getClass().getSimpleName() + " [map=" + map + ", trimmingDisabled=" + trimmingDisabled + "]"; 237 } 238 }