BasicBuilderParameters.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.builder;

  18. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.HashMap;
  21. import java.util.Map;

  22. import org.apache.commons.configuration2.ConfigurationDecoder;
  23. import org.apache.commons.configuration2.beanutils.BeanHelper;
  24. import org.apache.commons.configuration2.convert.ConversionHandler;
  25. import org.apache.commons.configuration2.convert.ListDelimiterHandler;
  26. import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
  27. import org.apache.commons.configuration2.interpol.InterpolatorSpecification;
  28. import org.apache.commons.configuration2.interpol.Lookup;
  29. import org.apache.commons.configuration2.io.ConfigurationLogger;
  30. import org.apache.commons.configuration2.sync.Synchronizer;

  31. /**
  32.  * <p>
  33.  * An implementation of {@code BuilderParameters} which handles the parameters of a {@link ConfigurationBuilder} common
  34.  * to all concrete {@code Configuration} implementations.
  35.  * </p>
  36.  * <p>
  37.  * This class provides methods for setting standard properties supported by the {@code AbstractConfiguration} base
  38.  * class. A fluent interface can be used to set property values.
  39.  * </p>
  40.  * <p>
  41.  * This class is not thread-safe. It is intended that an instance is constructed and initialized by a single thread
  42.  * during configuration of a {@code ConfigurationBuilder}.
  43.  * </p>
  44.  *
  45.  * @since 2.0
  46.  */
  47. public class BasicBuilderParameters implements Cloneable, BuilderParameters, BasicBuilderProperties<BasicBuilderParameters> {
  48.     /** The key of the <em>throwExceptionOnMissing</em> property. */
  49.     private static final String PROP_THROW_EXCEPTION_ON_MISSING = "throwExceptionOnMissing";

  50.     /** The key of the <em>listDelimiterHandler</em> property. */
  51.     private static final String PROP_LIST_DELIMITER_HANDLER = "listDelimiterHandler";

  52.     /** The key of the <em>logger</em> property. */
  53.     private static final String PROP_LOGGER = "logger";

  54.     /** The key for the <em>interpolator</em> property. */
  55.     private static final String PROP_INTERPOLATOR = "interpolator";

  56.     /** The key for the <em>prefixLookups</em> property. */
  57.     private static final String PROP_PREFIX_LOOKUPS = "prefixLookups";

  58.     /** The key for the <em>defaultLookups</em> property. */
  59.     private static final String PROP_DEFAULT_LOOKUPS = "defaultLookups";

  60.     /** The key for the <em>parentInterpolator</em> property. */
  61.     private static final String PROP_PARENT_INTERPOLATOR = "parentInterpolator";

  62.     /** The key for the <em>synchronizer</em> property. */
  63.     private static final String PROP_SYNCHRONIZER = "synchronizer";

  64.     /** The key for the <em>conversionHandler</em> property. */
  65.     private static final String PROP_CONVERSION_HANDLER = "conversionHandler";

  66.     /** The key for the <em>configurationDecoder</em> property. */
  67.     private static final String PROP_CONFIGURATION_DECODER = "configurationDecoder";

  68.     /** The key for the {@code BeanHelper}. */
  69.     private static final String PROP_BEAN_HELPER = RESERVED_PARAMETER_PREFIX + "BeanHelper";

  70.     /**
  71.      * Checks whether a map with parameters is present. Throws an exception if not.
  72.      *
  73.      * @param params the map with parameters to check
  74.      * @throws IllegalArgumentException if the map is <strong>null</strong>
  75.      */
  76.     private static void checkParameters(final Map<String, Object> params) {
  77.         if (params == null) {
  78.             throw new IllegalArgumentException("Parameters map must not be null!");
  79.         }
  80.     }

  81.     /**
  82.      * Creates defensive copies for collection structures when constructing the map with parameters. It should not be
  83.      * possible to modify this object's internal state when having access to the parameters map.
  84.      *
  85.      * @param params the map with parameters to be passed to the caller
  86.      */
  87.     private static void createDefensiveCopies(final HashMap<String, Object> params) {
  88.         final Map<String, ? extends Lookup> prefixLookups = fetchPrefixLookups(params);
  89.         if (prefixLookups != null) {
  90.             params.put(PROP_PREFIX_LOOKUPS, new HashMap<>(prefixLookups));
  91.         }
  92.         final Collection<? extends Lookup> defLookups = fetchDefaultLookups(params);
  93.         if (defLookups != null) {
  94.             params.put(PROP_DEFAULT_LOOKUPS, new ArrayList<>(defLookups));
  95.         }
  96.     }

  97.     /**
  98.      * Tests whether the passed in map with parameters contains a valid collection with default lookups. This method works
  99.      * like {@link #fetchAndCheckPrefixLookups(Map)}, but tests the default lookups collection.
  100.      *
  101.      * @param params the map with parameters
  102.      * @return the collection with default lookups (may be <strong>null</strong>)
  103.      * @throws IllegalArgumentException if invalid data is found
  104.      */
  105.     private static Collection<? extends Lookup> fetchAndCheckDefaultLookups(final Map<String, Object> params) {
  106.         final Collection<?> col = fetchParameter(params, PROP_DEFAULT_LOOKUPS, Collection.class);
  107.         if (col == null) {
  108.             return null;
  109.         }

  110.         if (col.stream().noneMatch(Lookup.class::isInstance)) {
  111.             throw new IllegalArgumentException("Collection with default lookups contains invalid data: " + col);
  112.         }
  113.         return fetchDefaultLookups(params);
  114.     }

  115.     /**
  116.      * Tests whether the passed in map with parameters contains a map with prefix lookups. This method is used if the
  117.      * parameters map is from an insecure source and we cannot be sure that it contains valid data. Therefore, we have to
  118.      * map that the key for the prefix lookups actually points to a map containing keys and values of expected data types.
  119.      *
  120.      * @param params the parameters map
  121.      * @return the obtained map with prefix lookups
  122.      * @throws IllegalArgumentException if the map contains invalid data
  123.      */
  124.     private static Map<String, ? extends Lookup> fetchAndCheckPrefixLookups(final Map<String, Object> params) {
  125.         final Map<?, ?> prefixes = fetchParameter(params, PROP_PREFIX_LOOKUPS, Map.class);
  126.         if (prefixes == null) {
  127.             return null;
  128.         }
  129.         prefixes.forEach((k, v) -> {
  130.             if (!(k instanceof String) || !(v instanceof Lookup)) {
  131.                 throw new IllegalArgumentException("Map with prefix lookups contains invalid data: " + prefixes);
  132.             }
  133.         });
  134.         return fetchPrefixLookups(params);
  135.     }

  136.     /**
  137.      * Obtains the {@code BeanHelper} object from the specified map with parameters. This method can be used to obtain an
  138.      * instance from a parameters map that has been set via the {@code setBeanHelper()} method. If no such instance is
  139.      * found, result is <strong>null</strong>.
  140.      *
  141.      * @param params the map with parameters (must not be <strong>null</strong>)
  142.      * @return the {@code BeanHelper} stored in this map or <strong>null</strong>
  143.      * @throws IllegalArgumentException if the map is <strong>null</strong>
  144.      */
  145.     public static BeanHelper fetchBeanHelper(final Map<String, Object> params) {
  146.         checkParameters(params);
  147.         return (BeanHelper) params.get(PROP_BEAN_HELPER);
  148.     }

  149.     /**
  150.      * Obtains the collection with default lookups from the parameters map.
  151.      *
  152.      * @param params the map with parameters
  153.      * @return the collection with default lookups (may be <strong>null</strong>)
  154.      */
  155.     private static Collection<? extends Lookup> fetchDefaultLookups(final Map<String, Object> params) {
  156.         // This is safe to cast because we either have full control over the map
  157.         // and thus know the types of the contained values or have checked
  158.         // the content before
  159.         @SuppressWarnings("unchecked")
  160.         final Collection<? extends Lookup> defLookups = (Collection<? extends Lookup>) params.get(PROP_DEFAULT_LOOKUPS);
  161.         return defLookups;
  162.     }

  163.     /**
  164.      * Obtains a specification for a {@link ConfigurationInterpolator} from the specified map with parameters. All
  165.      * properties related to interpolation are evaluated and added to the specification object.
  166.      *
  167.      * @param params the map with parameters (must not be <strong>null</strong>)
  168.      * @return an {@code InterpolatorSpecification} object constructed with data from the map
  169.      * @throws IllegalArgumentException if the map is <strong>null</strong> or contains invalid data
  170.      */
  171.     public static InterpolatorSpecification fetchInterpolatorSpecification(final Map<String, Object> params) {
  172.         checkParameters(params);
  173.         return new InterpolatorSpecification.Builder().withInterpolator(fetchParameter(params, PROP_INTERPOLATOR, ConfigurationInterpolator.class))
  174.             .withParentInterpolator(fetchParameter(params, PROP_PARENT_INTERPOLATOR, ConfigurationInterpolator.class))
  175.             .withPrefixLookups(fetchAndCheckPrefixLookups(params)).withDefaultLookups(fetchAndCheckDefaultLookups(params)).create();
  176.     }

  177.     /**
  178.      * Obtains a parameter from a map and performs a type check.
  179.      *
  180.      * @param params the map with parameters
  181.      * @param key the key of the parameter
  182.      * @param expClass the expected class of the parameter value
  183.      * @param <T> the parameter type
  184.      * @return the value of the parameter in the correct data type
  185.      * @throws IllegalArgumentException if the parameter is not of the expected type
  186.      */
  187.     private static <T> T fetchParameter(final Map<String, Object> params, final String key, final Class<T> expClass) {
  188.         final Object value = params.get(key);
  189.         if (value == null) {
  190.             return null;
  191.         }
  192.         if (!expClass.isInstance(value)) {
  193.             throw new IllegalArgumentException(String.format("Parameter %s is not of type %s!", key, expClass.getSimpleName()));
  194.         }
  195.         return expClass.cast(value);
  196.     }

  197.     /**
  198.      * Obtains the map with prefix lookups from the parameters map.
  199.      *
  200.      * @param params the map with parameters
  201.      * @return the map with prefix lookups (may be <strong>null</strong>)
  202.      */
  203.     private static Map<String, ? extends Lookup> fetchPrefixLookups(final Map<String, Object> params) {
  204.         // This is safe to cast because we either have full control over the map
  205.         // and thus know the types of the contained values or have checked
  206.         // the content before
  207.         @SuppressWarnings("unchecked")
  208.         final Map<String, ? extends Lookup> prefixLookups = (Map<String, ? extends Lookup>) params.get(PROP_PREFIX_LOOKUPS);
  209.         return prefixLookups;
  210.     }

  211.     /** The map for storing the current property values. */
  212.     private Map<String, Object> properties;

  213.     /**
  214.      * Creates a new instance of {@code BasicBuilderParameters}.
  215.      */
  216.     public BasicBuilderParameters() {
  217.         properties = new HashMap<>();
  218.     }

  219.     /**
  220.      * Clones this object. This is useful because multiple builder instances may use a similar set of parameters. However,
  221.      * single instances of parameter objects must not assigned to multiple builders. Therefore, cloning a parameters object
  222.      * provides a solution for this use case. This method creates a new parameters object with the same content as this one.
  223.      * The internal map storing the parameter values is cloned, too, also collection structures contained in this map.
  224.      * However, no a full deep clone operation is performed. Objects like a {@code ConfigurationInterpolator} or
  225.      * {@code Lookup}s are shared between this and the newly created instance.
  226.      *
  227.      * @return a clone of this object
  228.      */
  229.     @Override
  230.     public BasicBuilderParameters clone() {
  231.         try {
  232.             final BasicBuilderParameters copy = (BasicBuilderParameters) super.clone();
  233.             copy.properties = getParameters();
  234.             return copy;
  235.         } catch (final CloneNotSupportedException cnex) {
  236.             // should not happen
  237.             throw new AssertionError(cnex);
  238.         }
  239.     }

  240.     /**
  241.      * Copies a number of properties from the given map into this object. Properties are only copied if they are defined in
  242.      * the source map.
  243.      *
  244.      * @param source the source map
  245.      * @param keys the keys to be copied
  246.      */
  247.     protected void copyPropertiesFrom(final Map<String, ?> source, final String... keys) {
  248.         for (final String key : keys) {
  249.             final Object value = source.get(key);
  250.             if (value != null) {
  251.                 storeProperty(key, value);
  252.             }
  253.         }
  254.     }

  255.     /**
  256.      * Obtains the value of the specified property from the internal map. This method can be used by derived classes if a
  257.      * specific property is to be accessed. If the given key is not found, result is <strong>null</strong>.
  258.      *
  259.      * @param key the key of the property in question
  260.      * @return the value of the property with this key or <strong>null</strong>
  261.      */
  262.     protected Object fetchProperty(final String key) {
  263.         return properties.get(key);
  264.     }

  265.     /**
  266.      * {@inheritDoc} This implementation returns a copy of the internal parameters map with the values set so far.
  267.      * Collection structures (for example for lookup objects) are stored as defensive copies, so the original data cannot be
  268.      * modified.
  269.      */
  270.     @Override
  271.     public Map<String, Object> getParameters() {
  272.         final HashMap<String, Object> result = new HashMap<>(properties);
  273.         if (result.containsKey(PROP_INTERPOLATOR)) {
  274.             // A custom ConfigurationInterpolator overrides lookups
  275.             result.remove(PROP_PREFIX_LOOKUPS);
  276.             result.remove(PROP_DEFAULT_LOOKUPS);
  277.             result.remove(PROP_PARENT_INTERPOLATOR);
  278.         }

  279.         createDefensiveCopies(result);
  280.         return result;
  281.     }

  282.     /**
  283.      * Inherits properties from the specified map. This can be used for instance to reuse parameters from one builder in
  284.      * another builder - also in parent-child relations in which a parent builder creates child builders. The purpose of
  285.      * this method is to let a concrete implementation decide which properties can be inherited. Because parameters are
  286.      * basically organized as a map it would be possible to simply copy over all properties from the source object. However,
  287.      * this is not appropriate in all cases. For instance, some properties - like a {@code ConfigurationInterpolator} - are
  288.      * tightly connected to a configuration and cannot be reused in a different context. For other properties, for example a file
  289.      * name, it does not make sense to copy it. Therefore, an implementation has to be explicit in the properties it wants
  290.      * to take over.
  291.      *
  292.      * @param source the source properties to inherit from
  293.      * @throws IllegalArgumentException if the source map is <strong>null</strong>
  294.      */
  295.     public void inheritFrom(final Map<String, ?> source) {
  296.         if (source == null) {
  297.             throw new IllegalArgumentException("Source properties must not be null!");
  298.         }
  299.         copyPropertiesFrom(source, PROP_BEAN_HELPER, PROP_CONFIGURATION_DECODER, PROP_CONVERSION_HANDLER, PROP_LIST_DELIMITER_HANDLER, PROP_LOGGER,
  300.             PROP_SYNCHRONIZER, PROP_THROW_EXCEPTION_ON_MISSING);
  301.     }

  302.     /**
  303.      * Merges this object with the given parameters object. This method adds all property values defined by the passed in
  304.      * parameters object to the internal storage which are not already in. So properties already defined in this object take
  305.      * precedence. Property names starting with the reserved parameter prefix are ignored.
  306.      *
  307.      * @param p the object whose properties should be merged (must not be <strong>null</strong>)
  308.      * @throws IllegalArgumentException if the passed in object is <strong>null</strong>
  309.      */
  310.     public void merge(final BuilderParameters p) {
  311.         if (p == null) {
  312.             throw new IllegalArgumentException("Parameters to merge must not be null!");
  313.         }
  314.         p.getParameters().forEach((k, v) -> {
  315.             if (!properties.containsKey(k) && !k.startsWith(RESERVED_PARAMETER_PREFIX)) {
  316.                 storeProperty(k, v);
  317.             }
  318.         });
  319.     }

  320.     /**
  321.      * {@inheritDoc} This implementation stores the passed in {@code BeanHelper} object in the internal parameters map, but
  322.      * uses a reserved key, so that it is not used for the initialization of properties of the managed configuration object.
  323.      * The {@code fetchBeanHelper()} method can be used to obtain the {@code BeanHelper} instance from a parameters map.
  324.      */
  325.     @Override
  326.     public BasicBuilderParameters setBeanHelper(final BeanHelper beanHelper) {
  327.         return setProperty(PROP_BEAN_HELPER, beanHelper);
  328.     }

  329.     /**
  330.      * {@inheritDoc} This implementation stores the passed in {@code ConfigurationDecoder} object in the internal parameters
  331.      * map.
  332.      */
  333.     @Override
  334.     public BasicBuilderParameters setConfigurationDecoder(final ConfigurationDecoder decoder) {
  335.         return setProperty(PROP_CONFIGURATION_DECODER, decoder);
  336.     }

  337.     /**
  338.      * {@inheritDoc} This implementation stores the passed in {@code ConversionHandler} object in the internal parameters
  339.      * map.
  340.      */
  341.     @Override
  342.     public BasicBuilderParameters setConversionHandler(final ConversionHandler handler) {
  343.         return setProperty(PROP_CONVERSION_HANDLER, handler);
  344.     }

  345.     /**
  346.      * {@inheritDoc} A defensive copy of the passed in collection is created. A <strong>null</strong> argument causes all default
  347.      * lookups to be removed from the internal parameters map.
  348.      */
  349.     @Override
  350.     public BasicBuilderParameters setDefaultLookups(final Collection<? extends Lookup> lookups) {
  351.         if (lookups == null) {
  352.             properties.remove(PROP_DEFAULT_LOOKUPS);
  353.             return this;
  354.         }
  355.         return setProperty(PROP_DEFAULT_LOOKUPS, new ArrayList<>(lookups));
  356.     }

  357.     /**
  358.      * {@inheritDoc} The passed in {@code ConfigurationInterpolator} is set without modifications.
  359.      */
  360.     @Override
  361.     public BasicBuilderParameters setInterpolator(final ConfigurationInterpolator ci) {
  362.         return setProperty(PROP_INTERPOLATOR, ci);
  363.     }

  364.     /**
  365.      * Sets the value of the <em>listDelimiterHandler</em> property. This property defines the object responsible for
  366.      * dealing with list delimiter and escaping characters. Note:
  367.      * {@link org.apache.commons.configuration2.AbstractConfiguration AbstractConfiguration} does not allow setting this
  368.      * property to <strong>null</strong>. If the default {@code ListDelimiterHandler} is to be used, do not call this method.
  369.      *
  370.      * @param handler the {@code ListDelimiterHandler}
  371.      * @return a reference to this object for method chaining
  372.      */
  373.     @Override
  374.     public BasicBuilderParameters setListDelimiterHandler(final ListDelimiterHandler handler) {
  375.         return setProperty(PROP_LIST_DELIMITER_HANDLER, handler);
  376.     }

  377.     /**
  378.      * Sets the <em>logger</em> property. With this property a concrete {@code Log} object can be set for the configuration.
  379.      * Thus logging behavior can be controlled.
  380.      *
  381.      * @param log the {@code Log} for the configuration produced by this builder
  382.      * @return a reference to this object for method chaining
  383.      */
  384.     @Override
  385.     public BasicBuilderParameters setLogger(final ConfigurationLogger log) {
  386.         return setProperty(PROP_LOGGER, log);
  387.     }

  388.     /**
  389.      * {@inheritDoc} This implementation stores the passed in {@code ConfigurationInterpolator} object in the internal
  390.      * parameters map.
  391.      */
  392.     @Override
  393.     public BasicBuilderParameters setParentInterpolator(final ConfigurationInterpolator parent) {
  394.         return setProperty(PROP_PARENT_INTERPOLATOR, parent);
  395.     }

  396.     /**
  397.      * {@inheritDoc} A defensive copy of the passed in map is created. A <strong>null</strong> argument causes all prefix lookups to
  398.      * be removed from the internal parameters map.
  399.      */
  400.     @Override
  401.     public BasicBuilderParameters setPrefixLookups(final Map<String, ? extends Lookup> lookups) {
  402.         if (lookups == null) {
  403.             properties.remove(PROP_PREFIX_LOOKUPS);
  404.             return this;
  405.         }
  406.         return setProperty(PROP_PREFIX_LOOKUPS, new HashMap<>(lookups));
  407.     }

  408.     /**
  409.      * Helper method for setting a property value.
  410.      *
  411.      * @param key the key of the property
  412.      * @param value the value of the property
  413.      * @return a reference to this object
  414.      */
  415.     private BasicBuilderParameters setProperty(final String key, final Object value) {
  416.         storeProperty(key, value);
  417.         return this;
  418.     }

  419.     /**
  420.      * {@inheritDoc} This implementation stores the passed in {@code Synchronizer} object in the internal parameters map.
  421.      */
  422.     @Override
  423.     public BasicBuilderParameters setSynchronizer(final Synchronizer sync) {
  424.         return setProperty(PROP_SYNCHRONIZER, sync);
  425.     }

  426.     /**
  427.      * Sets the value of the <em>throwExceptionOnMissing</em> property. This property controls the configuration's behavior
  428.      * if missing properties are queried: a value of <strong>true</strong> causes the configuration to throw an exception, for a value
  429.      * of <strong>false</strong> it will return <strong>null</strong> values. (Note: Methods returning a primitive data type will always throw
  430.      * an exception if the property is not defined.)
  431.      *
  432.      * @param b the value of the property
  433.      * @return a reference to this object for method chaining
  434.      */
  435.     @Override
  436.     public BasicBuilderParameters setThrowExceptionOnMissing(final boolean b) {
  437.         return setProperty(PROP_THROW_EXCEPTION_ON_MISSING, Boolean.valueOf(b));
  438.     }

  439.     /**
  440.      * Sets a property for this parameters object. Properties are stored in an internal map. With this method a new entry
  441.      * can be added to this map. If the value is <strong>null</strong>, the key is removed from the internal map. This method can be
  442.      * used by sub classes which also store properties in a map.
  443.      *
  444.      * @param key the key of the property
  445.      * @param value the value of the property
  446.      */
  447.     protected void storeProperty(final String key, final Object value) {
  448.         if (value == null) {
  449.             properties.remove(key);
  450.         } else {
  451.             properties.put(key, value);
  452.         }
  453.     }
  454. }