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 018 package org.apache.commons.configuration; 019 020 import java.util.ArrayList; 021 import java.util.Iterator; 022 import java.util.List; 023 import java.util.Map; 024 025 /** 026 * <p> 027 * A Map based Configuration. 028 * </p> 029 * <p> 030 * This implementation of the <code>Configuration</code> interface is 031 * initialized with a <code>java.util.Map</code>. The methods of the 032 * <code>Configuration</code> interface are implemented on top of the content of 033 * this map. The following storage scheme is used: 034 * </p> 035 * <p> 036 * Property keys are directly mapped to map keys, i.e. the 037 * <code>getProperty()</code> method directly performs a <code>get()</code> on 038 * the map. Analogously, <code>setProperty()</code> or 039 * <code>addProperty()</code> operations write new data into the map. If a value 040 * is added to an existing property, a <code>java.util.List</code> is created, 041 * which stores the values of this property. 042 * </p> 043 * <p> 044 * An important use case of this class is to treat a map as a 045 * <code>Configuration</code> allowing access to its data through the richer 046 * interface. This can be a bit problematic in some cases because the map may 047 * contain values that need not adhere to the default storage scheme used by 048 * typical configuration implementations, e.g. regarding lists. In such cases 049 * care must be taken when manipulating the data through the 050 * <code>Configuration</code> interface, e.g. by calling 051 * <code>addProperty()</code>; results may be different than expected. 052 * </p> 053 * <p> 054 * An important point is the handling of list delimiters: If delimiter parsing 055 * is enabled (which it is per default), <code>getProperty()</code> checks 056 * whether the value of a property is a string and whether it contains the list 057 * delimiter character. If this is the case, the value is split at the delimiter 058 * resulting in a list. This split operation typically also involves trimming 059 * the single values as the list delimiter character may be surrounded by 060 * whitespace. Trimming can be disabled with the 061 * {@link #setTrimmingDisabled(boolean)} method. The whole list splitting 062 * behavior can be disabled using the 063 * {@link #setDelimiterParsingDisabled(boolean)} method. 064 * </p> 065 * <p> 066 * Notice that list splitting is only performed for single string values. If a 067 * property has multiple values, the single values are not split even if they 068 * contain the list delimiter character. 069 * </p> 070 * <p> 071 * As the underlying <code>Map</code> is directly used as store of the property 072 * values, the thread-safety of this <code>Configuration</code> implementation 073 * depends on the map passed to the constructor. 074 * </p> 075 * 076 * @author Emmanuel Bourg 077 * @version $Revision: 763367 $, $Date: 2009-04-08 21:56:10 +0200 (Mi, 08. Apr 2009) $ 078 * @since 1.1 079 */ 080 public class MapConfiguration extends AbstractConfiguration implements Cloneable 081 { 082 /** The Map decorated by this configuration. */ 083 protected Map map; 084 085 /** A flag whether trimming of property values should be disabled.*/ 086 private boolean trimmingDisabled; 087 088 /** 089 * Create a Configuration decorator around the specified Map. The map is 090 * used to store the configuration properties, any change will also affect 091 * the Map. 092 * 093 * @param map the map 094 */ 095 public MapConfiguration(Map map) 096 { 097 this.map = map; 098 } 099 100 /** 101 * Return the Map decorated by this configuration. 102 * 103 * @return the map this configuration is based onto 104 */ 105 public Map getMap() 106 { 107 return map; 108 } 109 110 /** 111 * Returns the flag whether trimming of property values is disabled. 112 * 113 * @return <b>true</b> if trimming of property values is disabled; 114 * <b>false</b> otherwise 115 * @since 1.7 116 */ 117 public boolean isTrimmingDisabled() 118 { 119 return trimmingDisabled; 120 } 121 122 /** 123 * Sets a flag whether trimming of property values is disabled. This flag is 124 * only evaluated if list splitting is enabled. Refer to the header comment 125 * for more information about list splitting and trimming. 126 * 127 * @param trimmingDisabled a flag whether trimming of property values should 128 * be disabled 129 * @since 1.7 130 */ 131 public void setTrimmingDisabled(boolean trimmingDisabled) 132 { 133 this.trimmingDisabled = trimmingDisabled; 134 } 135 136 public Object getProperty(String key) 137 { 138 Object value = map.get(key); 139 if ((value instanceof String) && (!isDelimiterParsingDisabled())) 140 { 141 List list = PropertyConverter.split((String) value, getListDelimiter(), !isTrimmingDisabled()); 142 return list.size() > 1 ? list : list.get(0); 143 } 144 else 145 { 146 return value; 147 } 148 } 149 150 protected void addPropertyDirect(String key, Object value) 151 { 152 Object previousValue = getProperty(key); 153 154 if (previousValue == null) 155 { 156 map.put(key, value); 157 } 158 else if (previousValue instanceof List) 159 { 160 // the value is added to the existing list 161 ((List) previousValue).add(value); 162 } 163 else 164 { 165 // the previous value is replaced by a list containing the previous value and the new value 166 List list = new ArrayList(); 167 list.add(previousValue); 168 list.add(value); 169 170 map.put(key, list); 171 } 172 } 173 174 public boolean isEmpty() 175 { 176 return map.isEmpty(); 177 } 178 179 public boolean containsKey(String key) 180 { 181 return map.containsKey(key); 182 } 183 184 protected void clearPropertyDirect(String key) 185 { 186 map.remove(key); 187 } 188 189 public Iterator getKeys() 190 { 191 return map.keySet().iterator(); 192 } 193 194 /** 195 * Returns a copy of this object. The returned configuration will contain 196 * the same properties as the original. Event listeners are not cloned. 197 * 198 * @return the copy 199 * @since 1.3 200 */ 201 public Object clone() 202 { 203 try 204 { 205 MapConfiguration copy = (MapConfiguration) super.clone(); 206 copy.clearConfigurationListeners(); 207 copy.map = (Map) ConfigurationUtils.clone(map); 208 return copy; 209 } 210 catch (CloneNotSupportedException cex) 211 { 212 // cannot happen 213 throw new ConfigurationRuntimeException(cex); 214 } 215 } 216 }