Coverage Report - org.apache.commons.configuration.MapConfiguration
 
Classes in this File Line Coverage Branch Coverage Complexity
MapConfiguration
95%
40/42
93%
15/16
1,846
 
 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.configuration;
 19  
 
 20  
 import java.util.ArrayList;
 21  
 import java.util.HashMap;
 22  
 import java.util.Iterator;
 23  
 import java.util.List;
 24  
 import java.util.Map;
 25  
 import java.util.Properties;
 26  
 
 27  
 /**
 28  
  * <p>
 29  
  * A Map based Configuration.
 30  
  * </p>
 31  
  * <p>
 32  
  * This implementation of the {@code Configuration} interface is
 33  
  * initialized with a {@code java.util.Map}. The methods of the
 34  
  * {@code Configuration} interface are implemented on top of the content of
 35  
  * this map. The following storage scheme is used:
 36  
  * </p>
 37  
  * <p>
 38  
  * Property keys are directly mapped to map keys, i.e. the
 39  
  * {@code getProperty()} method directly performs a {@code get()} on
 40  
  * the map. Analogously, {@code setProperty()} or
 41  
  * {@code addProperty()} operations write new data into the map. If a value
 42  
  * is added to an existing property, a {@code java.util.List} is created,
 43  
  * which stores the values of this property.
 44  
  * </p>
 45  
  * <p>
 46  
  * An important use case of this class is to treat a map as a
 47  
  * {@code Configuration} allowing access to its data through the richer
 48  
  * interface. This can be a bit problematic in some cases because the map may
 49  
  * contain values that need not adhere to the default storage scheme used by
 50  
  * typical configuration implementations, e.g. regarding lists. In such cases
 51  
  * care must be taken when manipulating the data through the
 52  
  * {@code Configuration} interface, e.g. by calling
 53  
  * {@code addProperty()}; results may be different than expected.
 54  
  * </p>
 55  
  * <p>
 56  
  * An important point is the handling of list delimiters: If delimiter parsing
 57  
  * is enabled (which it is per default), {@code getProperty()} checks
 58  
  * whether the value of a property is a string and whether it contains the list
 59  
  * delimiter character. If this is the case, the value is split at the delimiter
 60  
  * resulting in a list. This split operation typically also involves trimming
 61  
  * the single values as the list delimiter character may be surrounded by
 62  
  * whitespace. Trimming can be disabled with the
 63  
  * {@link #setTrimmingDisabled(boolean)} method. The whole list splitting
 64  
  * behavior can be disabled using the
 65  
  * {@link #setDelimiterParsingDisabled(boolean)} method.
 66  
  * </p>
 67  
  * <p>
 68  
  * Notice that list splitting is only performed for single string values. If a
 69  
  * property has multiple values, the single values are not split even if they
 70  
  * contain the list delimiter character.
 71  
  * </p>
 72  
  * <p>
 73  
  * As the underlying {@code Map} is directly used as store of the property
 74  
  * values, the thread-safety of this {@code Configuration} implementation
 75  
  * depends on the map passed to the constructor.
 76  
  * </p>
 77  
  * <p>
 78  
  * Notes about type safety: For properties with multiple values this implementation
 79  
  * creates lists of type {@code Object} and stores them. If a property is assigned
 80  
  * another value, the value is added to the list. This can cause problems if the
 81  
  * map passed to the constructor already contains lists of other types. This
 82  
  * should be avoided, otherwise it cannot be guaranteed that the application
 83  
  * might throw {@code ClassCastException} exceptions later.
 84  
  * </p>
 85  
  *
 86  
  * @author Emmanuel Bourg
 87  
  * @version $Id: MapConfiguration.java 1210171 2011-12-04 18:32:07Z oheger $
 88  
  * @since 1.1
 89  
  */
 90  
 public class MapConfiguration extends AbstractConfiguration implements Cloneable
 91  
 {
 92  
     /** The Map decorated by this configuration. */
 93  
     protected Map<String, Object> map;
 94  
 
 95  
     /** A flag whether trimming of property values should be disabled.*/
 96  
     private boolean trimmingDisabled;
 97  
 
 98  
     /**
 99  
      * Create a Configuration decorator around the specified Map. The map is
 100  
      * used to store the configuration properties, any change will also affect
 101  
      * the Map.
 102  
      *
 103  
      * @param map the map
 104  
      */
 105  
     public MapConfiguration(Map<String, Object> map)
 106  50
     {
 107  50
         this.map = map;
 108  50
     }
 109  
 
 110  
     /**
 111  
      * Creates a new instance of {@code MapConfiguration} and initializes its
 112  
      * content from the specified {@code Properties} object. The resulting
 113  
      * configuration is not connected to the {@code Properties} object, but all
 114  
      * keys which are strings are copied (keys of other types are ignored).
 115  
      *
 116  
      * @param props the {@code Properties} object defining the content of this
 117  
      *        configuration
 118  
      * @throws NullPointerException if the {@code Properties} object is
 119  
      *         <b>null</b>
 120  
      * @since 1.8
 121  
      */
 122  
     public MapConfiguration(Properties props)
 123  38
     {
 124  38
         map = convertPropertiesToMap(props);
 125  38
     }
 126  
 
 127  
     /**
 128  
      * Return the Map decorated by this configuration.
 129  
      *
 130  
      * @return the map this configuration is based onto
 131  
      */
 132  
     public Map<String, Object> getMap()
 133  
     {
 134  6
         return map;
 135  
     }
 136  
 
 137  
     /**
 138  
      * Returns the flag whether trimming of property values is disabled.
 139  
      *
 140  
      * @return <b>true</b> if trimming of property values is disabled;
 141  
      *         <b>false</b> otherwise
 142  
      * @since 1.7
 143  
      */
 144  
     public boolean isTrimmingDisabled()
 145  
     {
 146  3081
         return trimmingDisabled;
 147  
     }
 148  
 
 149  
     /**
 150  
      * Sets a flag whether trimming of property values is disabled. This flag is
 151  
      * only evaluated if list splitting is enabled. Refer to the header comment
 152  
      * for more information about list splitting and trimming.
 153  
      *
 154  
      * @param trimmingDisabled a flag whether trimming of property values should
 155  
      *        be disabled
 156  
      * @since 1.7
 157  
      */
 158  
     public void setTrimmingDisabled(boolean trimmingDisabled)
 159  
     {
 160  1
         this.trimmingDisabled = trimmingDisabled;
 161  1
     }
 162  
 
 163  
     public Object getProperty(String key)
 164  
     {
 165  3144
         Object value = map.get(key);
 166  3144
         if ((value instanceof String) && (!isDelimiterParsingDisabled()))
 167  
         {
 168  3081
             List<String> list = PropertyConverter.split((String) value, getListDelimiter(), !isTrimmingDisabled());
 169  3081
             return list.size() > 1 ? list : list.get(0);
 170  
         }
 171  
         else
 172  
         {
 173  63
             return value;
 174  
         }
 175  
     }
 176  
 
 177  
     @Override
 178  
     protected void addPropertyDirect(String key, Object value)
 179  
     {
 180  30
         Object previousValue = getProperty(key);
 181  
 
 182  30
         if (previousValue == null)
 183  
         {
 184  26
             map.put(key, value);
 185  
         }
 186  4
         else if (previousValue instanceof List)
 187  
         {
 188  
             // the value is added to the existing list
 189  
             // Note: This is problematic. See header comment!
 190  2
             ((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  2
             List<Object> list = new ArrayList<Object>();
 196  2
             list.add(previousValue);
 197  2
             list.add(value);
 198  
 
 199  2
             map.put(key, list);
 200  
         }
 201  30
     }
 202  
 
 203  
     public boolean isEmpty()
 204  
     {
 205  4
         return map.isEmpty();
 206  
     }
 207  
 
 208  
     public boolean containsKey(String key)
 209  
     {
 210  59
         return map.containsKey(key);
 211  
     }
 212  
 
 213  
     @Override
 214  
     protected void clearPropertyDirect(String key)
 215  
     {
 216  10
         map.remove(key);
 217  10
     }
 218  
 
 219  
     public Iterator<String> getKeys()
 220  
     {
 221  55
         return map.keySet().iterator();
 222  
     }
 223  
 
 224  
     /**
 225  
      * Returns a copy of this object. The returned configuration will contain
 226  
      * the same properties as the original. Event listeners are not cloned.
 227  
      *
 228  
      * @return the copy
 229  
      * @since 1.3
 230  
      */
 231  
     @Override
 232  
     public Object clone()
 233  
     {
 234  
         try
 235  
         {
 236  2
             MapConfiguration copy = (MapConfiguration) super.clone();
 237  2
             copy.clearConfigurationListeners();
 238  
             // Safe because ConfigurationUtils returns a map of the same types.
 239  
             @SuppressWarnings("unchecked")
 240  2
             Map<String, Object> clonedMap = (Map<String, Object>) ConfigurationUtils.clone(map);
 241  2
             copy.map = clonedMap;
 242  2
             return copy;
 243  
         }
 244  0
         catch (CloneNotSupportedException cex)
 245  
         {
 246  
             // cannot happen
 247  0
             throw new ConfigurationRuntimeException(cex);
 248  
         }
 249  
     }
 250  
 
 251  
     /**
 252  
      * Helper method for copying all string keys from the given
 253  
      * {@code Properties} object to a newly created map.
 254  
      *
 255  
      * @param props the {@code Properties} to be copied
 256  
      * @return a newly created map with all string keys of the properties
 257  
      */
 258  
     private static Map<String, Object> convertPropertiesToMap(Properties props)
 259  
     {
 260  38
         Map<String, Object> map = new HashMap<String, Object>();
 261  38
         for (Map.Entry<Object, Object> e : props.entrySet())
 262  
         {
 263  2025
             if (e.getKey() instanceof String)
 264  
             {
 265  2025
                 map.put((String) e.getKey(), e.getValue());
 266  
             }
 267  
         }
 268  38
         return map;
 269  
     }
 270  
 }