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