MapConfiguration.java

  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. package org.apache.commons.configuration2;

  18. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.Iterator;
  21. import java.util.List;
  22. import java.util.Map;
  23. import java.util.Objects;
  24. import java.util.Properties;

  25. import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;

  26. /**
  27.  * <p>
  28.  * A Map based Configuration.
  29.  * </p>
  30.  * <p>
  31.  * This implementation of the {@code Configuration} interface is initialized with a {@link java.util.Map}. The methods
  32.  * of the {@code Configuration} interface are implemented on top of the content of this map. The following storage
  33.  * scheme is used:
  34.  * </p>
  35.  * <p>
  36.  * Property keys are directly mapped to map keys, i.e. the {@code getProperty()} method directly performs a
  37.  * {@code get()} on the map. Analogously, {@code setProperty()} or {@code addProperty()} operations write new data into
  38.  * the map. If a value is added to an existing property, a {@link java.util.List} is created, which stores the values of
  39.  * this property.
  40.  * </p>
  41.  * <p>
  42.  * An important use case of this class is to treat a map as a {@code Configuration} allowing access to its data through
  43.  * the richer interface. This can be a bit problematic in some cases because the map may contain values that need not
  44.  * adhere to the default storage scheme used by typical configuration implementations, for example regarding lists. In such
  45.  * cases care must be taken when manipulating the data through the {@code Configuration} interface, for example by calling
  46.  * {@code addProperty()}; results may be different than expected.
  47.  * </p>
  48.  * <p>
  49.  * The handling of list delimiters is a bit different for this configuration implementation: When a property of type
  50.  * String is queried, it is passed to the current {@link org.apache.commons.configuration2.convert.ListDelimiterHandler
  51.  * ListDelimiterHandler} which may generate multiple values. Note that per default a list delimiter handler is set which
  52.  * does not do any list splitting, so this feature is disabled. It can be enabled by setting a properly configured
  53.  * {@code ListDelimiterHandler} implementation, for example a
  54.  * {@link org.apache.commons.configuration2.convert.DefaultListDelimiterHandler DefaultListDelimiterHandler} object.
  55.  * </p>
  56.  * <p>
  57.  * Notice that list splitting is only performed for single string values. If a property has multiple values, the single
  58.  * values are not split even if they contain the list delimiter character.
  59.  * </p>
  60.  * <p>
  61.  * As the underlying {@code Map} is directly used as store of the property values, the thread-safety of this
  62.  * {@code Configuration} implementation depends on the map passed to the constructor.
  63.  * </p>
  64.  * <p>
  65.  * Notes about type safety: For properties with multiple values this implementation creates lists of type {@code Object}
  66.  * and stores them. If a property is assigned another value, the value is added to the list. This can cause problems if
  67.  * the map passed to the constructor already contains lists of other types. This should be avoided, otherwise it cannot
  68.  * be guaranteed that the application might throw {@code ClassCastException} exceptions later.
  69.  * </p>
  70.  *
  71.  * @since 1.1
  72.  */
  73. public class MapConfiguration extends AbstractConfiguration implements Cloneable {
  74.     /**
  75.      * Helper method for converting the type of the {@code Properties} object to a supported map type. As stated by the
  76.      * comment of the constructor, we expect the {@code Properties} object to contain only String key; therefore, it is safe
  77.      * to do this cast.
  78.      *
  79.      * @param props the {@code Properties} to be copied
  80.      * @return a newly created map with all string keys of the properties
  81.      */
  82.     @SuppressWarnings("unchecked")
  83.     private static Map<String, Object> toMap(final Properties props) {
  84.         @SuppressWarnings("rawtypes")
  85.         final Map map = props;
  86.         return map;
  87.     }

  88.     /** The Map decorated by this configuration. */
  89.     protected Map<String, Object> map;

  90.     /** A flag whether trimming of property values should be disabled. */
  91.     private boolean trimmingDisabled;

  92.     /**
  93.      * Create a Configuration decorator around the specified Map. The map is used to store the configuration properties, any
  94.      * change will also affect the Map.
  95.      *
  96.      * @param map the map
  97.      */
  98.     public MapConfiguration(final Map<String, ?> map) {
  99.         this.map = (Map<String, Object>) Objects.requireNonNull(map, "map");
  100.     }

  101.     /**
  102.      * Creates a new instance of {@code MapConfiguration} which uses the specified {@code Properties} object as its data
  103.      * store. All changes of this configuration affect the given {@code Properties} object and vice versa. Note that while
  104.      * {@code Properties} actually implements {@code Map<Object, Object>}, we expect it to contain only string keys. Other
  105.      * key types will lead to {@code ClassCastException} exceptions on certain methods.
  106.      *
  107.      * @param props the {@code Properties} object defining the content of this configuration
  108.      * @since 1.8
  109.      */
  110.     public MapConfiguration(final Properties props) {
  111.         map = toMap(Objects.requireNonNull(props));
  112.     }

  113.     @Override
  114.     protected void addPropertyDirect(final String key, final Object value) {
  115.         final Object previousValue = getProperty(key);

  116.         if (previousValue == null) {
  117.             map.put(key, value);
  118.         } else if (previousValue instanceof List) {
  119.             // the value is added to the existing list
  120.             // Note: This is problematic. See header comment!
  121.             ((List<Object>) previousValue).add(value);
  122.         } else {
  123.             // the previous value is replaced by a list containing the previous value and the new value
  124.             final List<Object> list = new ArrayList<>();
  125.             list.add(previousValue);
  126.             list.add(value);

  127.             map.put(key, list);
  128.         }
  129.     }

  130.     @Override
  131.     protected void clearPropertyDirect(final String key) {
  132.         map.remove(key);
  133.     }

  134.     /**
  135.      * Returns a copy of this object. The returned configuration will contain the same properties as the original. Event
  136.      * listeners are not cloned.
  137.      *
  138.      * @return the copy
  139.      * @since 1.3
  140.      */
  141.     @Override
  142.     public Object clone() {
  143.         try {
  144.             final MapConfiguration copy = (MapConfiguration) super.clone();
  145.             // Safe because ConfigurationUtils returns a map of the same types.
  146.             @SuppressWarnings("unchecked")
  147.             final Map<String, Object> clonedMap = (Map<String, Object>) ConfigurationUtils.clone(map);
  148.             copy.map = clonedMap;
  149.             copy.cloneInterpolator(this);
  150.             return copy;
  151.         } catch (final CloneNotSupportedException cex) {
  152.             // cannot happen
  153.             throw new ConfigurationRuntimeException(cex);
  154.         }
  155.     }

  156.     @Override
  157.     protected boolean containsKeyInternal(final String key) {
  158.         return map.containsKey(key);
  159.     }

  160.     /**
  161.      * Tests whether this configuration contains one or more matches to this value. This operation stops at first match
  162.      * but may be more expensive than the containsKey method.
  163.      * @since 2.11.0
  164.      */
  165.     @Override
  166.     protected boolean containsValueInternal(final Object value) {
  167.         return value != null && map.containsValue(value);
  168.     }

  169.     @Override
  170.     protected Iterator<String> getKeysInternal() {
  171.         return map.keySet().iterator();
  172.     }

  173.     /**
  174.      * Gets the Map decorated by this configuration.
  175.      *
  176.      * @return the map this configuration is based onto
  177.      */
  178.     public Map<String, Object> getMap() {
  179.         return map;
  180.     }

  181.     @Override
  182.     protected Object getPropertyInternal(final String key) {
  183.         final Object value = map.get(key);
  184.         if (value instanceof String) {
  185.             final Collection<String> list = getListDelimiterHandler().split((String) value, !isTrimmingDisabled());
  186.             return list.size() > 1 ? list : list.iterator().next();
  187.         }
  188.         return value;
  189.     }

  190.     @Override
  191.     protected boolean isEmptyInternal() {
  192.         return map.isEmpty();
  193.     }

  194.     /**
  195.      * Returns the flag whether trimming of property values is disabled.
  196.      *
  197.      * @return <strong>true</strong> if trimming of property values is disabled; <strong>false</strong> otherwise
  198.      * @since 1.7
  199.      */
  200.     public boolean isTrimmingDisabled() {
  201.         return trimmingDisabled;
  202.     }

  203.     /**
  204.      * Sets a flag whether trimming of property values is disabled. This flag is only evaluated if list splitting is
  205.      * enabled. Refer to the header comment for more information about list splitting and trimming.
  206.      *
  207.      * @param trimmingDisabled a flag whether trimming of property values should be disabled
  208.      * @since 1.7
  209.      */
  210.     public void setTrimmingDisabled(final boolean trimmingDisabled) {
  211.         this.trimmingDisabled = trimmingDisabled;
  212.     }

  213.     @Override
  214.     protected int sizeInternal() {
  215.         return map.size();
  216.     }

  217.     /**
  218.      * Converts this object to a String suitable for debugging and logging.
  219.      *
  220.      * @since 2.3
  221.      */
  222.     @Override
  223.     public String toString() {
  224.         return getClass().getSimpleName() + " [map=" + map + ", trimmingDisabled=" + trimmingDisabled + "]";
  225.     }
  226. }