/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.configuration;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

A Map based Configuration.

This implementation of the Configuration interface is initialized with a java.util.Map. The methods of the Configuration interface are implemented on top of the content of this map. The following storage scheme is used:

Property keys are directly mapped to map keys, i.e. the getProperty() method directly performs a get() on the map. Analogously, setProperty() or addProperty() operations write new data into the map. If a value is added to an existing property, a java.util.List is created, which stores the values of this property.

An important use case of this class is to treat a map as a Configuration allowing access to its data through the richer interface. This can be a bit problematic in some cases because the map may contain values that need not adhere to the default storage scheme used by typical configuration implementations, e.g. regarding lists. In such cases care must be taken when manipulating the data through the Configuration interface, e.g. by calling addProperty(); results may be different than expected.

An important point is the handling of list delimiters: If delimiter parsing is enabled (which it is per default), getProperty() checks whether the value of a property is a string and whether it contains the list delimiter character. If this is the case, the value is split at the delimiter resulting in a list. This split operation typically also involves trimming the single values as the list delimiter character may be surrounded by whitespace. Trimming can be disabled with the setTrimmingDisabled(boolean) method. The whole list splitting behavior can be disabled using the AbstractConfiguration.setDelimiterParsingDisabled(boolean) method.

Notice that list splitting is only performed for single string values. If a property has multiple values, the single values are not split even if they contain the list delimiter character.

As the underlying Map is directly used as store of the property values, the thread-safety of this Configuration implementation depends on the map passed to the constructor.

Notes about type safety: For properties with multiple values this implementation creates lists of type Object and stores them. If a property is assigned another value, the value is added to the list. This can cause problems if the map passed to the constructor already contains lists of other types. This should be avoided, otherwise it cannot be guaranteed that the application might throw ClassCastException exceptions later.

