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 * https://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.Objects; 026import java.util.Properties; 027 028import org.apache.commons.configuration2.ex.ConfigurationRuntimeException; 029 030/** 031 * <p> 032 * A Map based Configuration. 033 * </p> 034 * <p> 035 * This implementation of the {@code Configuration} interface is initialized with a {@link java.util.Map}. The methods 036 * of the {@code Configuration} interface are implemented on top of the content of this map. The following storage 037 * scheme is used: 038 * </p> 039 * <p> 040 * Property keys are directly mapped to map keys, i.e. the {@code getProperty()} method directly performs a 041 * {@code get()} on the map. Analogously, {@code setProperty()} or {@code addProperty()} operations write new data into 042 * the map. If a value is added to an existing property, a {@link java.util.List} is created, which stores the values of 043 * this property. 044 * </p> 045 * <p> 046 * An important use case of this class is to treat a map as a {@code Configuration} allowing access to its data through 047 * the richer interface. This can be a bit problematic in some cases because the map may contain values that need not 048 * adhere to the default storage scheme used by typical configuration implementations, for example regarding lists. In such 049 * cases care must be taken when manipulating the data through the {@code Configuration} interface, for example by calling 050 * {@code addProperty()}; results may be different than expected. 051 * </p> 052 * <p> 053 * The handling of list delimiters is a bit different for this configuration implementation: When a property of type 054 * String is queried, it is passed to the current {@link org.apache.commons.configuration2.convert.ListDelimiterHandler 055 * ListDelimiterHandler} which may generate multiple values. Note that per default a list delimiter handler is set which 056 * does not do any list splitting, so this feature is disabled. It can be enabled by setting a properly configured 057 * {@code ListDelimiterHandler} implementation, for example a 058 * {@link org.apache.commons.configuration2.convert.DefaultListDelimiterHandler DefaultListDelimiterHandler} object. 059 * </p> 060 * <p> 061 * Notice that list splitting is only performed for single string values. If a property has multiple values, the single 062 * values are not split even if they contain the list delimiter character. 063 * </p> 064 * <p> 065 * As the underlying {@code Map} is directly used as store of the property values, the thread-safety of this 066 * {@code Configuration} implementation depends on the map passed to the constructor. 067 * </p> 068 * <p> 069 * Notes about type safety: For properties with multiple values this implementation creates lists of type {@code Object} 070 * and stores them. If a property is assigned another value, the value is added to the list. This can cause problems if 071 * the map passed to the constructor already contains lists of other types. This should be avoided, otherwise it cannot 072 * be guaranteed that the application might throw {@code ClassCastException} exceptions later. 073 * </p> 074 * 075 * @since 1.1 076 */ 077public class MapConfiguration extends AbstractConfiguration implements Cloneable { 078 079 /** 080 * Helper method for converting the type of the {@code Properties} object to a supported map type. As stated by the 081 * comment of the constructor, we expect the {@code Properties} object to contain only String key; therefore, it is safe 082 * to do this cast. 083 * 084 * @param props the {@code Properties} to be copied. 085 * @return a newly created map with all string keys of the properties. 086 */ 087 @SuppressWarnings("unchecked") 088 private static Map<String, Object> toMap(final Properties props) { 089 @SuppressWarnings("rawtypes") 090 final Map map = props; 091 return map; 092 } 093 094 /** The Map decorated by this configuration. */ 095 protected Map<String, Object> map; 096 097 /** A flag whether trimming of property values should be disabled. */ 098 private boolean trimmingDisabled; 099 100 /** 101 * Create a Configuration decorator around the specified Map. The map is used to store the configuration properties, any 102 * change will also affect the Map. 103 * 104 * @param map the map. 105 */ 106 public MapConfiguration(final Map<String, ?> map) { 107 this.map = (Map<String, Object>) Objects.requireNonNull(map, "map"); 108 } 109 110 /** 111 * Creates a new instance of {@code MapConfiguration} which uses the specified {@code Properties} object as its data 112 * store. All changes of this configuration affect the given {@code Properties} object and vice versa. Note that while 113 * {@code Properties} actually implements {@code Map<Object, Object>}, we expect it to contain only string keys. Other 114 * key types will lead to {@code ClassCastException} exceptions on certain methods. 115 * 116 * @param props the {@code Properties} object defining the content of this configuration. 117 * @since 1.8 118 */ 119 public MapConfiguration(final Properties props) { 120 map = toMap(Objects.requireNonNull(props)); 121 } 122 123 @Override 124 protected void addPropertyDirect(final String key, final Object value) { 125 final Object previousValue = getProperty(key); 126 127 if (previousValue == null) { 128 map.put(key, value); 129 } else if (previousValue instanceof List) { 130 // the value is added to the existing list 131 // Note: This is problematic. See header comment! 132 ((List<Object>) previousValue).add(value); 133 } else { 134 // the previous value is replaced by a list containing the previous value and the new value 135 final List<Object> list = new ArrayList<>(); 136 list.add(previousValue); 137 list.add(value); 138 139 map.put(key, list); 140 } 141 } 142 143 @Override 144 protected void clearPropertyDirect(final String key) { 145 map.remove(key); 146 } 147 148 /** 149 * Returns a copy of this object. The returned configuration will contain the same properties as the original. Event listeners are not cloned. 150 * 151 * @return the copy. 152 * @since 1.3 153 */ 154 @Override 155 public Object clone() { 156 try { 157 final MapConfiguration copy = (MapConfiguration) super.clone(); 158 copy.map = ConfigurationUtils.clone(map); 159 copy.cloneInterpolator(this); 160 return copy; 161 } catch (final CloneNotSupportedException cex) { 162 // cannot happen 163 throw new ConfigurationRuntimeException(cex); 164 } 165 } 166 167 @Override 168 protected boolean containsKeyInternal(final String key) { 169 return map.containsKey(key); 170 } 171 172 /** 173 * Tests whether this configuration contains one or more matches to this value. This operation stops at first match 174 * but may be more expensive than the containsKey method. 175 * 176 * @since 2.11.0 177 */ 178 @Override 179 protected boolean containsValueInternal(final Object value) { 180 return value != null && map.containsValue(value); 181 } 182 183 @Override 184 protected Iterator<String> getKeysInternal() { 185 return map.keySet().iterator(); 186 } 187 188 /** 189 * Gets the Map decorated by this configuration. 190 * 191 * @return the map this configuration is based onto. 192 */ 193 public Map<String, Object> getMap() { 194 return map; 195 } 196 197 @Override 198 protected Object getPropertyInternal(final String key) { 199 final Object value = map.get(key); 200 if (value instanceof String) { 201 final Collection<String> list = getListDelimiterHandler().split((String) value, !isTrimmingDisabled()); 202 return list.size() > 1 ? list : list.iterator().next(); 203 } 204 return value; 205 } 206 207 @Override 208 protected boolean isEmptyInternal() { 209 return map.isEmpty(); 210 } 211 212 /** 213 * Tests whether the flag whether trimming of property values is disabled. 214 * 215 * @return <strong>true</strong> if trimming of property values is disabled; <strong>false</strong> otherwise. 216 * @since 1.7 217 */ 218 public boolean isTrimmingDisabled() { 219 return trimmingDisabled; 220 } 221 222 /** 223 * Sets a flag whether trimming of property values is disabled. This flag is only evaluated if list splitting is 224 * enabled. Refer to the header comment for more information about list splitting and trimming. 225 * 226 * @param trimmingDisabled a flag whether trimming of property values should be disabled. 227 * @since 1.7 228 */ 229 public void setTrimmingDisabled(final boolean trimmingDisabled) { 230 this.trimmingDisabled = trimmingDisabled; 231 } 232 233 @Override 234 protected int sizeInternal() { 235 return map.size(); 236 } 237 238 /** 239 * Converts this object to a String suitable for debugging and logging. 240 * 241 * @since 2.3 242 */ 243 @Override 244 public String toString() { 245 return getClass().getSimpleName() + " [map=" + map + ", trimmingDisabled=" + trimmingDisabled + "]"; 246 } 247}