Author:Emmanuel Bourg
Version:$Id: MapConfiguration.java 1534429 2013-10-22 00:45:36Z henning $
Since:1.1
/** * <p> * A Map based Configuration. * </p> * <p> * This implementation of the {@code Configuration} interface is * initialized with a {@code java.util.Map}. The methods of the * {@code Configuration} interface are implemented on top of the content of * this map. The following storage scheme is used: * </p> * <p> * Property keys are directly mapped to map keys, i.e. the * {@code getProperty()} method directly performs a {@code get()} on * the map. Analogously, {@code setProperty()} or * {@code addProperty()} operations write new data into the map. If a value * is added to an existing property, a {@code java.util.List} is created, * which stores the values of this property. * </p> * <p> * An important use case of this class is to treat a map as a * {@code Configuration} allowing access to its data through the richer * interface. This can be a bit problematic in some cases because the map may * contain values that need not adhere to the default storage scheme used by * typical configuration implementations, e.g. regarding lists. In such cases * care must be taken when manipulating the data through the * {@code Configuration} interface, e.g. by calling * {@code addProperty()}; results may be different than expected. * </p> * <p> * An important point is the handling of list delimiters: If delimiter parsing * is enabled (which it is per default), {@code getProperty()} checks * whether the value of a property is a string and whether it contains the list * delimiter character. If this is the case, the value is split at the delimiter * resulting in a list. This split operation typically also involves trimming * the single values as the list delimiter character may be surrounded by * whitespace. Trimming can be disabled with the * {@link #setTrimmingDisabled(boolean)} method. The whole list splitting * behavior can be disabled using the * {@link #setDelimiterParsingDisabled(boolean)} method. * </p> * <p> * Notice that list splitting is only performed for single string values. If a * property has multiple values, the single values are not split even if they * contain the list delimiter character. * </p> * <p> * As the underlying {@code Map} is directly used as store of the property * values, the thread-safety of this {@code Configuration} implementation * depends on the map passed to the constructor. * </p> * <p> * Notes about type safety: For properties with multiple values this implementation * creates lists of type {@code Object} and stores them. If a property is assigned * another value, the value is added to the list. This can cause problems if the * map passed to the constructor already contains lists of other types. This * should be avoided, otherwise it cannot be guaranteed that the application * might throw {@code ClassCastException} exceptions later. * </p> * * @author Emmanuel Bourg * @version $Id: MapConfiguration.java 1534429 2013-10-22 00:45:36Z henning $ * @since 1.1 */
public class MapConfiguration extends AbstractConfiguration implements Cloneable {
The Map decorated by this configuration.
/** The Map decorated by this configuration. */
protected Map<String, Object> map;
A flag whether trimming of property values should be disabled.
/** A flag whether trimming of property values should be disabled.*/
private boolean trimmingDisabled;
Create a Configuration decorator around the specified Map. The map is used to store the configuration properties, any change will also affect the Map.
Params:
  • map – the map
/** * Create a Configuration decorator around the specified Map. The map is * used to store the configuration properties, any change will also affect * the Map. * * @param map the map */
public MapConfiguration(Map<String, ?> map) { this.map = (Map<String, Object>) map; }
Creates a new instance of MapConfiguration and initializes its content from the specified Properties object. The resulting configuration is not connected to the Properties object, but all keys which are strings are copied (keys of other types are ignored).
Params:
  • props – the Properties object defining the content of this configuration
Throws:
Since:1.8
/** * Creates a new instance of {@code MapConfiguration} and initializes its * content from the specified {@code Properties} object. The resulting * configuration is not connected to the {@code Properties} object, but all * keys which are strings are copied (keys of other types are ignored). * * @param props the {@code Properties} object defining the content of this * configuration * @throws NullPointerException if the {@code Properties} object is * <b>null</b> * @since 1.8 */
public MapConfiguration(Properties props) { map = convertPropertiesToMap(props); }
Return the Map decorated by this configuration.
Returns:the map this configuration is based onto
/** * Return the Map decorated by this configuration. * * @return the map this configuration is based onto */
public Map<String, Object> getMap() { return map; }
Returns the flag whether trimming of property values is disabled.
Returns:true if trimming of property values is disabled; false otherwise
Since:1.7
/** * Returns the flag whether trimming of property values is disabled. * * @return <b>true</b> if trimming of property values is disabled; * <b>false</b> otherwise * @since 1.7 */
public boolean isTrimmingDisabled() { return trimmingDisabled; }
Sets a flag whether trimming of property values is disabled. This flag is only evaluated if list splitting is enabled. Refer to the header comment for more information about list splitting and trimming.
Params:
  • trimmingDisabled – a flag whether trimming of property values should be disabled
Since:1.7
/** * Sets a flag whether trimming of property values is disabled. This flag is * only evaluated if list splitting is enabled. Refer to the header comment * for more information about list splitting and trimming. * * @param trimmingDisabled a flag whether trimming of property values should * be disabled * @since 1.7 */
public void setTrimmingDisabled(boolean trimmingDisabled) { this.trimmingDisabled = trimmingDisabled; } public Object getProperty(String key) { Object value = map.get(key); if ((value instanceof String) && (!isDelimiterParsingDisabled())) { List<String> list = PropertyConverter.split((String) value, getListDelimiter(), !isTrimmingDisabled()); return list.size() > 1 ? list : list.get(0); } else { return value; } } @Override protected void addPropertyDirect(String key, Object value) { Object previousValue = getProperty(key); if (previousValue == null) { map.put(key, value); } else if (previousValue instanceof List) { // the value is added to the existing list // Note: This is problematic. See header comment! ((List<Object>) previousValue).add(value); } else { // the previous value is replaced by a list containing the previous value and the new value List<Object> list = new ArrayList<Object>(); list.add(previousValue); list.add(value); map.put(key, list); } } public boolean isEmpty() { return map.isEmpty(); } public boolean containsKey(String key) { return map.containsKey(key); } @Override protected void clearPropertyDirect(String key) { map.remove(key); } public Iterator<String> getKeys() { return map.keySet().iterator(); }
Returns a copy of this object. The returned configuration will contain the same properties as the original. Event listeners are not cloned.
Returns:the copy
Since:1.3
/** * Returns a copy of this object. The returned configuration will contain * the same properties as the original. Event listeners are not cloned. * * @return the copy * @since 1.3 */
@Override public Object clone() { try { MapConfiguration copy = (MapConfiguration) super.clone(); copy.clearConfigurationListeners(); // Safe because ConfigurationUtils returns a map of the same types. @SuppressWarnings("unchecked") Map<String, Object> clonedMap = (Map<String, Object>) ConfigurationUtils.clone(map); copy.map = clonedMap; return copy; } catch (CloneNotSupportedException cex) { // cannot happen throw new ConfigurationRuntimeException(cex); } }
Helper method for copying all string keys from the given Properties object to a newly created map.
Params:
  • props – the Properties to be copied
Returns:a newly created map with all string keys of the properties
/** * Helper method for copying all string keys from the given * {@code Properties} object to a newly created map. * * @param props the {@code Properties} to be copied * @return a newly created map with all string keys of the properties */
private static Map<String, Object> convertPropertiesToMap(final Properties props) { return new AbstractMap<String, Object>() { @Override public Set<Map.Entry<String, Object>> entrySet() { Set<Map.Entry<String, Object>> entries = new HashSet<Map.Entry<String, Object>>(); for (final Map.Entry<Object, Object> propertyEntry : props.entrySet()) { if (propertyEntry.getKey() instanceof String) { entries.add(new Map.Entry<String, Object>() { public String getKey() { return propertyEntry.getKey().toString(); } public Object getValue() { return propertyEntry.getValue(); } public Object setValue(Object value) { throw new UnsupportedOperationException(); } }); } } return entries; } }; } }